dspy 0.20.0 → 0.21.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 +5 -1
- data/lib/dspy/evaluate.rb +1 -1
- data/lib/dspy/lm/strategies/enhanced_prompting_strategy.rb +43 -12
- data/lib/dspy/lm/vision_models.rb +3 -8
- data/lib/dspy/mixins/struct_builder.rb +14 -2
- data/lib/dspy/predict.rb +36 -1
- data/lib/dspy/prediction.rb +59 -10
- data/lib/dspy/signature.rb +94 -3
- data/lib/dspy/teleprompt/mipro_v2.rb +18 -7
- data/lib/dspy/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78e01258a3b9b5a1bccddad913a1d0aa45ecb145a65adb3e691986cadbea6e23
|
4
|
+
data.tar.gz: 9a778ea689150002e0766357dd4aa4af526be81979378b9996ba9067c2cdbd42
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7ba1376b844e5c5e61215b961142a70f82d758db41e061bf2e7404f4ffdbae867197b0c38254a92b25892beb51922fb3c3b963ab2474494c9d490b83814bba5d
|
7
|
+
data.tar.gz: 4a543e0b954469f316f003f36c5a55b97b0794ef1cbf395180ac5197acf5d4091bfe3ea87e342473c190d96adf5761baff59532502a20778edea23a83a9e5253
|
data/README.md
CHANGED
@@ -14,6 +14,10 @@ Traditional prompting is like writing code with string concatenation: it works u
|
|
14
14
|
the programming approach pioneered by [dspy.ai](https://dspy.ai/): instead of crafting fragile prompts, you define modular
|
15
15
|
signatures and let the framework handle the messy details.
|
16
16
|
|
17
|
+
DSPy.rb is an idiomatic Ruby port of Stanford's [DSPy framework](https://github.com/stanfordnlp/dspy). While implementing
|
18
|
+
the core concepts of signatures, predictors, and optimization from the original Python library, DSPy.rb embraces Ruby
|
19
|
+
conventions and adds Ruby-specific innovations like CodeAct agents and enhanced production instrumentation.
|
20
|
+
|
17
21
|
The result? LLM applications that actually scale and don't break when you sneeze.
|
18
22
|
|
19
23
|
## Your First DSPy Program
|
@@ -210,7 +214,7 @@ and ecosystem integration.
|
|
210
214
|
|
211
215
|
### Ecosystem Expansion
|
212
216
|
- 🚧 **Model Context Protocol (MCP)** - Integration with MCP ecosystem
|
213
|
-
- 🚧 **Additional Provider Support** -
|
217
|
+
- 🚧 **Additional Provider Support** - Azure OpenAI, local models beyond Ollama
|
214
218
|
- 🚧 **Tool Ecosystem** - Expanded tool integrations for ReAct agents
|
215
219
|
|
216
220
|
### Community & Adoption
|
data/lib/dspy/evaluate.rb
CHANGED
@@ -114,24 +114,55 @@ module DSPy
|
|
114
114
|
|
115
115
|
example = {}
|
116
116
|
schema[:properties].each do |field_name, field_schema|
|
117
|
-
example[field_name.to_s] =
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
117
|
+
example[field_name.to_s] = generate_example_value(field_schema)
|
118
|
+
end
|
119
|
+
example
|
120
|
+
end
|
121
|
+
|
122
|
+
sig { params(field_schema: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
123
|
+
def generate_example_value(field_schema)
|
124
|
+
case field_schema[:type]
|
125
|
+
when "string"
|
126
|
+
field_schema[:description] || "example string"
|
127
|
+
when "integer"
|
128
|
+
42
|
129
|
+
when "number"
|
130
|
+
3.14
|
131
|
+
when "boolean"
|
132
|
+
true
|
133
|
+
when "array"
|
134
|
+
if field_schema[:items]
|
135
|
+
[generate_example_value(field_schema[:items])]
|
136
|
+
else
|
127
137
|
["example item"]
|
128
|
-
|
138
|
+
end
|
139
|
+
when "object"
|
140
|
+
if field_schema[:properties]
|
141
|
+
# Generate proper nested object example
|
142
|
+
nested_example = {}
|
143
|
+
field_schema[:properties].each do |prop_name, prop_schema|
|
144
|
+
nested_example[prop_name.to_s] = generate_example_value(prop_schema)
|
145
|
+
end
|
146
|
+
nested_example
|
147
|
+
else
|
129
148
|
{ "nested" => "object" }
|
149
|
+
end
|
150
|
+
when Array
|
151
|
+
# Handle union types like ["object", "null"]
|
152
|
+
if field_schema[:type].include?("object") && field_schema[:properties]
|
153
|
+
nested_example = {}
|
154
|
+
field_schema[:properties].each do |prop_name, prop_schema|
|
155
|
+
nested_example[prop_name.to_s] = generate_example_value(prop_schema)
|
156
|
+
end
|
157
|
+
nested_example
|
158
|
+
elsif field_schema[:type].include?("string")
|
159
|
+
"example string"
|
130
160
|
else
|
131
161
|
"example value"
|
132
162
|
end
|
163
|
+
else
|
164
|
+
"example value"
|
133
165
|
end
|
134
|
-
example
|
135
166
|
end
|
136
167
|
|
137
168
|
sig { params(content: String).returns(T::Boolean) }
|
@@ -27,6 +27,7 @@ module DSPy
|
|
27
27
|
].freeze
|
28
28
|
|
29
29
|
# Gemini vision-capable models (all Gemini models support vision)
|
30
|
+
# Based on official Google AI API documentation (March 2025)
|
30
31
|
GEMINI_VISION_MODELS = [
|
31
32
|
# Gemini 2.5 series (2025)
|
32
33
|
'gemini-2.5-pro',
|
@@ -34,17 +35,11 @@ module DSPy
|
|
34
35
|
'gemini-2.5-flash-lite',
|
35
36
|
# Gemini 2.0 series (2024-2025)
|
36
37
|
'gemini-2.0-flash',
|
37
|
-
'gemini-2.0-flash-
|
38
|
-
'gemini-2.0-flash-lite',
|
39
|
-
'gemini-2.0-pro-experimental',
|
38
|
+
'gemini-2.0-flash-lite',
|
40
39
|
# Gemini 1.5 series
|
41
40
|
'gemini-1.5-pro',
|
42
41
|
'gemini-1.5-flash',
|
43
|
-
'gemini-1.5-
|
44
|
-
'gemini-1.5-flash-latest',
|
45
|
-
# Legacy models
|
46
|
-
'gemini-pro-vision',
|
47
|
-
'gemini-1.0-pro-vision'
|
42
|
+
'gemini-1.5-flash-8b'
|
48
43
|
].freeze
|
49
44
|
|
50
45
|
def self.supports_vision?(provider, model)
|
@@ -80,7 +80,8 @@ module DSPy
|
|
80
80
|
def extract_type_from_prop(prop)
|
81
81
|
case prop
|
82
82
|
when Hash
|
83
|
-
|
83
|
+
# Prefer type_object for nilable types, fallback to type
|
84
|
+
prop[:type_object] || prop[:type]
|
84
85
|
when Array
|
85
86
|
# Handle [Type, description] format
|
86
87
|
prop.first
|
@@ -94,7 +95,18 @@ module DSPy
|
|
94
95
|
def extract_options_from_prop(prop)
|
95
96
|
case prop
|
96
97
|
when Hash
|
97
|
-
|
98
|
+
# Preserve important flags like fully_optional for nilable types
|
99
|
+
extracted = prop.except(:type, :type_object, :accessor_key, :sensitivity, :redaction, :setter_proc, :value_validate_proc, :serialized_form, :need_nil_read_check, :immutable, :pii, :extra)
|
100
|
+
|
101
|
+
# Handle default values properly
|
102
|
+
if prop[:default]
|
103
|
+
extracted[:default] = prop[:default]
|
104
|
+
elsif prop[:fully_optional]
|
105
|
+
# For fully optional fields (nilable), set default to nil
|
106
|
+
extracted[:default] = nil
|
107
|
+
end
|
108
|
+
|
109
|
+
extracted
|
98
110
|
else
|
99
111
|
{}
|
100
112
|
end
|
data/lib/dspy/predict.rb
CHANGED
@@ -195,7 +195,11 @@ module DSPy
|
|
195
195
|
begin
|
196
196
|
combined_struct = create_combined_struct_class
|
197
197
|
all_attributes = input_values.merge(output_attributes)
|
198
|
-
|
198
|
+
|
199
|
+
# Preprocess nilable attributes before struct instantiation
|
200
|
+
processed_attributes = preprocess_nilable_attributes(all_attributes, combined_struct)
|
201
|
+
|
202
|
+
combined_struct.new(**processed_attributes)
|
199
203
|
rescue ArgumentError => e
|
200
204
|
raise PredictionInvalidError.new({ output: e.message })
|
201
205
|
rescue TypeError => e
|
@@ -231,5 +235,36 @@ module DSPy
|
|
231
235
|
|
232
236
|
output_attributes
|
233
237
|
end
|
238
|
+
|
239
|
+
# Preprocesses attributes to handle nilable fields properly before struct instantiation
|
240
|
+
sig { params(attributes: T::Hash[Symbol, T.untyped], struct_class: T.class_of(T::Struct)).returns(T::Hash[Symbol, T.untyped]) }
|
241
|
+
def preprocess_nilable_attributes(attributes, struct_class)
|
242
|
+
processed = attributes.dup
|
243
|
+
struct_props = struct_class.props
|
244
|
+
|
245
|
+
# Process each attribute based on its type in the struct
|
246
|
+
processed.each do |key, value|
|
247
|
+
prop_info = struct_props[key]
|
248
|
+
next unless prop_info
|
249
|
+
|
250
|
+
prop_type = prop_info[:type_object] || prop_info[:type]
|
251
|
+
next unless prop_type
|
252
|
+
|
253
|
+
# For nilable fields with nil values, ensure proper handling
|
254
|
+
if value.nil? && is_nilable_type?(prop_type)
|
255
|
+
# For nilable fields, nil is valid - keep it as is
|
256
|
+
next
|
257
|
+
elsif value.nil? && prop_info[:fully_optional]
|
258
|
+
# For fully optional fields, nil is valid - keep it as is
|
259
|
+
next
|
260
|
+
elsif value.nil? && prop_info[:default]
|
261
|
+
# Use default value if available
|
262
|
+
default_value = prop_info[:default]
|
263
|
+
processed[key] = default_value.is_a?(Proc) ? default_value.call : default_value
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
processed
|
268
|
+
end
|
234
269
|
end
|
235
270
|
end
|
data/lib/dspy/prediction.rb
CHANGED
@@ -123,7 +123,8 @@ module DSPy
|
|
123
123
|
end
|
124
124
|
elsif is_enum_type?(prop_type) && value.is_a?(String)
|
125
125
|
# Convert string to enum
|
126
|
-
|
126
|
+
enum_class = extract_enum_class(prop_type)
|
127
|
+
converted[key] = enum_class.deserialize(value)
|
127
128
|
elsif value.is_a?(Hash) && needs_struct_conversion?(prop_type)
|
128
129
|
# Regular struct field that needs conversion
|
129
130
|
converted[key] = convert_to_struct(value, prop_type)
|
@@ -188,18 +189,61 @@ module DSPy
|
|
188
189
|
sig { params(type: T.untyped).returns(T::Boolean) }
|
189
190
|
def is_enum_type?(type)
|
190
191
|
return false if type.nil?
|
191
|
-
return false unless type.is_a?(T::Types::Simple)
|
192
192
|
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
193
|
+
case type
|
194
|
+
when T::Types::Simple
|
195
|
+
# Handle regular enum types
|
196
|
+
begin
|
197
|
+
raw_type = type.raw_type
|
198
|
+
return false unless raw_type.is_a?(Class)
|
199
|
+
result = raw_type < T::Enum
|
200
|
+
return result == true # Force conversion to boolean
|
201
|
+
rescue StandardError
|
202
|
+
return false
|
203
|
+
end
|
204
|
+
when T::Private::Types::SimplePairUnion, T::Types::Union
|
205
|
+
# Handle T.nilable enum types
|
206
|
+
# Find the non-nil type and check if it's an enum
|
207
|
+
non_nil_types = if type.respond_to?(:types)
|
208
|
+
type.types.reject { |t| t.respond_to?(:raw_type) && t.raw_type == NilClass }
|
209
|
+
else
|
210
|
+
[]
|
211
|
+
end
|
212
|
+
|
213
|
+
# For nilable types, we expect exactly one non-nil type
|
214
|
+
return false unless non_nil_types.size == 1
|
215
|
+
|
216
|
+
non_nil_type = non_nil_types.first
|
217
|
+
return is_enum_type?(non_nil_type) # Recursively check
|
218
|
+
else
|
199
219
|
return false
|
200
220
|
end
|
201
221
|
end
|
202
222
|
|
223
|
+
sig { params(type: T.untyped).returns(T.untyped) }
|
224
|
+
def extract_enum_class(type)
|
225
|
+
case type
|
226
|
+
when T::Types::Simple
|
227
|
+
# Regular enum type
|
228
|
+
type.raw_type
|
229
|
+
when T::Private::Types::SimplePairUnion, T::Types::Union
|
230
|
+
# Nilable enum type - find the non-nil type
|
231
|
+
non_nil_types = if type.respond_to?(:types)
|
232
|
+
type.types.reject { |t| t.respond_to?(:raw_type) && t.raw_type == NilClass }
|
233
|
+
else
|
234
|
+
[]
|
235
|
+
end
|
236
|
+
|
237
|
+
if non_nil_types.size == 1
|
238
|
+
extract_enum_class(non_nil_types.first)
|
239
|
+
else
|
240
|
+
raise ArgumentError, "Unable to extract enum class from complex union type: #{type.inspect}"
|
241
|
+
end
|
242
|
+
else
|
243
|
+
raise ArgumentError, "Not an enum type: #{type.inspect}"
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
203
247
|
sig { params(union_type: T::Types::Union, discriminator_type: T.untyped).returns(T::Hash[String, T.untyped]) }
|
204
248
|
def build_type_mapping_from_union(union_type, discriminator_type)
|
205
249
|
mapping = {}
|
@@ -303,7 +347,12 @@ module DSPy
|
|
303
347
|
def needs_struct_conversion?(type)
|
304
348
|
case type
|
305
349
|
when T::Types::Simple
|
306
|
-
|
350
|
+
# Use !! to convert nil result of < comparison to false
|
351
|
+
begin
|
352
|
+
!!(type.raw_type < T::Struct)
|
353
|
+
rescue
|
354
|
+
false
|
355
|
+
end
|
307
356
|
when T::Types::Union
|
308
357
|
# Check if any type in the union is a struct
|
309
358
|
type.types.any? { |t| needs_struct_conversion?(t) }
|
@@ -352,7 +401,7 @@ module DSPy
|
|
352
401
|
end
|
353
402
|
begin
|
354
403
|
struct_class.new(**converted_hash)
|
355
|
-
rescue
|
404
|
+
rescue
|
356
405
|
# Return original value if conversion fails
|
357
406
|
value
|
358
407
|
end
|
data/lib/dspy/signature.rb
CHANGED
@@ -188,6 +188,11 @@ module DSPy
|
|
188
188
|
return { type: "boolean" }
|
189
189
|
end
|
190
190
|
|
191
|
+
# Handle type aliases by resolving to their underlying type
|
192
|
+
if type.is_a?(T::Private::Types::TypeAlias)
|
193
|
+
return type_to_json_schema(type.aliased_type)
|
194
|
+
end
|
195
|
+
|
191
196
|
# Handle raw class types first
|
192
197
|
if type.is_a?(Class)
|
193
198
|
if type < T::Enum
|
@@ -257,17 +262,103 @@ module DSPy
|
|
257
262
|
# Add a more explicit description of the expected structure
|
258
263
|
description: "A mapping where keys are #{key_schema[:type]}s and values are #{value_schema[:description] || value_schema[:type]}s"
|
259
264
|
}
|
265
|
+
elsif type.is_a?(T::Types::FixedHash)
|
266
|
+
# Handle fixed hashes (from type aliases like { "key" => Type })
|
267
|
+
properties = {}
|
268
|
+
required = []
|
269
|
+
|
270
|
+
type.types.each do |key, value_type|
|
271
|
+
properties[key] = type_to_json_schema(value_type)
|
272
|
+
required << key
|
273
|
+
end
|
274
|
+
|
275
|
+
{
|
276
|
+
type: "object",
|
277
|
+
properties: properties,
|
278
|
+
required: required,
|
279
|
+
additionalProperties: false
|
280
|
+
}
|
281
|
+
elsif type.class.name == "T::Private::Types::SimplePairUnion"
|
282
|
+
# Handle T.nilable types (T::Private::Types::SimplePairUnion)
|
283
|
+
# This is the actual implementation of T.nilable(SomeType)
|
284
|
+
has_nil = type.respond_to?(:types) && type.types.any? do |t|
|
285
|
+
(t.respond_to?(:raw_type) && t.raw_type == NilClass) ||
|
286
|
+
(t.respond_to?(:name) && t.name == "NilClass")
|
287
|
+
end
|
288
|
+
|
289
|
+
if has_nil
|
290
|
+
# Find the non-nil type
|
291
|
+
non_nil_type = type.types.find do |t|
|
292
|
+
!(t.respond_to?(:raw_type) && t.raw_type == NilClass) &&
|
293
|
+
!(t.respond_to?(:name) && t.name == "NilClass")
|
294
|
+
end
|
295
|
+
|
296
|
+
if non_nil_type
|
297
|
+
base_schema = type_to_json_schema(non_nil_type)
|
298
|
+
if base_schema[:type].is_a?(String)
|
299
|
+
# Convert single type to array with null
|
300
|
+
{ type: [base_schema[:type], "null"] }.merge(base_schema.except(:type))
|
301
|
+
else
|
302
|
+
# For complex schemas, use anyOf to allow null
|
303
|
+
{ anyOf: [base_schema, { type: "null" }] }
|
304
|
+
end
|
305
|
+
else
|
306
|
+
{ type: "string" } # Fallback
|
307
|
+
end
|
308
|
+
else
|
309
|
+
# Not nilable SimplePairUnion - this is a regular T.any() union
|
310
|
+
# Generate oneOf schema for all types
|
311
|
+
if type.respond_to?(:types) && type.types.length > 1
|
312
|
+
{
|
313
|
+
oneOf: type.types.map { |t| type_to_json_schema(t) },
|
314
|
+
description: "Union of multiple types"
|
315
|
+
}
|
316
|
+
else
|
317
|
+
# Single type or fallback
|
318
|
+
first_type = type.respond_to?(:types) ? type.types.first : type
|
319
|
+
type_to_json_schema(first_type)
|
320
|
+
end
|
321
|
+
end
|
260
322
|
elsif type.is_a?(T::Types::Union)
|
261
|
-
#
|
323
|
+
# Check if this is a nilable type (contains NilClass)
|
324
|
+
is_nilable = type.types.any? { |t| t == T::Utils.coerce(NilClass) }
|
262
325
|
non_nil_types = type.types.reject { |t| t == T::Utils.coerce(NilClass) }
|
263
|
-
|
326
|
+
|
327
|
+
# Special case: check if we have TrueClass + FalseClass (T.nilable(T::Boolean))
|
328
|
+
if non_nil_types.size == 2 && is_nilable
|
329
|
+
true_class_type = non_nil_types.find { |t| t.respond_to?(:raw_type) && t.raw_type == TrueClass }
|
330
|
+
false_class_type = non_nil_types.find { |t| t.respond_to?(:raw_type) && t.raw_type == FalseClass }
|
331
|
+
|
332
|
+
if true_class_type && false_class_type
|
333
|
+
# This is T.nilable(T::Boolean) - treat as nilable boolean
|
334
|
+
return { type: ["boolean", "null"] }
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
if non_nil_types.size == 1 && is_nilable
|
339
|
+
# This is T.nilable(SomeType) - generate proper schema with null allowed
|
340
|
+
base_schema = type_to_json_schema(non_nil_types.first)
|
341
|
+
if base_schema[:type].is_a?(String)
|
342
|
+
# Convert single type to array with null
|
343
|
+
{ type: [base_schema[:type], "null"] }.merge(base_schema.except(:type))
|
344
|
+
else
|
345
|
+
# For complex schemas, use anyOf to allow null
|
346
|
+
{ anyOf: [base_schema, { type: "null" }] }
|
347
|
+
end
|
348
|
+
elsif non_nil_types.size == 1
|
349
|
+
# Non-nilable single type union (shouldn't happen in practice)
|
264
350
|
type_to_json_schema(non_nil_types.first)
|
265
351
|
elsif non_nil_types.size > 1
|
266
352
|
# Handle complex unions with oneOf for better JSON schema compliance
|
267
|
-
{
|
353
|
+
base_schema = {
|
268
354
|
oneOf: non_nil_types.map { |t| type_to_json_schema(t) },
|
269
355
|
description: "Union of multiple types"
|
270
356
|
}
|
357
|
+
if is_nilable
|
358
|
+
# Add null as an option for complex nilable unions
|
359
|
+
base_schema[:oneOf] << { type: "null" }
|
360
|
+
end
|
361
|
+
base_schema
|
271
362
|
else
|
272
363
|
{ type: "string" } # Fallback for complex unions
|
273
364
|
end
|
@@ -203,6 +203,9 @@ module DSPy
|
|
203
203
|
sig { returns(T::Hash[Symbol, T.untyped]) }
|
204
204
|
attr_reader :proposal_statistics
|
205
205
|
|
206
|
+
sig { returns(T.nilable(DSPy::Evaluate::BatchEvaluationResult)) }
|
207
|
+
attr_reader :best_evaluation_result
|
208
|
+
|
206
209
|
sig do
|
207
210
|
params(
|
208
211
|
optimized_program: T.untyped,
|
@@ -214,10 +217,11 @@ module DSPy
|
|
214
217
|
proposal_statistics: T::Hash[Symbol, T.untyped],
|
215
218
|
best_score_name: T.nilable(String),
|
216
219
|
best_score_value: T.nilable(Float),
|
217
|
-
metadata: T::Hash[Symbol, T.untyped]
|
220
|
+
metadata: T::Hash[Symbol, T.untyped],
|
221
|
+
best_evaluation_result: T.nilable(DSPy::Evaluate::BatchEvaluationResult)
|
218
222
|
).void
|
219
223
|
end
|
220
|
-
def initialize(optimized_program:, scores:, history:, evaluated_candidates:, optimization_trace:, bootstrap_statistics:, proposal_statistics:, best_score_name: nil, best_score_value: nil, metadata: {})
|
224
|
+
def initialize(optimized_program:, scores:, history:, evaluated_candidates:, optimization_trace:, bootstrap_statistics:, proposal_statistics:, best_score_name: nil, best_score_value: nil, metadata: {}, best_evaluation_result: nil)
|
221
225
|
super(
|
222
226
|
optimized_program: optimized_program,
|
223
227
|
scores: scores,
|
@@ -230,6 +234,7 @@ module DSPy
|
|
230
234
|
@optimization_trace = optimization_trace.freeze
|
231
235
|
@bootstrap_statistics = bootstrap_statistics.freeze
|
232
236
|
@proposal_statistics = proposal_statistics.freeze
|
237
|
+
@best_evaluation_result = best_evaluation_result&.freeze
|
233
238
|
end
|
234
239
|
|
235
240
|
sig { returns(T::Hash[Symbol, T.untyped]) }
|
@@ -238,7 +243,8 @@ module DSPy
|
|
238
243
|
evaluated_candidates: @evaluated_candidates.map(&:to_h),
|
239
244
|
optimization_trace: @optimization_trace,
|
240
245
|
bootstrap_statistics: @bootstrap_statistics,
|
241
|
-
proposal_statistics: @proposal_statistics
|
246
|
+
proposal_statistics: @proposal_statistics,
|
247
|
+
best_evaluation_result: @best_evaluation_result&.to_h
|
242
248
|
})
|
243
249
|
end
|
244
250
|
end
|
@@ -399,6 +405,7 @@ module DSPy
|
|
399
405
|
best_score = 0.0
|
400
406
|
best_candidate = nil
|
401
407
|
best_program = nil
|
408
|
+
best_evaluation_result = nil
|
402
409
|
|
403
410
|
@mipro_config.num_trials.times do |trial_idx|
|
404
411
|
trials_completed = trial_idx + 1
|
@@ -415,7 +422,7 @@ module DSPy
|
|
415
422
|
|
416
423
|
begin
|
417
424
|
# Evaluate candidate
|
418
|
-
score, modified_program = evaluate_candidate(program, candidate, evaluation_set)
|
425
|
+
score, modified_program, evaluation_result = evaluate_candidate(program, candidate, evaluation_set)
|
419
426
|
|
420
427
|
# Update optimization state
|
421
428
|
update_optimization_state(optimization_state, candidate, score)
|
@@ -426,6 +433,7 @@ module DSPy
|
|
426
433
|
best_score = score
|
427
434
|
best_candidate = candidate
|
428
435
|
best_program = modified_program
|
436
|
+
best_evaluation_result = evaluation_result
|
429
437
|
end
|
430
438
|
|
431
439
|
emit_event('trial_complete', {
|
@@ -456,6 +464,7 @@ module DSPy
|
|
456
464
|
best_score: best_score,
|
457
465
|
best_candidate: best_candidate,
|
458
466
|
best_program: best_program,
|
467
|
+
best_evaluation_result: best_evaluation_result,
|
459
468
|
trials_completed: trials_completed,
|
460
469
|
optimization_state: optimization_state,
|
461
470
|
evaluated_candidates: @evaluated_candidates
|
@@ -626,7 +635,7 @@ module DSPy
|
|
626
635
|
program: T.untyped,
|
627
636
|
candidate: CandidateConfig,
|
628
637
|
evaluation_set: T::Array[DSPy::Example]
|
629
|
-
).returns([Float, T.untyped])
|
638
|
+
).returns([Float, T.untyped, DSPy::Evaluate::BatchEvaluationResult])
|
630
639
|
end
|
631
640
|
def evaluate_candidate(program, candidate, evaluation_set)
|
632
641
|
# Apply candidate configuration to program
|
@@ -638,7 +647,7 @@ module DSPy
|
|
638
647
|
# Store evaluation details
|
639
648
|
@evaluated_candidates << candidate
|
640
649
|
|
641
|
-
[evaluation_result.pass_rate, modified_program]
|
650
|
+
[evaluation_result.pass_rate, modified_program, evaluation_result]
|
642
651
|
end
|
643
652
|
|
644
653
|
# Apply candidate configuration to program
|
@@ -724,6 +733,7 @@ module DSPy
|
|
724
733
|
best_candidate = optimization_result[:best_candidate]
|
725
734
|
best_program = optimization_result[:best_program]
|
726
735
|
best_score = optimization_result[:best_score]
|
736
|
+
best_evaluation_result = optimization_result[:best_evaluation_result]
|
727
737
|
|
728
738
|
scores = { pass_rate: best_score }
|
729
739
|
|
@@ -753,7 +763,8 @@ module DSPy
|
|
753
763
|
evaluated_candidates: @evaluated_candidates,
|
754
764
|
optimization_trace: serialize_optimization_trace(optimization_result[:optimization_state]),
|
755
765
|
bootstrap_statistics: bootstrap_result.statistics,
|
756
|
-
proposal_statistics: proposal_result.analysis
|
766
|
+
proposal_statistics: proposal_result.analysis,
|
767
|
+
best_evaluation_result: best_evaluation_result
|
757
768
|
)
|
758
769
|
end
|
759
770
|
|
data/lib/dspy/version.rb
CHANGED
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dspy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.21.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vicente Reig Rincón de Arellano
|
8
8
|
bindir: bin
|
9
9
|
cert_chain: []
|
10
|
-
date: 2025-
|
10
|
+
date: 2025-09-01 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dry-configurable
|