dspy 0.2.0 → 0.3.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/README.md +357 -248
- data/lib/dspy/chain_of_thought.rb +151 -11
- data/lib/dspy/instrumentation/token_tracker.rb +54 -0
- data/lib/dspy/instrumentation.rb +100 -0
- data/lib/dspy/lm/adapter.rb +41 -0
- data/lib/dspy/lm/adapter_factory.rb +59 -0
- data/lib/dspy/lm/adapters/anthropic_adapter.rb +96 -0
- data/lib/dspy/lm/adapters/openai_adapter.rb +53 -0
- data/lib/dspy/lm/adapters/ruby_llm_adapter.rb +81 -0
- data/lib/dspy/lm/errors.rb +10 -0
- data/lib/dspy/lm/response.rb +28 -0
- data/lib/dspy/lm.rb +92 -40
- data/lib/dspy/module.rb +51 -6
- data/lib/dspy/predict.rb +135 -15
- data/lib/dspy/re_act.rb +366 -191
- data/lib/dspy/schema_adapters.rb +55 -0
- data/lib/dspy/signature.rb +282 -10
- data/lib/dspy/subscribers/logger_subscriber.rb +197 -0
- data/lib/dspy/tools/{sorbet_tool.rb → base.rb} +33 -33
- data/lib/dspy/tools.rb +1 -1
- data/lib/dspy.rb +19 -10
- metadata +60 -28
- data/lib/dspy/ext/dry_schema.rb +0 -94
- data/lib/dspy/sorbet_chain_of_thought.rb +0 -91
- data/lib/dspy/sorbet_module.rb +0 -47
- data/lib/dspy/sorbet_predict.rb +0 -180
- data/lib/dspy/sorbet_re_act.rb +0 -332
- data/lib/dspy/sorbet_signature.rb +0 -218
- data/lib/dspy/types.rb +0 -3
data/lib/dspy/sorbet_predict.rb
DELETED
@@ -1,180 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'sorbet-runtime'
|
4
|
-
require_relative 'sorbet_module'
|
5
|
-
|
6
|
-
module DSPy
|
7
|
-
class SorbetPredict < DSPy::SorbetModule
|
8
|
-
extend T::Sig
|
9
|
-
|
10
|
-
sig { returns(T.class_of(SorbetSignature)) }
|
11
|
-
attr_reader :signature_class
|
12
|
-
|
13
|
-
sig { params(signature_class: T.class_of(SorbetSignature)).void }
|
14
|
-
def initialize(signature_class)
|
15
|
-
@signature_class = signature_class
|
16
|
-
end
|
17
|
-
|
18
|
-
sig { returns(String) }
|
19
|
-
def system_signature
|
20
|
-
<<-PROMPT
|
21
|
-
Your input schema fields are:
|
22
|
-
```json
|
23
|
-
#{JSON.generate(@signature_class.input_json_schema)}
|
24
|
-
```
|
25
|
-
Your output schema fields are:
|
26
|
-
```json
|
27
|
-
#{JSON.generate(@signature_class.output_json_schema)}
|
28
|
-
````
|
29
|
-
|
30
|
-
For example, based on the schemas above, a valid interaction would be:
|
31
|
-
## Input values
|
32
|
-
```json
|
33
|
-
#{JSON.generate(generate_example_input)}
|
34
|
-
```
|
35
|
-
## Output values
|
36
|
-
```json
|
37
|
-
#{JSON.generate(generate_example_output)}
|
38
|
-
```
|
39
|
-
|
40
|
-
All interactions will be structured in the following way, with the appropriate values filled in.
|
41
|
-
|
42
|
-
## Input values
|
43
|
-
```json
|
44
|
-
{input_values}
|
45
|
-
```
|
46
|
-
## Output values
|
47
|
-
Respond exclusively with the output schema fields in the json block below.
|
48
|
-
```json
|
49
|
-
{output_values}
|
50
|
-
```
|
51
|
-
|
52
|
-
In adhering to this structure, your objective is: #{@signature_class.description}
|
53
|
-
|
54
|
-
PROMPT
|
55
|
-
end
|
56
|
-
|
57
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
58
|
-
def generate_example_input
|
59
|
-
example = {}
|
60
|
-
@signature_class.input_struct_class.props.each do |name, prop|
|
61
|
-
example[name] = case prop[:type]
|
62
|
-
when T::Types::Simple
|
63
|
-
case prop[:type].raw_type.to_s
|
64
|
-
when "String" then "example text"
|
65
|
-
when "Integer" then 42
|
66
|
-
when "Float" then 3.14
|
67
|
-
else "example"
|
68
|
-
end
|
69
|
-
else
|
70
|
-
"example"
|
71
|
-
end
|
72
|
-
end
|
73
|
-
example
|
74
|
-
end
|
75
|
-
|
76
|
-
sig { returns(T::Hash[Symbol, T.untyped]) }
|
77
|
-
def generate_example_output
|
78
|
-
example = {}
|
79
|
-
@signature_class.output_struct_class.props.each do |name, prop|
|
80
|
-
example[name] = case prop[:type]
|
81
|
-
when T::Types::Simple
|
82
|
-
if prop[:type].raw_type < T::Enum
|
83
|
-
# Use the first enum value as example
|
84
|
-
prop[:type].raw_type.values.first.serialize
|
85
|
-
else
|
86
|
-
case prop[:type].raw_type.to_s
|
87
|
-
when "String" then "example result"
|
88
|
-
when "Integer" then 1
|
89
|
-
when "Float" then 0.95
|
90
|
-
else "example"
|
91
|
-
end
|
92
|
-
end
|
93
|
-
else
|
94
|
-
"example"
|
95
|
-
end
|
96
|
-
end
|
97
|
-
example
|
98
|
-
end
|
99
|
-
|
100
|
-
sig { params(input_values: T::Hash[Symbol, T.untyped]).returns(String) }
|
101
|
-
def user_signature(input_values)
|
102
|
-
<<-PROMPT
|
103
|
-
## Input Values
|
104
|
-
```json
|
105
|
-
#{JSON.generate(input_values)}
|
106
|
-
```
|
107
|
-
|
108
|
-
Respond with the corresponding output schema fields wrapped in a ```json ``` block,
|
109
|
-
starting with the heading `## Output values`.
|
110
|
-
PROMPT
|
111
|
-
end
|
112
|
-
|
113
|
-
sig { returns(DSPy::LM) }
|
114
|
-
def lm
|
115
|
-
DSPy.config.lm
|
116
|
-
end
|
117
|
-
|
118
|
-
sig { params(input_values: T.untyped).returns(T.untyped) }
|
119
|
-
def forward_untyped(**input_values)
|
120
|
-
DSPy.logger.info(module: self.class.to_s, **input_values)
|
121
|
-
|
122
|
-
# Validate input using T::Struct
|
123
|
-
begin
|
124
|
-
_input_struct = @signature_class.input_struct_class.new(**input_values)
|
125
|
-
rescue ArgumentError => e
|
126
|
-
raise PredictionInvalidError.new({ input: e.message })
|
127
|
-
end
|
128
|
-
|
129
|
-
# Use the original input_values since input_struct.to_h may not be available
|
130
|
-
# The input has already been validated through the struct instantiation
|
131
|
-
output_attributes = lm.chat(self, input_values)
|
132
|
-
|
133
|
-
# Debug: log what we got from LM
|
134
|
-
DSPy.logger.info("LM returned: #{output_attributes.inspect}")
|
135
|
-
DSPy.logger.info("Output attributes class: #{output_attributes.class}")
|
136
|
-
|
137
|
-
# Convert string keys to symbols
|
138
|
-
output_attributes = output_attributes.transform_keys(&:to_sym)
|
139
|
-
|
140
|
-
# Handle enum deserialization
|
141
|
-
output_props = @signature_class.output_struct_class.props
|
142
|
-
output_attributes = output_attributes.map do |key, value|
|
143
|
-
prop_type = output_props[key][:type] if output_props[key]
|
144
|
-
if prop_type
|
145
|
-
# Check if it's an enum (can be raw Class or T::Types::Simple)
|
146
|
-
enum_class = if prop_type.is_a?(Class) && prop_type < T::Enum
|
147
|
-
prop_type
|
148
|
-
elsif prop_type.is_a?(T::Types::Simple) && prop_type.raw_type < T::Enum
|
149
|
-
prop_type.raw_type
|
150
|
-
end
|
151
|
-
|
152
|
-
if enum_class
|
153
|
-
# Deserialize enum value
|
154
|
-
[key, enum_class.deserialize(value)]
|
155
|
-
elsif prop_type == Float || (prop_type.is_a?(T::Types::Simple) && prop_type.raw_type == Float)
|
156
|
-
# Coerce to Float
|
157
|
-
[key, value.to_f]
|
158
|
-
elsif prop_type == Integer || (prop_type.is_a?(T::Types::Simple) && prop_type.raw_type == Integer)
|
159
|
-
# Coerce to Integer
|
160
|
-
[key, value.to_i]
|
161
|
-
else
|
162
|
-
[key, value]
|
163
|
-
end
|
164
|
-
else
|
165
|
-
[key, value]
|
166
|
-
end
|
167
|
-
end.to_h
|
168
|
-
|
169
|
-
# Create output struct with validation
|
170
|
-
begin
|
171
|
-
output_struct = @signature_class.output_struct_class.new(**output_attributes)
|
172
|
-
return output_struct
|
173
|
-
rescue ArgumentError => e
|
174
|
-
raise PredictionInvalidError.new({ output: e.message })
|
175
|
-
rescue TypeError => e
|
176
|
-
raise PredictionInvalidError.new({ output: e.message })
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
end
|
data/lib/dspy/sorbet_re_act.rb
DELETED
@@ -1,332 +0,0 @@
|
|
1
|
-
# typed: strict
|
2
|
-
# frozen_string_literal: true
|
3
|
-
|
4
|
-
require 'sorbet-runtime'
|
5
|
-
require_relative 'sorbet_predict'
|
6
|
-
require_relative 'sorbet_signature'
|
7
|
-
require_relative 'sorbet_chain_of_thought'
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
module DSPy
|
11
|
-
# Define a simple struct for history entries with proper type annotations
|
12
|
-
class HistoryEntry < T::Struct
|
13
|
-
const :step, Integer
|
14
|
-
prop :thought, T.nilable(String)
|
15
|
-
prop :action, T.nilable(String)
|
16
|
-
prop :action_input, T.nilable(T.any(String, Numeric, T::Hash[T.untyped, T.untyped], T::Array[T.untyped]))
|
17
|
-
prop :observation, T.nilable(String)
|
18
|
-
|
19
|
-
# Custom serialization to ensure compatibility with the rest of the code
|
20
|
-
def to_h
|
21
|
-
{
|
22
|
-
step: step,
|
23
|
-
thought: thought,
|
24
|
-
action: action,
|
25
|
-
action_input: action_input,
|
26
|
-
observation: observation
|
27
|
-
}.compact
|
28
|
-
end
|
29
|
-
end
|
30
|
-
# Defines the signature for ReAct reasoning using Sorbet signatures
|
31
|
-
class SorbetThought < DSPy::SorbetSignature
|
32
|
-
description "Generate a thought about what to do next to answer the question."
|
33
|
-
|
34
|
-
input do
|
35
|
-
const :question, String,
|
36
|
-
description: "The question to answer"
|
37
|
-
const :history, T::Array[HistoryEntry],
|
38
|
-
description: "Previous thoughts and actions, including observations from tools. The agent MUST use information from the history to inform its actions and final answer. Each entry is a hash representing a step in the reasoning process."
|
39
|
-
const :available_tools, String,
|
40
|
-
description: "List of available tools and their JSON schemas. The agent MUST choose an action from this list or use \"finish\". For each tool, use the name exactly as specified and provide action_input as a JSON object matching the tool's schema."
|
41
|
-
end
|
42
|
-
|
43
|
-
output do
|
44
|
-
const :thought, String,
|
45
|
-
description: "Reasoning about what to do next, considering the history and observations."
|
46
|
-
const :action, String,
|
47
|
-
description: "The action to take. MUST be one of the tool names listed in `available_tools` input, or the literal string \"finish\" to provide the final answer."
|
48
|
-
const :action_input, T.any(String, T::Hash[T.untyped, T.untyped]),
|
49
|
-
description: "Input for the chosen action. If action is a tool name, this MUST be a JSON object matching the tool's schema. If action is \"finish\", this field MUST contain the final answer to the original question. This answer MUST be directly taken from the relevant Observation in the history if available. For example, if an observation showed \"Observation: 100.0\", and you are finishing, this field MUST be \"100.0\". Do not leave empty if finishing with an observed answer."
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# ReAct Agent using Sorbet signatures
|
54
|
-
class SorbetReAct < SorbetPredict
|
55
|
-
extend T::Sig
|
56
|
-
|
57
|
-
sig { returns(T.class_of(DSPy::SorbetSignature)) }
|
58
|
-
attr_reader :original_signature_class
|
59
|
-
|
60
|
-
sig { returns(T.class_of(T::Struct)) }
|
61
|
-
attr_reader :enhanced_output_struct
|
62
|
-
|
63
|
-
sig { returns(T::Hash[String, T.untyped]) }
|
64
|
-
attr_reader :tools
|
65
|
-
|
66
|
-
sig { returns(Integer) }
|
67
|
-
attr_reader :max_iterations
|
68
|
-
|
69
|
-
|
70
|
-
sig { params(signature_class: T.class_of(DSPy::SorbetSignature), tools: T::Array[T.untyped], max_iterations: Integer).void }
|
71
|
-
def initialize(signature_class, tools: [], max_iterations: 5)
|
72
|
-
@original_signature_class = signature_class
|
73
|
-
@tools = T.let({}, T::Hash[String, T.untyped])
|
74
|
-
tools.each { |tool| @tools[tool.name.downcase] = tool }
|
75
|
-
@max_iterations = max_iterations
|
76
|
-
|
77
|
-
# Create thought generator using SorbetPredict to preserve field descriptions
|
78
|
-
@thought_generator = T.let(DSPy::SorbetPredict.new(SorbetThought), DSPy::SorbetPredict)
|
79
|
-
|
80
|
-
# Create enhanced output struct with ReAct fields
|
81
|
-
@enhanced_output_struct = create_enhanced_output_struct(signature_class)
|
82
|
-
enhanced_output_struct = @enhanced_output_struct
|
83
|
-
|
84
|
-
# Create enhanced signature class
|
85
|
-
enhanced_signature = Class.new(DSPy::SorbetSignature) do
|
86
|
-
# Set the description
|
87
|
-
description signature_class.description
|
88
|
-
|
89
|
-
# Use the same input struct
|
90
|
-
@input_struct_class = signature_class.input_struct_class
|
91
|
-
|
92
|
-
# Use the enhanced output struct with ReAct fields
|
93
|
-
@output_struct_class = enhanced_output_struct
|
94
|
-
|
95
|
-
class << self
|
96
|
-
attr_reader :input_struct_class, :output_struct_class
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
# Call parent constructor with enhanced signature
|
101
|
-
super(enhanced_signature)
|
102
|
-
end
|
103
|
-
|
104
|
-
sig { params(kwargs: T.untyped).returns(T.untyped) }
|
105
|
-
def forward(**kwargs)
|
106
|
-
# Validate input using Sorbet struct validation
|
107
|
-
input_struct = @original_signature_class.input_struct_class.new(**kwargs)
|
108
|
-
|
109
|
-
# Get the question (assume first field is the question for now)
|
110
|
-
question = T.cast(input_struct.serialize.values.first, String)
|
111
|
-
|
112
|
-
history = T.let([], T::Array[HistoryEntry])
|
113
|
-
available_tools_desc = @tools.map { |name, tool| "- #{name}: #{tool.schema}" }.join("\n")
|
114
|
-
|
115
|
-
final_answer = T.let(nil, T.nilable(String))
|
116
|
-
iterations_count = 0
|
117
|
-
last_observation = T.let(nil, T.nilable(String))
|
118
|
-
potential_answer = T.let(nil, T.nilable(String))
|
119
|
-
|
120
|
-
while @max_iterations.nil? || iterations_count < @max_iterations
|
121
|
-
iterations_count += 1
|
122
|
-
|
123
|
-
# Get next thought from LM
|
124
|
-
thought_obj = @thought_generator.forward(
|
125
|
-
question: question,
|
126
|
-
history: history,
|
127
|
-
available_tools: available_tools_desc
|
128
|
-
)
|
129
|
-
|
130
|
-
thought = thought_obj.thought
|
131
|
-
action = thought_obj.action
|
132
|
-
action_input = thought_obj.action_input
|
133
|
-
|
134
|
-
# Store this step in history
|
135
|
-
step = history.length + 1
|
136
|
-
current_entry = HistoryEntry.new(
|
137
|
-
step: step,
|
138
|
-
thought: thought,
|
139
|
-
action: action,
|
140
|
-
action_input: action_input
|
141
|
-
)
|
142
|
-
history << current_entry
|
143
|
-
|
144
|
-
if action.downcase == "finish"
|
145
|
-
# If action is finish, set the final answer
|
146
|
-
final_answer = action_input.to_s
|
147
|
-
|
148
|
-
# If final_answer is empty but we have a last observation, use it
|
149
|
-
if (final_answer.nil? || final_answer.empty?) && last_observation
|
150
|
-
final_answer = last_observation
|
151
|
-
# Update the action_input for consistency by replacing the last entry
|
152
|
-
history.pop
|
153
|
-
history << HistoryEntry.new(
|
154
|
-
step: step,
|
155
|
-
thought: thought,
|
156
|
-
action: action,
|
157
|
-
action_input: final_answer
|
158
|
-
)
|
159
|
-
end
|
160
|
-
|
161
|
-
break
|
162
|
-
end
|
163
|
-
|
164
|
-
# Execute action and get observation
|
165
|
-
observation = execute_action(action, action_input)
|
166
|
-
|
167
|
-
# Store the raw observation for potential use as the final answer
|
168
|
-
last_observation = observation
|
169
|
-
|
170
|
-
# Update the entry with the observation by replacing it
|
171
|
-
history.pop
|
172
|
-
history << HistoryEntry.new(
|
173
|
-
step: step,
|
174
|
-
thought: thought,
|
175
|
-
action: action,
|
176
|
-
action_input: action_input,
|
177
|
-
observation: "Observation: #{observation}"
|
178
|
-
)
|
179
|
-
|
180
|
-
# Special case for add_numbers tool - if the question is about addition and we got a numeric result
|
181
|
-
if action.downcase == "add_numbers" &&
|
182
|
-
question.downcase.include?("plus") &&
|
183
|
-
observation.to_s.match?(/^\d+(\.\d+)?$/)
|
184
|
-
# This looks like it might be the final answer to an addition question
|
185
|
-
potential_answer = observation.to_s
|
186
|
-
end
|
187
|
-
end
|
188
|
-
|
189
|
-
# If we reached max iterations without a finish action
|
190
|
-
if final_answer.nil?
|
191
|
-
# Try to extract answer from special cases we recognized
|
192
|
-
if defined?(potential_answer) && !potential_answer.nil?
|
193
|
-
final_answer = potential_answer
|
194
|
-
# Otherwise use the last observation as fallback
|
195
|
-
elsif last_observation
|
196
|
-
final_answer = last_observation
|
197
|
-
else
|
198
|
-
final_answer = "I was unable to determine the answer"
|
199
|
-
end
|
200
|
-
|
201
|
-
# Add a finish step to history
|
202
|
-
step = history.length + 1
|
203
|
-
history << HistoryEntry.new(
|
204
|
-
step: step,
|
205
|
-
thought: "I've reached the maximum number of iterations and will provide the answer based on the tools I've used.",
|
206
|
-
action: "finish",
|
207
|
-
action_input: final_answer
|
208
|
-
)
|
209
|
-
end
|
210
|
-
|
211
|
-
# Create result with enhanced output struct
|
212
|
-
if @enhanced_output_struct
|
213
|
-
begin
|
214
|
-
# Get the first output field name from the original signature
|
215
|
-
output_field_name = @original_signature_class.output_struct_class.props.keys.first
|
216
|
-
|
217
|
-
# Create enhanced output struct with answer and history
|
218
|
-
result = @enhanced_output_struct.new(
|
219
|
-
"#{output_field_name}": final_answer || "",
|
220
|
-
history: history.map(&:to_h),
|
221
|
-
iterations: iterations_count
|
222
|
-
)
|
223
|
-
|
224
|
-
# Run validation
|
225
|
-
validate_output_schema!(result)
|
226
|
-
|
227
|
-
result
|
228
|
-
rescue => e
|
229
|
-
puts "Error creating enhanced output: #{e.message}"
|
230
|
-
# Fall back to basic result
|
231
|
-
Struct.new(:answer, :history, :iterations).new(final_answer || "", history, iterations_count)
|
232
|
-
end
|
233
|
-
else
|
234
|
-
# Basic result for compatibility
|
235
|
-
Struct.new(:answer, :history, :iterations).new(final_answer || "", history, iterations_count)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
private
|
240
|
-
|
241
|
-
sig { params(signature_class: T.class_of(DSPy::SorbetSignature)).returns(T.class_of(T::Struct)) }
|
242
|
-
def create_enhanced_output_struct(signature_class)
|
243
|
-
# Get original output props
|
244
|
-
original_props = signature_class.output_struct_class.props
|
245
|
-
|
246
|
-
# Create new struct class with ReAct fields added
|
247
|
-
Class.new(T::Struct) do
|
248
|
-
# Add all original fields
|
249
|
-
original_props.each do |name, prop|
|
250
|
-
# Extract the type and other options
|
251
|
-
type = prop[:type]
|
252
|
-
options = prop.except(:type, :type_object, :accessor_key, :sensitivity, :redaction)
|
253
|
-
|
254
|
-
# Handle default values
|
255
|
-
if options[:default]
|
256
|
-
const name, type, default: options[:default]
|
257
|
-
elsif options[:factory]
|
258
|
-
const name, type, factory: options[:factory]
|
259
|
-
else
|
260
|
-
const name, type
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
|
-
# Add ReAct-specific fields
|
265
|
-
const :history, T::Array[T::Hash[Symbol, T.untyped]]
|
266
|
-
const :iterations, Integer
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
sig { params(action: String, action_input: T.untyped).returns(String) }
|
271
|
-
def execute_action(action, action_input)
|
272
|
-
tool_name = action.downcase
|
273
|
-
tool = @tools[tool_name]
|
274
|
-
return "Tool '#{action}' not found. Available tools: #{@tools.keys.join(', ')}" unless tool
|
275
|
-
|
276
|
-
begin
|
277
|
-
result = if action_input.nil? ||
|
278
|
-
(action_input.is_a?(String) && action_input.strip.empty?)
|
279
|
-
# No input provided
|
280
|
-
tool.dynamic_call({})
|
281
|
-
else
|
282
|
-
# Pass the action_input directly to dynamic_call, which can handle
|
283
|
-
# either a Hash or a JSON string
|
284
|
-
tool.dynamic_call(action_input)
|
285
|
-
end
|
286
|
-
result.to_s
|
287
|
-
rescue => e
|
288
|
-
"Error executing tool '#{action}': #{e.message}"
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
sig { params(output: T.untyped).void }
|
293
|
-
def validate_output_schema!(output)
|
294
|
-
# Validate that output is an instance of the enhanced output struct
|
295
|
-
unless output.is_a?(@enhanced_output_struct)
|
296
|
-
raise "Output must be an instance of #{@enhanced_output_struct}, got #{output.class}"
|
297
|
-
end
|
298
|
-
|
299
|
-
# Validate original signature output fields are present
|
300
|
-
@original_signature_class.output_struct_class.props.each do |field_name, _prop|
|
301
|
-
unless output.respond_to?(field_name)
|
302
|
-
raise "Missing required field: #{field_name}"
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
|
-
# Validate ReAct-specific fields
|
307
|
-
unless output.respond_to?(:history) && output.history.is_a?(Array)
|
308
|
-
raise "Missing or invalid history field"
|
309
|
-
end
|
310
|
-
|
311
|
-
unless output.respond_to?(:iterations) && output.iterations.is_a?(Integer)
|
312
|
-
raise "Missing or invalid iterations field"
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
sig { override.returns(T::Hash[Symbol, T.untyped]) }
|
317
|
-
def generate_example_output
|
318
|
-
example = super
|
319
|
-
example[:history] = [
|
320
|
-
{
|
321
|
-
step: 1,
|
322
|
-
thought: "I need to think about this question...",
|
323
|
-
action: "some_tool",
|
324
|
-
action_input: "input for tool",
|
325
|
-
observation: "result from tool"
|
326
|
-
}
|
327
|
-
]
|
328
|
-
example[:iterations] = 1
|
329
|
-
example
|
330
|
-
end
|
331
|
-
end
|
332
|
-
end
|