dspy 0.29.0 → 0.30.0
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 +121 -101
- data/lib/dspy/callbacks.rb +74 -19
- data/lib/dspy/context.rb +49 -4
- 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/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 +33 -7
- metadata +14 -60
- 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 -1423
- 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,14 +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.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vicente Reig Rincón de Arellano
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dry-configurable
|
|
@@ -151,19 +151,19 @@ dependencies:
|
|
|
151
151
|
- !ruby/object:Gem::Version
|
|
152
152
|
version: '0.1'
|
|
153
153
|
- !ruby/object:Gem::Dependency
|
|
154
|
-
name:
|
|
154
|
+
name: dspy-schema
|
|
155
155
|
requirement: !ruby/object:Gem::Requirement
|
|
156
156
|
requirements:
|
|
157
157
|
- - "~>"
|
|
158
158
|
- !ruby/object:Gem::Version
|
|
159
|
-
version:
|
|
159
|
+
version: 1.0.0
|
|
160
160
|
type: :runtime
|
|
161
161
|
prerelease: false
|
|
162
162
|
version_requirements: !ruby/object:Gem::Requirement
|
|
163
163
|
requirements:
|
|
164
164
|
- - "~>"
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
|
-
version:
|
|
166
|
+
version: 1.0.0
|
|
167
167
|
- !ruby/object:Gem::Dependency
|
|
168
168
|
name: informers
|
|
169
169
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -178,34 +178,6 @@ dependencies:
|
|
|
178
178
|
- - "~>"
|
|
179
179
|
- !ruby/object:Gem::Version
|
|
180
180
|
version: '1.2'
|
|
181
|
-
- !ruby/object:Gem::Dependency
|
|
182
|
-
name: opentelemetry-sdk
|
|
183
|
-
requirement: !ruby/object:Gem::Requirement
|
|
184
|
-
requirements:
|
|
185
|
-
- - "~>"
|
|
186
|
-
- !ruby/object:Gem::Version
|
|
187
|
-
version: '1.8'
|
|
188
|
-
type: :runtime
|
|
189
|
-
prerelease: false
|
|
190
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
191
|
-
requirements:
|
|
192
|
-
- - "~>"
|
|
193
|
-
- !ruby/object:Gem::Version
|
|
194
|
-
version: '1.8'
|
|
195
|
-
- !ruby/object:Gem::Dependency
|
|
196
|
-
name: opentelemetry-exporter-otlp
|
|
197
|
-
requirement: !ruby/object:Gem::Requirement
|
|
198
|
-
requirements:
|
|
199
|
-
- - "~>"
|
|
200
|
-
- !ruby/object:Gem::Version
|
|
201
|
-
version: '0.30'
|
|
202
|
-
type: :runtime
|
|
203
|
-
prerelease: false
|
|
204
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
205
|
-
requirements:
|
|
206
|
-
- - "~>"
|
|
207
|
-
- !ruby/object:Gem::Version
|
|
208
|
-
version: '0.30'
|
|
209
181
|
description: The Ruby framework for programming with large language models. DSPy.rb
|
|
210
182
|
brings structured LLM programming to Ruby developers. Instead of wrestling with
|
|
211
183
|
prompt strings and parsing responses, you define typed signatures using idiomatic
|
|
@@ -216,17 +188,16 @@ executables: []
|
|
|
216
188
|
extensions: []
|
|
217
189
|
extra_rdoc_files: []
|
|
218
190
|
files:
|
|
191
|
+
- LICENSE
|
|
219
192
|
- README.md
|
|
220
193
|
- lib/dspy.rb
|
|
221
194
|
- lib/dspy/callbacks.rb
|
|
222
195
|
- lib/dspy/chain_of_thought.rb
|
|
223
|
-
- lib/dspy/code_act.rb
|
|
224
196
|
- lib/dspy/context.rb
|
|
225
|
-
- lib/dspy/datasets.rb
|
|
226
|
-
- lib/dspy/datasets/ade.rb
|
|
227
197
|
- lib/dspy/error_formatter.rb
|
|
228
198
|
- lib/dspy/errors.rb
|
|
229
|
-
- lib/dspy/
|
|
199
|
+
- lib/dspy/evals.rb
|
|
200
|
+
- lib/dspy/evals/version.rb
|
|
230
201
|
- lib/dspy/events.rb
|
|
231
202
|
- lib/dspy/events/subscriber_mixin.rb
|
|
232
203
|
- lib/dspy/events/subscribers.rb
|
|
@@ -261,13 +232,11 @@ files:
|
|
|
261
232
|
- lib/dspy/memory/memory_manager.rb
|
|
262
233
|
- lib/dspy/memory/memory_record.rb
|
|
263
234
|
- lib/dspy/memory/memory_store.rb
|
|
235
|
+
- lib/dspy/mixins/instruction_updatable.rb
|
|
264
236
|
- lib/dspy/mixins/struct_builder.rb
|
|
265
237
|
- lib/dspy/mixins/type_coercion.rb
|
|
266
238
|
- lib/dspy/module.rb
|
|
267
239
|
- lib/dspy/observability.rb
|
|
268
|
-
- lib/dspy/observability/async_span_processor.rb
|
|
269
|
-
- lib/dspy/observability/observation_type.rb
|
|
270
|
-
- lib/dspy/optimizers/gaussian_process.rb
|
|
271
240
|
- lib/dspy/predict.rb
|
|
272
241
|
- lib/dspy/prediction.rb
|
|
273
242
|
- lib/dspy/prompt.rb
|
|
@@ -277,15 +246,19 @@ files:
|
|
|
277
246
|
- lib/dspy/reflection_lm.rb
|
|
278
247
|
- lib/dspy/registry/registry_manager.rb
|
|
279
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
|
|
280
252
|
- lib/dspy/schema_adapters.rb
|
|
281
253
|
- lib/dspy/signature.rb
|
|
282
254
|
- lib/dspy/storage/program_storage.rb
|
|
283
255
|
- lib/dspy/storage/storage_manager.rb
|
|
284
256
|
- lib/dspy/structured_outputs_prompt.rb
|
|
257
|
+
- lib/dspy/support/warning_filters.rb
|
|
285
258
|
- lib/dspy/teleprompt/bootstrap_strategy.rb
|
|
286
259
|
- lib/dspy/teleprompt/data_handler.rb
|
|
287
260
|
- lib/dspy/teleprompt/gepa.rb
|
|
288
|
-
- lib/dspy/teleprompt/
|
|
261
|
+
- lib/dspy/teleprompt/instruction_updates.rb
|
|
289
262
|
- lib/dspy/teleprompt/teleprompter.rb
|
|
290
263
|
- lib/dspy/teleprompt/utils.rb
|
|
291
264
|
- lib/dspy/tools.rb
|
|
@@ -298,25 +271,6 @@ files:
|
|
|
298
271
|
- lib/dspy/type_system/sorbet_json_schema.rb
|
|
299
272
|
- lib/dspy/utils/serialization.rb
|
|
300
273
|
- lib/dspy/version.rb
|
|
301
|
-
- lib/gepa.rb
|
|
302
|
-
- lib/gepa/api.rb
|
|
303
|
-
- lib/gepa/core/engine.rb
|
|
304
|
-
- lib/gepa/core/evaluation_batch.rb
|
|
305
|
-
- lib/gepa/core/result.rb
|
|
306
|
-
- lib/gepa/core/state.rb
|
|
307
|
-
- lib/gepa/logging.rb
|
|
308
|
-
- lib/gepa/logging/experiment_tracker.rb
|
|
309
|
-
- lib/gepa/logging/logger.rb
|
|
310
|
-
- lib/gepa/proposer/base.rb
|
|
311
|
-
- lib/gepa/proposer/merge_proposer.rb
|
|
312
|
-
- lib/gepa/proposer/reflective_mutation/base.rb
|
|
313
|
-
- lib/gepa/proposer/reflective_mutation/reflective_mutation.rb
|
|
314
|
-
- lib/gepa/strategies/batch_sampler.rb
|
|
315
|
-
- lib/gepa/strategies/candidate_selector.rb
|
|
316
|
-
- lib/gepa/strategies/component_selector.rb
|
|
317
|
-
- lib/gepa/strategies/instruction_proposal.rb
|
|
318
|
-
- lib/gepa/telemetry.rb
|
|
319
|
-
- lib/gepa/utils/pareto.rb
|
|
320
274
|
homepage: https://github.com/vicentereig/dspy.rb
|
|
321
275
|
licenses:
|
|
322
276
|
- MIT
|
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
|