dspy 0.29.1 → 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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +45 -0
  3. data/README.md +120 -100
  4. data/lib/dspy/callbacks.rb +74 -19
  5. data/lib/dspy/context.rb +49 -4
  6. data/lib/dspy/errors.rb +19 -1
  7. data/lib/dspy/{datasets.rb → evals/version.rb} +2 -3
  8. data/lib/dspy/{evaluate.rb → evals.rb} +373 -110
  9. data/lib/dspy/mixins/instruction_updatable.rb +22 -0
  10. data/lib/dspy/observability.rb +40 -182
  11. data/lib/dspy/predict.rb +10 -2
  12. data/lib/dspy/propose/dataset_summary_generator.rb +28 -18
  13. data/lib/dspy/re_act.rb +21 -0
  14. data/lib/dspy/schema/sorbet_json_schema.rb +302 -0
  15. data/lib/dspy/schema/version.rb +7 -0
  16. data/lib/dspy/schema.rb +4 -0
  17. data/lib/dspy/structured_outputs_prompt.rb +48 -0
  18. data/lib/dspy/support/warning_filters.rb +27 -0
  19. data/lib/dspy/teleprompt/gepa.rb +9 -588
  20. data/lib/dspy/teleprompt/instruction_updates.rb +94 -0
  21. data/lib/dspy/teleprompt/teleprompter.rb +6 -6
  22. data/lib/dspy/teleprompt/utils.rb +5 -65
  23. data/lib/dspy/type_system/sorbet_json_schema.rb +2 -299
  24. data/lib/dspy/version.rb +1 -1
  25. data/lib/dspy.rb +33 -7
  26. metadata +18 -61
  27. data/lib/dspy/code_act.rb +0 -477
  28. data/lib/dspy/datasets/ade.rb +0 -90
  29. data/lib/dspy/observability/async_span_processor.rb +0 -250
  30. data/lib/dspy/observability/observation_type.rb +0 -65
  31. data/lib/dspy/optimizers/gaussian_process.rb +0 -141
  32. data/lib/dspy/teleprompt/mipro_v2.rb +0 -1672
  33. data/lib/gepa/api.rb +0 -61
  34. data/lib/gepa/core/engine.rb +0 -226
  35. data/lib/gepa/core/evaluation_batch.rb +0 -26
  36. data/lib/gepa/core/result.rb +0 -92
  37. data/lib/gepa/core/state.rb +0 -231
  38. data/lib/gepa/logging/experiment_tracker.rb +0 -54
  39. data/lib/gepa/logging/logger.rb +0 -57
  40. data/lib/gepa/logging.rb +0 -9
  41. data/lib/gepa/proposer/base.rb +0 -27
  42. data/lib/gepa/proposer/merge_proposer.rb +0 -424
  43. data/lib/gepa/proposer/reflective_mutation/base.rb +0 -48
  44. data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +0 -188
  45. data/lib/gepa/strategies/batch_sampler.rb +0 -91
  46. data/lib/gepa/strategies/candidate_selector.rb +0 -97
  47. data/lib/gepa/strategies/component_selector.rb +0 -57
  48. data/lib/gepa/strategies/instruction_proposal.rb +0 -120
  49. data/lib/gepa/telemetry.rb +0 -122
  50. data/lib/gepa/utils/pareto.rb +0 -119
  51. 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.29.1
4
+ version: 0.30.0
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-20 00:00:00.000000000 Z
11
+ date: 2025-10-25 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: numo-narray
154
+ name: dspy-schema
154
155
  requirement: !ruby/object:Gem::Requirement
155
156
  requirements:
156
157
  - - "~>"
157
158
  - !ruby/object:Gem::Version
158
- version: '0.9'
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: '0.9'
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/evaluate.rb
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/mipro_v2.rb
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.6.5
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