dspy 0.29.1 → 0.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE +45 -0
- data/README.md +159 -95
- data/lib/dspy/callbacks.rb +93 -19
- data/lib/dspy/context.rb +101 -5
- data/lib/dspy/errors.rb +19 -1
- data/lib/dspy/{datasets.rb → evals/version.rb} +2 -3
- data/lib/dspy/{evaluate.rb → evals.rb} +373 -110
- data/lib/dspy/mixins/instruction_updatable.rb +22 -0
- data/lib/dspy/module.rb +213 -17
- data/lib/dspy/observability.rb +40 -182
- data/lib/dspy/predict.rb +10 -2
- data/lib/dspy/propose/dataset_summary_generator.rb +28 -18
- data/lib/dspy/re_act.rb +21 -0
- data/lib/dspy/schema/sorbet_json_schema.rb +302 -0
- data/lib/dspy/schema/version.rb +7 -0
- data/lib/dspy/schema.rb +4 -0
- data/lib/dspy/structured_outputs_prompt.rb +48 -0
- data/lib/dspy/support/warning_filters.rb +27 -0
- data/lib/dspy/teleprompt/gepa.rb +9 -588
- data/lib/dspy/teleprompt/instruction_updates.rb +94 -0
- data/lib/dspy/teleprompt/teleprompter.rb +6 -6
- data/lib/dspy/teleprompt/utils.rb +5 -65
- data/lib/dspy/type_system/sorbet_json_schema.rb +2 -299
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +39 -7
- metadata +18 -61
- data/lib/dspy/code_act.rb +0 -477
- data/lib/dspy/datasets/ade.rb +0 -90
- data/lib/dspy/observability/async_span_processor.rb +0 -250
- data/lib/dspy/observability/observation_type.rb +0 -65
- data/lib/dspy/optimizers/gaussian_process.rb +0 -141
- data/lib/dspy/teleprompt/mipro_v2.rb +0 -1672
- data/lib/gepa/api.rb +0 -61
- data/lib/gepa/core/engine.rb +0 -226
- data/lib/gepa/core/evaluation_batch.rb +0 -26
- data/lib/gepa/core/result.rb +0 -92
- data/lib/gepa/core/state.rb +0 -231
- data/lib/gepa/logging/experiment_tracker.rb +0 -54
- data/lib/gepa/logging/logger.rb +0 -57
- data/lib/gepa/logging.rb +0 -9
- data/lib/gepa/proposer/base.rb +0 -27
- data/lib/gepa/proposer/merge_proposer.rb +0 -424
- data/lib/gepa/proposer/reflective_mutation/base.rb +0 -48
- data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +0 -188
- data/lib/gepa/strategies/batch_sampler.rb +0 -91
- data/lib/gepa/strategies/candidate_selector.rb +0 -97
- data/lib/gepa/strategies/component_selector.rb +0 -57
- data/lib/gepa/strategies/instruction_proposal.rb +0 -120
- data/lib/gepa/telemetry.rb +0 -122
- data/lib/gepa/utils/pareto.rb +0 -119
- data/lib/gepa.rb +0 -21
metadata
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dspy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.30.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vicente Reig Rincón de Arellano
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-26 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: dry-configurable
|
|
@@ -150,19 +151,19 @@ dependencies:
|
|
|
150
151
|
- !ruby/object:Gem::Version
|
|
151
152
|
version: '0.1'
|
|
152
153
|
- !ruby/object:Gem::Dependency
|
|
153
|
-
name:
|
|
154
|
+
name: dspy-schema
|
|
154
155
|
requirement: !ruby/object:Gem::Requirement
|
|
155
156
|
requirements:
|
|
156
157
|
- - "~>"
|
|
157
158
|
- !ruby/object:Gem::Version
|
|
158
|
-
version:
|
|
159
|
+
version: 1.0.0
|
|
159
160
|
type: :runtime
|
|
160
161
|
prerelease: false
|
|
161
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
162
163
|
requirements:
|
|
163
164
|
- - "~>"
|
|
164
165
|
- !ruby/object:Gem::Version
|
|
165
|
-
version:
|
|
166
|
+
version: 1.0.0
|
|
166
167
|
- !ruby/object:Gem::Dependency
|
|
167
168
|
name: informers
|
|
168
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -177,34 +178,6 @@ dependencies:
|
|
|
177
178
|
- - "~>"
|
|
178
179
|
- !ruby/object:Gem::Version
|
|
179
180
|
version: '1.2'
|
|
180
|
-
- !ruby/object:Gem::Dependency
|
|
181
|
-
name: opentelemetry-sdk
|
|
182
|
-
requirement: !ruby/object:Gem::Requirement
|
|
183
|
-
requirements:
|
|
184
|
-
- - "~>"
|
|
185
|
-
- !ruby/object:Gem::Version
|
|
186
|
-
version: '1.8'
|
|
187
|
-
type: :runtime
|
|
188
|
-
prerelease: false
|
|
189
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
190
|
-
requirements:
|
|
191
|
-
- - "~>"
|
|
192
|
-
- !ruby/object:Gem::Version
|
|
193
|
-
version: '1.8'
|
|
194
|
-
- !ruby/object:Gem::Dependency
|
|
195
|
-
name: opentelemetry-exporter-otlp
|
|
196
|
-
requirement: !ruby/object:Gem::Requirement
|
|
197
|
-
requirements:
|
|
198
|
-
- - "~>"
|
|
199
|
-
- !ruby/object:Gem::Version
|
|
200
|
-
version: '0.30'
|
|
201
|
-
type: :runtime
|
|
202
|
-
prerelease: false
|
|
203
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
204
|
-
requirements:
|
|
205
|
-
- - "~>"
|
|
206
|
-
- !ruby/object:Gem::Version
|
|
207
|
-
version: '0.30'
|
|
208
181
|
description: The Ruby framework for programming with large language models. DSPy.rb
|
|
209
182
|
brings structured LLM programming to Ruby developers. Instead of wrestling with
|
|
210
183
|
prompt strings and parsing responses, you define typed signatures using idiomatic
|
|
@@ -215,17 +188,16 @@ executables: []
|
|
|
215
188
|
extensions: []
|
|
216
189
|
extra_rdoc_files: []
|
|
217
190
|
files:
|
|
191
|
+
- LICENSE
|
|
218
192
|
- README.md
|
|
219
193
|
- lib/dspy.rb
|
|
220
194
|
- lib/dspy/callbacks.rb
|
|
221
195
|
- lib/dspy/chain_of_thought.rb
|
|
222
|
-
- lib/dspy/code_act.rb
|
|
223
196
|
- lib/dspy/context.rb
|
|
224
|
-
- lib/dspy/datasets.rb
|
|
225
|
-
- lib/dspy/datasets/ade.rb
|
|
226
197
|
- lib/dspy/error_formatter.rb
|
|
227
198
|
- lib/dspy/errors.rb
|
|
228
|
-
- lib/dspy/
|
|
199
|
+
- lib/dspy/evals.rb
|
|
200
|
+
- lib/dspy/evals/version.rb
|
|
229
201
|
- lib/dspy/events.rb
|
|
230
202
|
- lib/dspy/events/subscriber_mixin.rb
|
|
231
203
|
- lib/dspy/events/subscribers.rb
|
|
@@ -260,13 +232,11 @@ files:
|
|
|
260
232
|
- lib/dspy/memory/memory_manager.rb
|
|
261
233
|
- lib/dspy/memory/memory_record.rb
|
|
262
234
|
- lib/dspy/memory/memory_store.rb
|
|
235
|
+
- lib/dspy/mixins/instruction_updatable.rb
|
|
263
236
|
- lib/dspy/mixins/struct_builder.rb
|
|
264
237
|
- lib/dspy/mixins/type_coercion.rb
|
|
265
238
|
- lib/dspy/module.rb
|
|
266
239
|
- lib/dspy/observability.rb
|
|
267
|
-
- lib/dspy/observability/async_span_processor.rb
|
|
268
|
-
- lib/dspy/observability/observation_type.rb
|
|
269
|
-
- lib/dspy/optimizers/gaussian_process.rb
|
|
270
240
|
- lib/dspy/predict.rb
|
|
271
241
|
- lib/dspy/prediction.rb
|
|
272
242
|
- lib/dspy/prompt.rb
|
|
@@ -276,15 +246,19 @@ files:
|
|
|
276
246
|
- lib/dspy/reflection_lm.rb
|
|
277
247
|
- lib/dspy/registry/registry_manager.rb
|
|
278
248
|
- lib/dspy/registry/signature_registry.rb
|
|
249
|
+
- lib/dspy/schema.rb
|
|
250
|
+
- lib/dspy/schema/sorbet_json_schema.rb
|
|
251
|
+
- lib/dspy/schema/version.rb
|
|
279
252
|
- lib/dspy/schema_adapters.rb
|
|
280
253
|
- lib/dspy/signature.rb
|
|
281
254
|
- lib/dspy/storage/program_storage.rb
|
|
282
255
|
- lib/dspy/storage/storage_manager.rb
|
|
283
256
|
- lib/dspy/structured_outputs_prompt.rb
|
|
257
|
+
- lib/dspy/support/warning_filters.rb
|
|
284
258
|
- lib/dspy/teleprompt/bootstrap_strategy.rb
|
|
285
259
|
- lib/dspy/teleprompt/data_handler.rb
|
|
286
260
|
- lib/dspy/teleprompt/gepa.rb
|
|
287
|
-
- lib/dspy/teleprompt/
|
|
261
|
+
- lib/dspy/teleprompt/instruction_updates.rb
|
|
288
262
|
- lib/dspy/teleprompt/teleprompter.rb
|
|
289
263
|
- lib/dspy/teleprompt/utils.rb
|
|
290
264
|
- lib/dspy/tools.rb
|
|
@@ -297,29 +271,11 @@ files:
|
|
|
297
271
|
- lib/dspy/type_system/sorbet_json_schema.rb
|
|
298
272
|
- lib/dspy/utils/serialization.rb
|
|
299
273
|
- lib/dspy/version.rb
|
|
300
|
-
- lib/gepa.rb
|
|
301
|
-
- lib/gepa/api.rb
|
|
302
|
-
- lib/gepa/core/engine.rb
|
|
303
|
-
- lib/gepa/core/evaluation_batch.rb
|
|
304
|
-
- lib/gepa/core/result.rb
|
|
305
|
-
- lib/gepa/core/state.rb
|
|
306
|
-
- lib/gepa/logging.rb
|
|
307
|
-
- lib/gepa/logging/experiment_tracker.rb
|
|
308
|
-
- lib/gepa/logging/logger.rb
|
|
309
|
-
- lib/gepa/proposer/base.rb
|
|
310
|
-
- lib/gepa/proposer/merge_proposer.rb
|
|
311
|
-
- lib/gepa/proposer/reflective_mutation/base.rb
|
|
312
|
-
- lib/gepa/proposer/reflective_mutation/reflective_mutation.rb
|
|
313
|
-
- lib/gepa/strategies/batch_sampler.rb
|
|
314
|
-
- lib/gepa/strategies/candidate_selector.rb
|
|
315
|
-
- lib/gepa/strategies/component_selector.rb
|
|
316
|
-
- lib/gepa/strategies/instruction_proposal.rb
|
|
317
|
-
- lib/gepa/telemetry.rb
|
|
318
|
-
- lib/gepa/utils/pareto.rb
|
|
319
274
|
homepage: https://github.com/vicentereig/dspy.rb
|
|
320
275
|
licenses:
|
|
321
276
|
- MIT
|
|
322
277
|
metadata: {}
|
|
278
|
+
post_install_message:
|
|
323
279
|
rdoc_options: []
|
|
324
280
|
require_paths:
|
|
325
281
|
- lib
|
|
@@ -334,7 +290,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
334
290
|
- !ruby/object:Gem::Version
|
|
335
291
|
version: '0'
|
|
336
292
|
requirements: []
|
|
337
|
-
rubygems_version: 3.
|
|
293
|
+
rubygems_version: 3.0.3.1
|
|
294
|
+
signing_key:
|
|
338
295
|
specification_version: 4
|
|
339
296
|
summary: The Ruby framework for programming—rather than prompting—language models.
|
|
340
297
|
test_files: []
|
data/lib/dspy/code_act.rb
DELETED
|
@@ -1,477 +0,0 @@
|
|
|
1
|
-
# typed: strict
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require 'sorbet-runtime'
|
|
5
|
-
require_relative 'predict'
|
|
6
|
-
require_relative 'signature'
|
|
7
|
-
require 'json'
|
|
8
|
-
require 'stringio'
|
|
9
|
-
require_relative 'mixins/struct_builder'
|
|
10
|
-
require_relative 'type_serializer'
|
|
11
|
-
|
|
12
|
-
module DSPy
|
|
13
|
-
# Define a simple struct for CodeAct history entries with proper type annotations
|
|
14
|
-
class CodeActHistoryEntry < T::Struct
|
|
15
|
-
const :step, Integer
|
|
16
|
-
prop :thought, T.nilable(String)
|
|
17
|
-
prop :ruby_code, T.nilable(String)
|
|
18
|
-
prop :execution_result, T.nilable(String)
|
|
19
|
-
prop :error_message, String
|
|
20
|
-
|
|
21
|
-
# Custom serialization to ensure compatibility with the rest of the code
|
|
22
|
-
def to_h
|
|
23
|
-
{
|
|
24
|
-
step: step,
|
|
25
|
-
thought: thought,
|
|
26
|
-
ruby_code: ruby_code,
|
|
27
|
-
execution_result: execution_result,
|
|
28
|
-
error_message: error_message
|
|
29
|
-
}.compact
|
|
30
|
-
end
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
# Defines the signature for Ruby code generation
|
|
34
|
-
class RubyCodeGeneration < DSPy::Signature
|
|
35
|
-
description "Generate Ruby code to solve the given task."
|
|
36
|
-
|
|
37
|
-
input do
|
|
38
|
-
const :task, String,
|
|
39
|
-
description: "JSON representation of all input fields for the task"
|
|
40
|
-
const :context, String,
|
|
41
|
-
description: "Available variables and previous results from code execution history"
|
|
42
|
-
const :history, T::Array[CodeActHistoryEntry],
|
|
43
|
-
description: "Previous thoughts and code executions with their results. Use this to understand what has been tried and what variables are available."
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
output do
|
|
47
|
-
const :thought, String,
|
|
48
|
-
description: "Reasoning about the approach to solve the task with Ruby code"
|
|
49
|
-
const :ruby_code, String,
|
|
50
|
-
description: "Ruby code to execute. This should be valid Ruby code that can be evaluated safely. Avoid system calls, file operations, or other potentially dangerous operations."
|
|
51
|
-
const :explanation, String,
|
|
52
|
-
description: "Brief explanation of what the code does and why this approach was chosen"
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
class CodeActNextStep < T::Enum
|
|
57
|
-
enums do
|
|
58
|
-
Continue = new("continue")
|
|
59
|
-
Finish = new("finish")
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# Defines the signature for processing code execution results
|
|
64
|
-
class RubyCodeObservation < DSPy::Signature
|
|
65
|
-
description "Process the result of Ruby code execution and decide what to do next."
|
|
66
|
-
|
|
67
|
-
input do
|
|
68
|
-
const :task, String,
|
|
69
|
-
description: "JSON representation of all input fields for the task"
|
|
70
|
-
const :history, T::Array[CodeActHistoryEntry],
|
|
71
|
-
description: "Previous thoughts, code executions, and their results"
|
|
72
|
-
const :execution_result, T.nilable(String),
|
|
73
|
-
description: "The result from executing the Ruby code"
|
|
74
|
-
const :error_message, String,
|
|
75
|
-
description: "Error message if the code execution failed (empty string if no error)"
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
output do
|
|
79
|
-
const :observation, String,
|
|
80
|
-
description: "Analysis of the execution result and what it means for solving the task"
|
|
81
|
-
const :next_step, CodeActNextStep,
|
|
82
|
-
description: "What to do next: '#{CodeActNextStep::Continue}' to continue with more code or '#{CodeActNextStep::Finish}' if the task is complete"
|
|
83
|
-
const :final_answer, T.nilable(String),
|
|
84
|
-
description: "If next_step is 'finish', provide the final answer to the task based on the execution results"
|
|
85
|
-
end
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
# CodeAct Agent using Think-Code-Observe pattern
|
|
89
|
-
class CodeAct < Predict
|
|
90
|
-
extend T::Sig
|
|
91
|
-
include Mixins::StructBuilder
|
|
92
|
-
|
|
93
|
-
sig { returns(T.class_of(DSPy::Signature)) }
|
|
94
|
-
attr_reader :original_signature_class
|
|
95
|
-
|
|
96
|
-
sig { returns(T.class_of(T::Struct)) }
|
|
97
|
-
attr_reader :enhanced_output_struct
|
|
98
|
-
|
|
99
|
-
sig { returns(Integer) }
|
|
100
|
-
attr_reader :max_iterations
|
|
101
|
-
|
|
102
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
103
|
-
attr_reader :execution_context
|
|
104
|
-
|
|
105
|
-
sig { params(signature_class: T.class_of(DSPy::Signature), max_iterations: Integer).void }
|
|
106
|
-
def initialize(signature_class, max_iterations: 10)
|
|
107
|
-
@original_signature_class = signature_class
|
|
108
|
-
@max_iterations = max_iterations
|
|
109
|
-
@execution_context = T.let({}, T::Hash[Symbol, T.untyped])
|
|
110
|
-
|
|
111
|
-
# Create code generator using Predict to preserve field descriptions
|
|
112
|
-
@code_generator = T.let(DSPy::Predict.new(RubyCodeGeneration), DSPy::Predict)
|
|
113
|
-
|
|
114
|
-
# Create observation processor using Predict to preserve field descriptions
|
|
115
|
-
@observation_processor = T.let(DSPy::Predict.new(RubyCodeObservation), DSPy::Predict)
|
|
116
|
-
|
|
117
|
-
# Create enhanced output struct with CodeAct fields
|
|
118
|
-
@enhanced_output_struct = create_enhanced_output_struct(signature_class)
|
|
119
|
-
enhanced_output_struct = @enhanced_output_struct
|
|
120
|
-
|
|
121
|
-
# Create enhanced signature class
|
|
122
|
-
enhanced_signature = Class.new(DSPy::Signature) do
|
|
123
|
-
# Set the description
|
|
124
|
-
description signature_class.description
|
|
125
|
-
|
|
126
|
-
# Use the same input struct
|
|
127
|
-
@input_struct_class = signature_class.input_struct_class
|
|
128
|
-
|
|
129
|
-
# Use the enhanced output struct with CodeAct fields
|
|
130
|
-
@output_struct_class = enhanced_output_struct
|
|
131
|
-
|
|
132
|
-
# Store original signature name
|
|
133
|
-
@original_signature_name = signature_class.name
|
|
134
|
-
|
|
135
|
-
class << self
|
|
136
|
-
attr_reader :input_struct_class, :output_struct_class, :original_signature_name
|
|
137
|
-
|
|
138
|
-
# Override name to return the original signature name
|
|
139
|
-
def name
|
|
140
|
-
@original_signature_name || super
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
end
|
|
144
|
-
|
|
145
|
-
# Call parent constructor with enhanced signature
|
|
146
|
-
super(enhanced_signature)
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
sig { override.returns(T::Array[[String, DSPy::Module]]) }
|
|
150
|
-
def named_predictors
|
|
151
|
-
pairs = T.let([], T::Array[[String, DSPy::Module]])
|
|
152
|
-
pairs << ["code_generator", @code_generator]
|
|
153
|
-
pairs << ["observation_processor", @observation_processor]
|
|
154
|
-
pairs
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
sig { override.returns(T::Array[DSPy::Module]) }
|
|
158
|
-
def predictors
|
|
159
|
-
named_predictors.map { |(_, predictor)| predictor }
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
sig { params(kwargs: T.untyped).returns(T.untyped).override }
|
|
163
|
-
def forward(**kwargs)
|
|
164
|
-
# Validate input and serialize all fields as task context
|
|
165
|
-
input_struct = @original_signature_class.input_struct_class.new(**kwargs)
|
|
166
|
-
task = DSPy::TypeSerializer.serialize(input_struct).to_json
|
|
167
|
-
|
|
168
|
-
# Execute CodeAct reasoning loop
|
|
169
|
-
reasoning_result = execute_codeact_reasoning_loop(task)
|
|
170
|
-
|
|
171
|
-
# Create enhanced output with all CodeAct data
|
|
172
|
-
create_enhanced_result(kwargs, reasoning_result)
|
|
173
|
-
end
|
|
174
|
-
|
|
175
|
-
private
|
|
176
|
-
|
|
177
|
-
# Executes the main CodeAct reasoning loop (Think-Code-Observe)
|
|
178
|
-
sig { params(task: String).returns(T::Hash[Symbol, T.untyped]) }
|
|
179
|
-
def execute_codeact_reasoning_loop(task)
|
|
180
|
-
history = T.let([], T::Array[CodeActHistoryEntry])
|
|
181
|
-
final_answer = T.let(nil, T.nilable(String))
|
|
182
|
-
iterations_count = 0
|
|
183
|
-
context = ""
|
|
184
|
-
|
|
185
|
-
while should_continue_iteration?(iterations_count, final_answer)
|
|
186
|
-
iterations_count += 1
|
|
187
|
-
|
|
188
|
-
iteration_result = execute_single_iteration(
|
|
189
|
-
task, history, context, iterations_count
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
if iteration_result[:should_finish]
|
|
193
|
-
final_answer = iteration_result[:final_answer]
|
|
194
|
-
break
|
|
195
|
-
end
|
|
196
|
-
|
|
197
|
-
history = iteration_result[:history]
|
|
198
|
-
context = iteration_result[:context]
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
handle_max_iterations_if_needed(iterations_count, final_answer, history)
|
|
202
|
-
|
|
203
|
-
{
|
|
204
|
-
history: history,
|
|
205
|
-
iterations: iterations_count,
|
|
206
|
-
final_answer: final_answer || default_no_answer_message,
|
|
207
|
-
execution_context: @execution_context
|
|
208
|
-
}
|
|
209
|
-
end
|
|
210
|
-
|
|
211
|
-
# Executes a single iteration of the Think-Code-Observe loop
|
|
212
|
-
sig { params(task: String, history: T::Array[CodeActHistoryEntry], context: String, iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
213
|
-
def execute_single_iteration(task, history, context, iteration)
|
|
214
|
-
DSPy::Context.with_span(
|
|
215
|
-
operation: 'codeact.iteration',
|
|
216
|
-
'dspy.module' => 'CodeAct',
|
|
217
|
-
'codeact.iteration' => iteration,
|
|
218
|
-
'codeact.max_iterations' => @max_iterations,
|
|
219
|
-
'codeact.history_length' => history.length
|
|
220
|
-
) do
|
|
221
|
-
execution_state = execute_think_code_step(task, context, history, iteration)
|
|
222
|
-
|
|
223
|
-
observation_decision = process_observation_and_decide_next_step(
|
|
224
|
-
task, execution_state[:history], execution_state[:execution_result],
|
|
225
|
-
execution_state[:error_message], iteration
|
|
226
|
-
)
|
|
227
|
-
|
|
228
|
-
if observation_decision[:should_finish]
|
|
229
|
-
return { should_finish: true, final_answer: observation_decision[:final_answer] }
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
finalize_iteration(execution_state, iteration)
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
# Executes the Think-Code step: generates code and executes it
|
|
237
|
-
sig { params(task: String, context: String, history: T::Array[CodeActHistoryEntry], iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
238
|
-
def execute_think_code_step(task, context, history, iteration)
|
|
239
|
-
code_obj = @code_generator.forward(
|
|
240
|
-
task: task,
|
|
241
|
-
context: context.empty? ? "No previous context available." : context,
|
|
242
|
-
history: history
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
execution_result, error_message = execute_ruby_code_with_instrumentation(
|
|
246
|
-
code_obj.ruby_code, iteration
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
history << create_history_entry(
|
|
250
|
-
iteration, code_obj.thought, code_obj.ruby_code,
|
|
251
|
-
execution_result, error_message
|
|
252
|
-
)
|
|
253
|
-
|
|
254
|
-
{
|
|
255
|
-
history: history,
|
|
256
|
-
thought: code_obj.thought,
|
|
257
|
-
ruby_code: code_obj.ruby_code,
|
|
258
|
-
execution_result: execution_result,
|
|
259
|
-
error_message: error_message
|
|
260
|
-
}
|
|
261
|
-
end
|
|
262
|
-
|
|
263
|
-
# Finalizes iteration by updating context and emitting events
|
|
264
|
-
sig { params(execution_state: T::Hash[Symbol, T.untyped], iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
265
|
-
def finalize_iteration(execution_state, iteration)
|
|
266
|
-
new_context = build_context_from_history(execution_state[:history])
|
|
267
|
-
|
|
268
|
-
emit_iteration_complete_event(
|
|
269
|
-
iteration, execution_state[:thought], execution_state[:ruby_code],
|
|
270
|
-
execution_state[:execution_result], execution_state[:error_message]
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
{
|
|
274
|
-
should_finish: false,
|
|
275
|
-
history: execution_state[:history],
|
|
276
|
-
context: new_context
|
|
277
|
-
}
|
|
278
|
-
end
|
|
279
|
-
|
|
280
|
-
# Creates enhanced output struct with CodeAct-specific fields
|
|
281
|
-
sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T.class_of(T::Struct)) }
|
|
282
|
-
def create_enhanced_output_struct(signature_class)
|
|
283
|
-
input_props = signature_class.input_struct_class.props
|
|
284
|
-
output_props = signature_class.output_struct_class.props
|
|
285
|
-
|
|
286
|
-
build_enhanced_struct(
|
|
287
|
-
{ input: input_props, output: output_props },
|
|
288
|
-
{
|
|
289
|
-
history: [T::Array[T::Hash[Symbol, T.untyped]], "CodeAct execution history"],
|
|
290
|
-
iterations: [Integer, "Number of iterations executed"],
|
|
291
|
-
execution_context: [T::Hash[Symbol, T.untyped], "Variables and context from code execution"]
|
|
292
|
-
}
|
|
293
|
-
)
|
|
294
|
-
end
|
|
295
|
-
|
|
296
|
-
# Creates enhanced result struct
|
|
297
|
-
sig { params(input_kwargs: T::Hash[Symbol, T.untyped], reasoning_result: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
|
298
|
-
def create_enhanced_result(input_kwargs, reasoning_result)
|
|
299
|
-
output_field_name = @original_signature_class.output_struct_class.props.keys.first
|
|
300
|
-
|
|
301
|
-
output_data = input_kwargs.merge({
|
|
302
|
-
history: reasoning_result[:history].map(&:to_h),
|
|
303
|
-
iterations: reasoning_result[:iterations],
|
|
304
|
-
execution_context: reasoning_result[:execution_context]
|
|
305
|
-
})
|
|
306
|
-
output_data[output_field_name] = reasoning_result[:final_answer]
|
|
307
|
-
|
|
308
|
-
@enhanced_output_struct.new(**output_data)
|
|
309
|
-
end
|
|
310
|
-
|
|
311
|
-
# Helper methods for CodeAct logic
|
|
312
|
-
sig { params(iterations_count: Integer, final_answer: T.nilable(String)).returns(T::Boolean) }
|
|
313
|
-
def should_continue_iteration?(iterations_count, final_answer)
|
|
314
|
-
final_answer.nil? && (@max_iterations.nil? || iterations_count < @max_iterations)
|
|
315
|
-
end
|
|
316
|
-
|
|
317
|
-
sig { params(ruby_code: String, iteration: Integer).returns([T.nilable(String), String]) }
|
|
318
|
-
def execute_ruby_code_with_instrumentation(ruby_code, iteration)
|
|
319
|
-
DSPy::Context.with_span(
|
|
320
|
-
operation: 'codeact.code_execution',
|
|
321
|
-
'dspy.module' => 'CodeAct',
|
|
322
|
-
'codeact.iteration' => iteration,
|
|
323
|
-
'code.length' => ruby_code.length
|
|
324
|
-
) do
|
|
325
|
-
execute_ruby_code_safely(ruby_code)
|
|
326
|
-
end
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
sig { params(step: Integer, thought: String, ruby_code: String, execution_result: T.nilable(String), error_message: String).returns(CodeActHistoryEntry) }
|
|
330
|
-
def create_history_entry(step, thought, ruby_code, execution_result, error_message)
|
|
331
|
-
CodeActHistoryEntry.new(
|
|
332
|
-
step: step,
|
|
333
|
-
thought: thought,
|
|
334
|
-
ruby_code: ruby_code,
|
|
335
|
-
execution_result: execution_result,
|
|
336
|
-
error_message: error_message
|
|
337
|
-
)
|
|
338
|
-
end
|
|
339
|
-
|
|
340
|
-
sig { params(task: String, history: T::Array[CodeActHistoryEntry], execution_result: T.nilable(String), error_message: String, iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
341
|
-
def process_observation_and_decide_next_step(task, history, execution_result, error_message, iteration)
|
|
342
|
-
observation_result = @observation_processor.forward(
|
|
343
|
-
task: task,
|
|
344
|
-
history: history,
|
|
345
|
-
execution_result: execution_result,
|
|
346
|
-
error_message: error_message
|
|
347
|
-
)
|
|
348
|
-
|
|
349
|
-
return { should_finish: false } unless observation_result.next_step == CodeActNextStep::Finish
|
|
350
|
-
|
|
351
|
-
final_answer = observation_result.final_answer || execution_result || "Task completed"
|
|
352
|
-
|
|
353
|
-
{ should_finish: true, final_answer: final_answer }
|
|
354
|
-
end
|
|
355
|
-
|
|
356
|
-
sig { params(history: T::Array[CodeActHistoryEntry]).returns(String) }
|
|
357
|
-
def build_context_from_history(history)
|
|
358
|
-
context_parts = []
|
|
359
|
-
|
|
360
|
-
history.each do |entry|
|
|
361
|
-
if entry.execution_result && !entry.execution_result.empty?
|
|
362
|
-
context_parts << "Step #{entry.step} result: #{entry.execution_result}"
|
|
363
|
-
end
|
|
364
|
-
end
|
|
365
|
-
|
|
366
|
-
context_parts.join("\n")
|
|
367
|
-
end
|
|
368
|
-
|
|
369
|
-
sig { params(iteration: Integer, thought: String, ruby_code: String, execution_result: T.nilable(String), error_message: T.nilable(String)).void }
|
|
370
|
-
def emit_iteration_complete_event(iteration, thought, ruby_code, execution_result, error_message)
|
|
371
|
-
DSPy.event('codeact.iteration_complete', {
|
|
372
|
-
'codeact.iteration' => iteration,
|
|
373
|
-
'codeact.thought' => thought,
|
|
374
|
-
'codeact.ruby_code' => ruby_code,
|
|
375
|
-
'codeact.execution_result' => execution_result,
|
|
376
|
-
'codeact.error_message' => error_message,
|
|
377
|
-
'codeact.success' => error_message.nil?
|
|
378
|
-
})
|
|
379
|
-
end
|
|
380
|
-
|
|
381
|
-
sig { params(iterations_count: Integer, final_answer: T.nilable(String), history: T::Array[CodeActHistoryEntry]).void }
|
|
382
|
-
def handle_max_iterations_if_needed(iterations_count, final_answer, history)
|
|
383
|
-
if iterations_count >= @max_iterations && final_answer.nil?
|
|
384
|
-
DSPy.event('codeact.max_iterations', {
|
|
385
|
-
'codeact.iteration_count' => iterations_count,
|
|
386
|
-
'codeact.max_iterations' => @max_iterations,
|
|
387
|
-
'codeact.final_history_length' => history.length
|
|
388
|
-
})
|
|
389
|
-
end
|
|
390
|
-
end
|
|
391
|
-
|
|
392
|
-
sig { returns(String) }
|
|
393
|
-
def default_no_answer_message
|
|
394
|
-
"No solution reached within #{@max_iterations} iterations"
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
# Safe Ruby code execution method - placeholder for now
|
|
398
|
-
sig { params(ruby_code: String).returns([T.nilable(String), String]) }
|
|
399
|
-
def execute_ruby_code_safely(ruby_code)
|
|
400
|
-
# TODO: Implement proper sandboxing in Phase 2
|
|
401
|
-
# For now, use basic eval with error handling
|
|
402
|
-
original_stdout = nil
|
|
403
|
-
captured_output = nil
|
|
404
|
-
|
|
405
|
-
begin
|
|
406
|
-
# Capture stdout to get print/puts output
|
|
407
|
-
original_stdout = $stdout
|
|
408
|
-
captured_output = StringIO.new
|
|
409
|
-
$stdout = captured_output
|
|
410
|
-
|
|
411
|
-
result = eval(ruby_code, binding)
|
|
412
|
-
|
|
413
|
-
# Get the captured output
|
|
414
|
-
output = captured_output.string
|
|
415
|
-
|
|
416
|
-
# If there's captured output, use it, otherwise use the eval result
|
|
417
|
-
final_result = output.empty? ? result.to_s : output.chomp
|
|
418
|
-
|
|
419
|
-
[final_result, ""]
|
|
420
|
-
rescue SyntaxError => e
|
|
421
|
-
[nil, "Error: #{e.message}"]
|
|
422
|
-
rescue => e
|
|
423
|
-
[nil, "Error: #{e.message}"]
|
|
424
|
-
ensure
|
|
425
|
-
$stdout = original_stdout if original_stdout
|
|
426
|
-
end
|
|
427
|
-
end
|
|
428
|
-
|
|
429
|
-
sig { params(output: T.untyped).void }
|
|
430
|
-
def validate_output_schema!(output)
|
|
431
|
-
# Validate that output is an instance of the enhanced output struct
|
|
432
|
-
unless output.is_a?(@enhanced_output_struct)
|
|
433
|
-
raise "Output must be an instance of #{@enhanced_output_struct}, got #{output.class}"
|
|
434
|
-
end
|
|
435
|
-
|
|
436
|
-
# Validate original signature output fields are present
|
|
437
|
-
@original_signature_class.output_struct_class.props.each do |field_name, _prop|
|
|
438
|
-
unless output.respond_to?(field_name)
|
|
439
|
-
raise "Missing required field: #{field_name}"
|
|
440
|
-
end
|
|
441
|
-
end
|
|
442
|
-
|
|
443
|
-
# Validate CodeAct-specific fields
|
|
444
|
-
unless output.respond_to?(:history) && output.history.is_a?(Array)
|
|
445
|
-
raise "Missing or invalid history field"
|
|
446
|
-
end
|
|
447
|
-
|
|
448
|
-
unless output.respond_to?(:iterations) && output.iterations.is_a?(Integer)
|
|
449
|
-
raise "Missing or invalid iterations field"
|
|
450
|
-
end
|
|
451
|
-
|
|
452
|
-
unless output.respond_to?(:execution_context) && output.execution_context.is_a?(Hash)
|
|
453
|
-
raise "Missing or invalid execution_context field"
|
|
454
|
-
end
|
|
455
|
-
end
|
|
456
|
-
|
|
457
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
|
458
|
-
def generate_example_output
|
|
459
|
-
# Create a base example structure
|
|
460
|
-
example = {}
|
|
461
|
-
|
|
462
|
-
# Add CodeAct-specific example data
|
|
463
|
-
example[:history] = [
|
|
464
|
-
{
|
|
465
|
-
step: 1,
|
|
466
|
-
thought: "I need to write Ruby code to solve this task...",
|
|
467
|
-
ruby_code: "result = 2 + 2",
|
|
468
|
-
execution_result: "4",
|
|
469
|
-
error_message: nil
|
|
470
|
-
}
|
|
471
|
-
]
|
|
472
|
-
example[:iterations] = 1
|
|
473
|
-
example[:execution_context] = { result: 4 }
|
|
474
|
-
example
|
|
475
|
-
end
|
|
476
|
-
end
|
|
477
|
-
end
|