dspy 0.30.1 → 0.31.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/README.md +51 -64
- data/lib/dspy/evals.rb +21 -2
- data/lib/dspy/lm/adapter_factory.rb +40 -17
- data/lib/dspy/lm/errors.rb +3 -0
- data/lib/dspy/lm/json_strategy.rb +24 -8
- data/lib/dspy/lm.rb +62 -19
- data/lib/dspy/mixins/type_coercion.rb +2 -0
- data/lib/dspy/module.rb +6 -6
- data/lib/dspy/prompt.rb +207 -36
- data/lib/dspy/re_act.rb +50 -17
- data/lib/dspy/schema/sorbet_json_schema.rb +5 -2
- data/lib/dspy/schema/sorbet_toon_adapter.rb +81 -0
- data/lib/dspy/structured_outputs_prompt.rb +5 -3
- data/lib/dspy/type_serializer.rb +2 -1
- data/lib/dspy/version.rb +1 -1
- metadata +14 -51
- data/lib/dspy/lm/adapters/anthropic_adapter.rb +0 -291
- data/lib/dspy/lm/adapters/gemini/schema_converter.rb +0 -186
- data/lib/dspy/lm/adapters/gemini_adapter.rb +0 -220
- data/lib/dspy/lm/adapters/ollama_adapter.rb +0 -73
- data/lib/dspy/lm/adapters/openai/schema_converter.rb +0 -359
- data/lib/dspy/lm/adapters/openai_adapter.rb +0 -188
- data/lib/dspy/lm/adapters/openrouter_adapter.rb +0 -68
data/lib/dspy/prompt.rb
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
3
4
|
require 'sorbet-runtime'
|
|
5
|
+
require 'sorbet/toon'
|
|
6
|
+
|
|
4
7
|
require_relative 'few_shot_example'
|
|
8
|
+
require_relative 'schema/sorbet_toon_adapter'
|
|
5
9
|
|
|
6
10
|
module DSPy
|
|
7
11
|
class Prompt
|
|
@@ -33,6 +37,15 @@ module DSPy
|
|
|
33
37
|
DSPy.config.lm&.schema_format || @schema_format || :json
|
|
34
38
|
end
|
|
35
39
|
|
|
40
|
+
sig { returns(Symbol) }
|
|
41
|
+
def data_format
|
|
42
|
+
return @data_format if @data_format && @data_format != :json
|
|
43
|
+
|
|
44
|
+
lm = DSPy.config.lm
|
|
45
|
+
lm_format = lm&.respond_to?(:data_format) ? lm.data_format : nil
|
|
46
|
+
lm_format || @data_format || :json
|
|
47
|
+
end
|
|
48
|
+
|
|
36
49
|
sig { returns(T.nilable(T.class_of(Signature))) }
|
|
37
50
|
attr_reader :signature_class
|
|
38
51
|
|
|
@@ -44,10 +57,11 @@ module DSPy
|
|
|
44
57
|
few_shot_examples: T::Array[FewShotExample],
|
|
45
58
|
signature_class_name: T.nilable(String),
|
|
46
59
|
schema_format: Symbol,
|
|
47
|
-
signature_class: T.nilable(T.class_of(Signature))
|
|
60
|
+
signature_class: T.nilable(T.class_of(Signature)),
|
|
61
|
+
data_format: Symbol
|
|
48
62
|
).void
|
|
49
63
|
end
|
|
50
|
-
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil)
|
|
64
|
+
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil, data_format: :json)
|
|
51
65
|
@instruction = instruction
|
|
52
66
|
@few_shot_examples = few_shot_examples.freeze
|
|
53
67
|
@input_schema = input_schema.freeze
|
|
@@ -55,6 +69,7 @@ module DSPy
|
|
|
55
69
|
@signature_class_name = signature_class_name
|
|
56
70
|
@schema_format = schema_format
|
|
57
71
|
@signature_class = signature_class
|
|
72
|
+
@data_format = data_format
|
|
58
73
|
end
|
|
59
74
|
|
|
60
75
|
# Immutable update methods for optimization
|
|
@@ -67,7 +82,8 @@ module DSPy
|
|
|
67
82
|
few_shot_examples: @few_shot_examples,
|
|
68
83
|
signature_class_name: @signature_class_name,
|
|
69
84
|
schema_format: @schema_format,
|
|
70
|
-
signature_class: @signature_class
|
|
85
|
+
signature_class: @signature_class,
|
|
86
|
+
data_format: @data_format
|
|
71
87
|
)
|
|
72
88
|
end
|
|
73
89
|
|
|
@@ -80,7 +96,8 @@ module DSPy
|
|
|
80
96
|
few_shot_examples: new_examples,
|
|
81
97
|
signature_class_name: @signature_class_name,
|
|
82
98
|
schema_format: @schema_format,
|
|
83
|
-
signature_class: @signature_class
|
|
99
|
+
signature_class: @signature_class,
|
|
100
|
+
data_format: @data_format
|
|
84
101
|
)
|
|
85
102
|
end
|
|
86
103
|
|
|
@@ -106,7 +123,13 @@ module DSPy
|
|
|
106
123
|
sections << "```baml"
|
|
107
124
|
sections << render_baml_schema(@output_schema, :output)
|
|
108
125
|
sections << "```"
|
|
109
|
-
|
|
126
|
+
when :toon
|
|
127
|
+
sections << "Your input schema fields (TOON order) are:"
|
|
128
|
+
sections << Sorbet::Toon::SignatureFormatter.describe_signature(@signature_class, :input)
|
|
129
|
+
sections << ""
|
|
130
|
+
sections << "Your output schema fields (TOON order) are:"
|
|
131
|
+
sections << Sorbet::Toon::SignatureFormatter.describe_signature(@signature_class, :output)
|
|
132
|
+
else
|
|
110
133
|
sections << "Your input schema fields are:"
|
|
111
134
|
sections << "```json"
|
|
112
135
|
sections << JSON.pretty_generate(@input_schema)
|
|
@@ -117,11 +140,10 @@ module DSPy
|
|
|
117
140
|
sections << JSON.pretty_generate(@output_schema)
|
|
118
141
|
sections << "```"
|
|
119
142
|
end
|
|
120
|
-
|
|
143
|
+
|
|
121
144
|
sections << ""
|
|
122
145
|
sections << "All interactions will be structured in the following way, with the appropriate values filled in."
|
|
123
|
-
|
|
124
|
-
# Add few-shot examples if present
|
|
146
|
+
|
|
125
147
|
if @few_shot_examples.any?
|
|
126
148
|
sections << ""
|
|
127
149
|
sections << "Here are some examples:"
|
|
@@ -133,36 +155,81 @@ module DSPy
|
|
|
133
155
|
end
|
|
134
156
|
end
|
|
135
157
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
if toon_data_format_enabled?
|
|
159
|
+
sections << "## TOON data format instructions"
|
|
160
|
+
sections << "All input and output payloads must use Token-Oriented Object Notation (TOON). Do not return JSON, YAML, or prose."
|
|
161
|
+
sections << ""
|
|
162
|
+
sections << "## Input values"
|
|
163
|
+
sections << "Copy the TOON block below and replace the placeholder values with the correct inputs."
|
|
164
|
+
sections << "```toon"
|
|
165
|
+
sections << "{input_values}"
|
|
166
|
+
sections << "```"
|
|
167
|
+
|
|
168
|
+
if (example_input = example_toon_payload(:input))
|
|
169
|
+
sections << ""
|
|
170
|
+
sections << "### Example TOON input"
|
|
171
|
+
sections << "```toon"
|
|
172
|
+
sections << example_input
|
|
173
|
+
sections << "```"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
sections << ""
|
|
177
|
+
sections << "## Output values"
|
|
178
|
+
sections << "Respond exclusively with a ```toon``` block that lists the output fields in the exact order shown in the schema."
|
|
179
|
+
sections << "```toon"
|
|
180
|
+
sections << "{output_values}"
|
|
181
|
+
sections << "```"
|
|
182
|
+
|
|
183
|
+
if (example_output = example_toon_payload(:output))
|
|
184
|
+
sections << ""
|
|
185
|
+
sections << "### Example TOON output"
|
|
186
|
+
sections << "```toon"
|
|
187
|
+
sections << example_output
|
|
188
|
+
sections << "```"
|
|
189
|
+
end
|
|
190
|
+
else
|
|
191
|
+
sections << "## Input values"
|
|
192
|
+
sections << "```json"
|
|
193
|
+
sections << "{input_values}"
|
|
194
|
+
sections << "```"
|
|
195
|
+
|
|
196
|
+
sections << "## Output values"
|
|
197
|
+
sections << "Respond exclusively with the output schema fields in the json block below."
|
|
198
|
+
sections << "```json"
|
|
199
|
+
sections << "{output_values}"
|
|
200
|
+
sections << "```"
|
|
201
|
+
end
|
|
202
|
+
|
|
147
203
|
sections << ""
|
|
148
204
|
sections << "In adhering to this structure, your objective is: #{@instruction}"
|
|
149
|
-
|
|
205
|
+
|
|
150
206
|
sections.join("\n")
|
|
151
207
|
end
|
|
152
208
|
|
|
153
209
|
sig { params(input_values: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
154
210
|
def render_user_prompt(input_values)
|
|
155
211
|
sections = []
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
212
|
+
|
|
213
|
+
if toon_data_format_enabled?
|
|
214
|
+
toon_payload = DSPy::Schema::SorbetToonAdapter.render_input(@signature_class, input_values)
|
|
215
|
+
|
|
216
|
+
sections << "## Input Values"
|
|
217
|
+
sections << "Use the TOON block below as-is; do not convert it to JSON."
|
|
218
|
+
sections << "```toon"
|
|
219
|
+
sections << toon_payload
|
|
220
|
+
sections << "```"
|
|
221
|
+
sections << ""
|
|
222
|
+
sections << "Respond with the corresponding output schema fields encoded as TOON inside a ```toon``` block starting with the heading `## Output values`. Do not include any JSON."
|
|
223
|
+
else
|
|
224
|
+
sections << "## Input Values"
|
|
225
|
+
sections << "```json"
|
|
226
|
+
sections << JSON.pretty_generate(serialize_for_json(input_values))
|
|
227
|
+
sections << "```"
|
|
228
|
+
sections << ""
|
|
229
|
+
sections << "Respond with the corresponding output schema fields wrapped in a ```json ``` block,"
|
|
230
|
+
sections << "starting with the heading `## Output values`."
|
|
231
|
+
end
|
|
232
|
+
|
|
166
233
|
sections.join("\n")
|
|
167
234
|
end
|
|
168
235
|
|
|
@@ -184,7 +251,8 @@ module DSPy
|
|
|
184
251
|
input_schema: @input_schema,
|
|
185
252
|
output_schema: @output_schema,
|
|
186
253
|
signature_class_name: @signature_class_name,
|
|
187
|
-
schema_format: @schema_format
|
|
254
|
+
schema_format: @schema_format,
|
|
255
|
+
data_format: @data_format
|
|
188
256
|
}
|
|
189
257
|
end
|
|
190
258
|
|
|
@@ -198,13 +266,24 @@ module DSPy
|
|
|
198
266
|
output_schema: hash[:output_schema] || {},
|
|
199
267
|
few_shot_examples: examples,
|
|
200
268
|
signature_class_name: hash[:signature_class_name],
|
|
201
|
-
schema_format: hash[:schema_format] || :json
|
|
269
|
+
schema_format: hash[:schema_format] || :json,
|
|
270
|
+
data_format: hash[:data_format] || :json
|
|
202
271
|
)
|
|
203
272
|
end
|
|
204
273
|
|
|
205
274
|
# Create prompt from signature class
|
|
206
|
-
sig
|
|
207
|
-
|
|
275
|
+
sig do
|
|
276
|
+
params(
|
|
277
|
+
signature_class: T.class_of(Signature),
|
|
278
|
+
schema_format: T.nilable(Symbol),
|
|
279
|
+
data_format: T.nilable(Symbol)
|
|
280
|
+
).returns(Prompt)
|
|
281
|
+
end
|
|
282
|
+
def self.from_signature(signature_class, schema_format: nil, data_format: nil)
|
|
283
|
+
lm = DSPy.config.lm
|
|
284
|
+
schema_format ||= lm&.schema_format || :json
|
|
285
|
+
data_format ||= (lm&.respond_to?(:data_format) ? lm.data_format : nil) || :json
|
|
286
|
+
|
|
208
287
|
new(
|
|
209
288
|
instruction: signature_class.description || "Complete this task.",
|
|
210
289
|
input_schema: signature_class.input_json_schema,
|
|
@@ -212,7 +291,8 @@ module DSPy
|
|
|
212
291
|
few_shot_examples: [],
|
|
213
292
|
signature_class_name: signature_class.name,
|
|
214
293
|
schema_format: schema_format,
|
|
215
|
-
signature_class: signature_class
|
|
294
|
+
signature_class: signature_class,
|
|
295
|
+
data_format: data_format
|
|
216
296
|
)
|
|
217
297
|
end
|
|
218
298
|
|
|
@@ -335,5 +415,96 @@ module DSPy
|
|
|
335
415
|
|
|
336
416
|
result
|
|
337
417
|
end
|
|
418
|
+
|
|
419
|
+
def toon_data_format_enabled?
|
|
420
|
+
data_format == :toon && @signature_class
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
SAMPLE_DEPTH_LIMIT = 3
|
|
424
|
+
private_constant :SAMPLE_DEPTH_LIMIT
|
|
425
|
+
|
|
426
|
+
def example_toon_payload(role)
|
|
427
|
+
return nil unless toon_data_format_enabled?
|
|
428
|
+
|
|
429
|
+
sample_values = case role
|
|
430
|
+
when :input
|
|
431
|
+
sample_struct_values(@signature_class.input_struct_class)
|
|
432
|
+
when :output
|
|
433
|
+
sample_struct_values(@signature_class.output_struct_class)
|
|
434
|
+
else
|
|
435
|
+
{}
|
|
436
|
+
end
|
|
437
|
+
|
|
438
|
+
return nil if sample_values.empty?
|
|
439
|
+
|
|
440
|
+
case role
|
|
441
|
+
when :input
|
|
442
|
+
DSPy::Schema::SorbetToonAdapter.render_input(@signature_class, sample_values)
|
|
443
|
+
when :output
|
|
444
|
+
DSPy::Schema::SorbetToonAdapter.render_expected_output(@signature_class, sample_values)
|
|
445
|
+
end
|
|
446
|
+
rescue StandardError
|
|
447
|
+
nil
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
def sample_struct_values(struct_class, depth = 0)
|
|
451
|
+
return {} unless struct_class&.respond_to?(:props)
|
|
452
|
+
struct_class.props.each_with_object({}) do |(name, prop_info), memo|
|
|
453
|
+
memo[name] = sample_value_for_type(prop_info[:type], name, depth)
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
def sample_value_for_type(prop_type, field_name, depth)
|
|
458
|
+
return sample_string(field_name) if prop_type.nil? || depth > SAMPLE_DEPTH_LIMIT
|
|
459
|
+
|
|
460
|
+
case prop_type
|
|
461
|
+
when T::Types::Simple
|
|
462
|
+
sample_value_for_type(prop_type.raw_type, field_name, depth + 1)
|
|
463
|
+
when T::Types::Union
|
|
464
|
+
preferred = prop_type.types.find { |type| !nil_type?(type) } || prop_type.types.first
|
|
465
|
+
sample_value_for_type(preferred, field_name, depth + 1)
|
|
466
|
+
when T::Types::TypedArray
|
|
467
|
+
[sample_value_for_type(prop_type.type, field_name, depth + 1)]
|
|
468
|
+
when T::Types::TypedHash
|
|
469
|
+
key_sample = sample_value_for_type(prop_type.keys, "#{field_name}_key", depth + 1)
|
|
470
|
+
value_sample = sample_value_for_type(prop_type.values, "#{field_name}_value", depth + 1)
|
|
471
|
+
{ key_sample.to_s => value_sample }
|
|
472
|
+
when Class
|
|
473
|
+
sample_for_class_type(prop_type, field_name, depth)
|
|
474
|
+
else
|
|
475
|
+
sample_string(field_name)
|
|
476
|
+
end
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
def sample_for_class_type(prop_type, field_name, depth)
|
|
480
|
+
if prop_type <= String
|
|
481
|
+
sample_string(field_name)
|
|
482
|
+
elsif prop_type <= Integer
|
|
483
|
+
1
|
|
484
|
+
elsif prop_type <= Float
|
|
485
|
+
1.0
|
|
486
|
+
elsif prop_type <= Numeric
|
|
487
|
+
1
|
|
488
|
+
elsif prop_type <= TrueClass || prop_type <= FalseClass
|
|
489
|
+
true
|
|
490
|
+
elsif prop_type <= T::Enum
|
|
491
|
+
enum_value = prop_type.values.first
|
|
492
|
+
enum_value ? enum_value.serialize : sample_string(field_name)
|
|
493
|
+
elsif prop_type <= T::Struct
|
|
494
|
+
sample_struct_values(prop_type, depth + 1)
|
|
495
|
+
else
|
|
496
|
+
sample_string(field_name)
|
|
497
|
+
end
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
def nil_type?(type)
|
|
501
|
+
(type.respond_to?(:raw_type) && type.raw_type == NilClass) || type == NilClass
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def sample_string(field_name)
|
|
505
|
+
base = field_name.to_s.gsub(/[^a-z0-9]+/i, '_').gsub(/_{2,}/, '_').sub(/^_+|_+$/, '')
|
|
506
|
+
base = 'value' if base.empty?
|
|
507
|
+
"example_#{base}"
|
|
508
|
+
end
|
|
338
509
|
end
|
|
339
|
-
end
|
|
510
|
+
end
|
data/lib/dspy/re_act.rb
CHANGED
|
@@ -98,13 +98,14 @@ module DSPy
|
|
|
98
98
|
@tools = T.let({}, T::Hash[String, T.untyped])
|
|
99
99
|
tools.each { |tool| @tools[tool.name.downcase] = tool }
|
|
100
100
|
@max_iterations = max_iterations
|
|
101
|
+
@data_format = T.let(DSPy.config.lm&.data_format || :json, Symbol)
|
|
101
102
|
|
|
102
103
|
# Create dynamic ActionEnum class with tool names + finish
|
|
103
104
|
@action_enum_class = create_action_enum_class
|
|
104
105
|
|
|
105
106
|
# Create dynamic signature classes that include the original input fields
|
|
106
|
-
thought_signature = create_thought_signature(signature_class)
|
|
107
|
-
observation_signature = create_observation_signature(signature_class)
|
|
107
|
+
thought_signature = create_thought_signature(signature_class, @data_format)
|
|
108
|
+
observation_signature = create_observation_signature(signature_class, @data_format)
|
|
108
109
|
|
|
109
110
|
# Create thought generator using Predict to preserve field descriptions
|
|
110
111
|
@thought_generator = T.let(DSPy::Predict.new(thought_signature), DSPy::Predict)
|
|
@@ -216,6 +217,28 @@ module DSPy
|
|
|
216
217
|
end
|
|
217
218
|
end
|
|
218
219
|
|
|
220
|
+
sig { params(input_struct: T.untyped).returns(T.untyped) }
|
|
221
|
+
def format_input_context(input_struct)
|
|
222
|
+
return input_struct if toon_data_format?
|
|
223
|
+
|
|
224
|
+
DSPy::TypeSerializer.serialize(input_struct).to_json
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
sig { params(history: T::Array[HistoryEntry]).returns(T.untyped) }
|
|
228
|
+
def format_history(history)
|
|
229
|
+
toon_data_format? ? history : serialize_history_for_llm(history)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
sig { params(observation: T.untyped).returns(T.untyped) }
|
|
233
|
+
def format_observation(observation)
|
|
234
|
+
toon_data_format? ? observation : serialize_for_llm(observation)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
sig { returns(T::Boolean) }
|
|
238
|
+
def toon_data_format?
|
|
239
|
+
@data_format == :toon
|
|
240
|
+
end
|
|
241
|
+
|
|
219
242
|
# Creates a dynamic ActionEnum class with tool names and "finish"
|
|
220
243
|
sig { returns(T.class_of(T::Enum)) }
|
|
221
244
|
def create_action_enum_class
|
|
@@ -241,9 +264,14 @@ module DSPy
|
|
|
241
264
|
end
|
|
242
265
|
|
|
243
266
|
# Creates a dynamic Thought signature that includes the original input fields
|
|
244
|
-
sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T.class_of(DSPy::Signature)) }
|
|
245
|
-
def create_thought_signature(signature_class)
|
|
267
|
+
sig { params(signature_class: T.class_of(DSPy::Signature), data_format: Symbol).returns(T.class_of(DSPy::Signature)) }
|
|
268
|
+
def create_thought_signature(signature_class, data_format)
|
|
246
269
|
action_enum_class = @action_enum_class
|
|
270
|
+
input_context_type = if data_format == :toon
|
|
271
|
+
signature_class.input_struct_class || String
|
|
272
|
+
else
|
|
273
|
+
String
|
|
274
|
+
end
|
|
247
275
|
# Create new class that inherits from DSPy::Signature
|
|
248
276
|
Class.new(DSPy::Signature) do
|
|
249
277
|
# Set description
|
|
@@ -251,8 +279,8 @@ module DSPy
|
|
|
251
279
|
|
|
252
280
|
# Define input fields
|
|
253
281
|
input do
|
|
254
|
-
const :input_context,
|
|
255
|
-
description: "Serialized representation of all input fields"
|
|
282
|
+
const :input_context, input_context_type,
|
|
283
|
+
description: data_format == :toon ? "All original input fields with their typed values" : "Serialized representation of all input fields"
|
|
256
284
|
const :history, T::Array[HistoryEntry],
|
|
257
285
|
description: "Previous thoughts and actions, including observations from tools."
|
|
258
286
|
const :available_tools, T::Array[AvailableTool],
|
|
@@ -272,8 +300,13 @@ module DSPy
|
|
|
272
300
|
end
|
|
273
301
|
|
|
274
302
|
# Creates a dynamic observation signature that includes the original input fields
|
|
275
|
-
sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T.class_of(DSPy::Signature)) }
|
|
276
|
-
def create_observation_signature(signature_class)
|
|
303
|
+
sig { params(signature_class: T.class_of(DSPy::Signature), data_format: Symbol).returns(T.class_of(DSPy::Signature)) }
|
|
304
|
+
def create_observation_signature(signature_class, data_format)
|
|
305
|
+
input_context_type = if data_format == :toon
|
|
306
|
+
signature_class.input_struct_class || String
|
|
307
|
+
else
|
|
308
|
+
String
|
|
309
|
+
end
|
|
277
310
|
# Create new class that inherits from DSPy::Signature
|
|
278
311
|
Class.new(DSPy::Signature) do
|
|
279
312
|
# Set description
|
|
@@ -281,8 +314,8 @@ module DSPy
|
|
|
281
314
|
|
|
282
315
|
# Define input fields
|
|
283
316
|
input do
|
|
284
|
-
const :input_context,
|
|
285
|
-
description: "Serialized representation of all input fields"
|
|
317
|
+
const :input_context, input_context_type,
|
|
318
|
+
description: data_format == :toon ? "All original input fields with their typed values" : "Serialized representation of all input fields"
|
|
286
319
|
const :history, T::Array[HistoryEntry],
|
|
287
320
|
description: "Previous thoughts, actions, and observations."
|
|
288
321
|
const :observation, T.untyped,
|
|
@@ -358,8 +391,8 @@ module DSPy
|
|
|
358
391
|
) do
|
|
359
392
|
# Generate thought and action
|
|
360
393
|
thought_obj = @thought_generator.forward(
|
|
361
|
-
input_context:
|
|
362
|
-
history:
|
|
394
|
+
input_context: format_input_context(input_struct),
|
|
395
|
+
history: format_history(history),
|
|
363
396
|
available_tools: available_tools_desc
|
|
364
397
|
)
|
|
365
398
|
|
|
@@ -617,9 +650,9 @@ module DSPy
|
|
|
617
650
|
sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], observation: T.untyped, available_tools_desc: T::Array[AvailableTool], iteration: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
618
651
|
def process_observation_and_decide_next_step(input_struct, history, observation, available_tools_desc, iteration)
|
|
619
652
|
observation_result = @observation_processor.forward(
|
|
620
|
-
input_context:
|
|
621
|
-
history:
|
|
622
|
-
observation:
|
|
653
|
+
input_context: format_input_context(input_struct),
|
|
654
|
+
history: format_history(history),
|
|
655
|
+
observation: format_observation(observation)
|
|
623
656
|
)
|
|
624
657
|
|
|
625
658
|
return { should_finish: false } unless observation_result.next_step == NextStep::Finish
|
|
@@ -634,8 +667,8 @@ module DSPy
|
|
|
634
667
|
sig { params(input_struct: T.untyped, history: T::Array[HistoryEntry], available_tools_desc: T::Array[AvailableTool], observation_result: T.untyped, iteration: Integer).returns(T.untyped) }
|
|
635
668
|
def generate_forced_final_answer(input_struct, history, available_tools_desc, observation_result, iteration)
|
|
636
669
|
final_thought = @thought_generator.forward(
|
|
637
|
-
input_context:
|
|
638
|
-
history:
|
|
670
|
+
input_context: format_input_context(input_struct),
|
|
671
|
+
history: format_history(history),
|
|
639
672
|
available_tools: available_tools_desc
|
|
640
673
|
)
|
|
641
674
|
|
|
@@ -254,10 +254,13 @@ module DSPy
|
|
|
254
254
|
"DSPy uses _type for automatic type detection in union types."
|
|
255
255
|
end
|
|
256
256
|
|
|
257
|
+
struct_name = struct_class.name || "Struct#{format('%x', struct_class.object_id)}"
|
|
258
|
+
simple_name = struct_name.split('::').last || struct_name
|
|
259
|
+
|
|
257
260
|
# Add automatic _type field for type detection
|
|
258
261
|
properties[:_type] = {
|
|
259
262
|
type: "string",
|
|
260
|
-
const:
|
|
263
|
+
const: simple_name # Use the simple class name
|
|
261
264
|
}
|
|
262
265
|
required << "_type"
|
|
263
266
|
|
|
@@ -280,7 +283,7 @@ module DSPy
|
|
|
280
283
|
type: "object",
|
|
281
284
|
properties: properties,
|
|
282
285
|
required: required,
|
|
283
|
-
description: "#{
|
|
286
|
+
description: "#{struct_name} struct"
|
|
284
287
|
}
|
|
285
288
|
end
|
|
286
289
|
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'sorbet-runtime'
|
|
4
|
+
require 'sorbet/toon'
|
|
5
|
+
|
|
6
|
+
require_relative '../lm/errors'
|
|
7
|
+
|
|
8
|
+
module DSPy
|
|
9
|
+
module Schema
|
|
10
|
+
module SorbetToonAdapter
|
|
11
|
+
extend T::Sig
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
sig { params(signature_class: T.nilable(T.class_of(DSPy::Signature)), values: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
16
|
+
def render_input(signature_class, values)
|
|
17
|
+
Sorbet::Toon.encode(
|
|
18
|
+
values,
|
|
19
|
+
signature: signature_class,
|
|
20
|
+
role: :input
|
|
21
|
+
)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
sig { params(signature_class: T.nilable(T.class_of(DSPy::Signature)), values: T::Hash[Symbol, T.untyped]).returns(String) }
|
|
25
|
+
def render_expected_output(signature_class, values)
|
|
26
|
+
Sorbet::Toon.encode(
|
|
27
|
+
values,
|
|
28
|
+
signature: signature_class,
|
|
29
|
+
role: :output
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
sig { params(signature_class: T.nilable(T.class_of(DSPy::Signature)), toon_string: String).returns(T.untyped) }
|
|
34
|
+
def parse_output(signature_class, toon_string)
|
|
35
|
+
payload = strip_code_fences(toon_string)
|
|
36
|
+
|
|
37
|
+
Sorbet::Toon.decode(
|
|
38
|
+
payload,
|
|
39
|
+
signature: signature_class,
|
|
40
|
+
role: :output,
|
|
41
|
+
strict: false
|
|
42
|
+
)
|
|
43
|
+
rescue Sorbet::Toon::DecodeError => e
|
|
44
|
+
log_decode_error(payload, e)
|
|
45
|
+
raise DSPy::LM::AdapterError,
|
|
46
|
+
"Failed to parse TOON response: #{e.message}. Ensure the model replies with a ```toon``` block using the schema described in the system prompt."
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
sig { params(text: T.nilable(String)).returns(String) }
|
|
50
|
+
def strip_code_fences(text)
|
|
51
|
+
return '' if text.nil?
|
|
52
|
+
|
|
53
|
+
match = text.match(/```(?:toon)?\s*(.*?)```/m)
|
|
54
|
+
return match[1].strip if match
|
|
55
|
+
|
|
56
|
+
text.strip
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
sig { params(payload: String, error: StandardError).void }
|
|
60
|
+
def log_decode_error(payload, error)
|
|
61
|
+
logger = DSPy.logger if DSPy.respond_to?(:logger)
|
|
62
|
+
return unless logger.respond_to?(:warn)
|
|
63
|
+
|
|
64
|
+
preview = payload.to_s.lines.first(5).join
|
|
65
|
+
logger.warn(
|
|
66
|
+
event: 'toon.decode_error',
|
|
67
|
+
error: error.message,
|
|
68
|
+
preview: preview,
|
|
69
|
+
length: payload.to_s.length
|
|
70
|
+
)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
sig { params(signature_class: T.nilable(T.class_of(DSPy::Signature)), role: Symbol).returns(String) }
|
|
74
|
+
def field_guidance(signature_class, role)
|
|
75
|
+
return '' unless signature_class
|
|
76
|
+
|
|
77
|
+
Sorbet::Toon::SignatureFormatter.describe_signature(signature_class, role)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -17,10 +17,11 @@ module DSPy
|
|
|
17
17
|
few_shot_examples: T::Array[T.untyped],
|
|
18
18
|
signature_class_name: T.nilable(String),
|
|
19
19
|
schema_format: Symbol,
|
|
20
|
-
signature_class: T.nilable(T.class_of(Signature))
|
|
20
|
+
signature_class: T.nilable(T.class_of(Signature)),
|
|
21
|
+
data_format: Symbol
|
|
21
22
|
).void
|
|
22
23
|
end
|
|
23
|
-
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil)
|
|
24
|
+
def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil, data_format: :json)
|
|
24
25
|
normalized_examples = few_shot_examples.map do |example|
|
|
25
26
|
case example
|
|
26
27
|
when FewShotExample
|
|
@@ -39,7 +40,8 @@ module DSPy
|
|
|
39
40
|
few_shot_examples: normalized_examples,
|
|
40
41
|
signature_class_name: signature_class_name,
|
|
41
42
|
schema_format: schema_format,
|
|
42
|
-
signature_class: signature_class
|
|
43
|
+
signature_class: signature_class,
|
|
44
|
+
data_format: data_format
|
|
43
45
|
)
|
|
44
46
|
end
|
|
45
47
|
|
data/lib/dspy/type_serializer.rb
CHANGED
data/lib/dspy/version.rb
CHANGED