dspy 0.19.1 → 0.20.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 +93 -69
- data/lib/dspy/chain_of_thought.rb +1 -0
- data/lib/dspy/code_act.rb +9 -3
- data/lib/dspy/evaluate.rb +1 -1
- data/lib/dspy/image.rb +33 -0
- data/lib/dspy/lm/adapter_factory.rb +2 -1
- data/lib/dspy/lm/adapters/gemini_adapter.rb +189 -0
- data/lib/dspy/lm/response.rb +40 -4
- data/lib/dspy/lm/usage.rb +19 -0
- data/lib/dspy/lm/vision_models.rb +20 -0
- data/lib/dspy/lm.rb +5 -4
- data/lib/dspy/mixins/struct_builder.rb +14 -2
- data/lib/dspy/module.rb +2 -2
- data/lib/dspy/predict.rb +72 -1
- data/lib/dspy/prediction.rb +59 -10
- data/lib/dspy/propose/grounded_proposer.rb +38 -3
- data/lib/dspy/re_act.rb +9 -4
- data/lib/dspy/signature.rb +73 -3
- data/lib/dspy/storage/program_storage.rb +45 -10
- data/lib/dspy/teleprompt/mipro_v2.rb +58 -17
- data/lib/dspy/teleprompt/utils.rb +30 -5
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +14 -0
- metadata +17 -2
@@ -18,8 +18,13 @@ module DSPy
|
|
18
18
|
module AutoMode
|
19
19
|
extend T::Sig
|
20
20
|
|
21
|
-
sig
|
22
|
-
|
21
|
+
sig do
|
22
|
+
params(
|
23
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)),
|
24
|
+
kwargs: T.untyped
|
25
|
+
).returns(MIPROv2)
|
26
|
+
end
|
27
|
+
def self.light(metric: nil, **kwargs)
|
23
28
|
config = MIPROv2Config.new
|
24
29
|
config.num_trials = 6
|
25
30
|
config.num_instruction_candidates = 3
|
@@ -28,11 +33,16 @@ module DSPy
|
|
28
33
|
config.bootstrap_sets = 3
|
29
34
|
config.optimization_strategy = "greedy"
|
30
35
|
config.early_stopping_patience = 2
|
31
|
-
MIPROv2.new(config: config)
|
36
|
+
MIPROv2.new(metric: metric, config: config, **kwargs)
|
32
37
|
end
|
33
38
|
|
34
|
-
sig
|
35
|
-
|
39
|
+
sig do
|
40
|
+
params(
|
41
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)),
|
42
|
+
kwargs: T.untyped
|
43
|
+
).returns(MIPROv2)
|
44
|
+
end
|
45
|
+
def self.medium(metric: nil, **kwargs)
|
36
46
|
config = MIPROv2Config.new
|
37
47
|
config.num_trials = 12
|
38
48
|
config.num_instruction_candidates = 5
|
@@ -41,11 +51,16 @@ module DSPy
|
|
41
51
|
config.bootstrap_sets = 5
|
42
52
|
config.optimization_strategy = "adaptive"
|
43
53
|
config.early_stopping_patience = 3
|
44
|
-
MIPROv2.new(config: config)
|
54
|
+
MIPROv2.new(metric: metric, config: config, **kwargs)
|
45
55
|
end
|
46
56
|
|
47
|
-
sig
|
48
|
-
|
57
|
+
sig do
|
58
|
+
params(
|
59
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T.untyped)),
|
60
|
+
kwargs: T.untyped
|
61
|
+
).returns(MIPROv2)
|
62
|
+
end
|
63
|
+
def self.heavy(metric: nil, **kwargs)
|
49
64
|
config = MIPROv2Config.new
|
50
65
|
config.num_trials = 18
|
51
66
|
config.num_instruction_candidates = 8
|
@@ -54,7 +69,7 @@ module DSPy
|
|
54
69
|
config.bootstrap_sets = 8
|
55
70
|
config.optimization_strategy = "bayesian"
|
56
71
|
config.early_stopping_patience = 5
|
57
|
-
MIPROv2.new(config: config)
|
72
|
+
MIPROv2.new(metric: metric, config: config, **kwargs)
|
58
73
|
end
|
59
74
|
end
|
60
75
|
|
@@ -188,6 +203,9 @@ module DSPy
|
|
188
203
|
sig { returns(T::Hash[Symbol, T.untyped]) }
|
189
204
|
attr_reader :proposal_statistics
|
190
205
|
|
206
|
+
sig { returns(T.nilable(DSPy::Evaluate::BatchEvaluationResult)) }
|
207
|
+
attr_reader :best_evaluation_result
|
208
|
+
|
191
209
|
sig do
|
192
210
|
params(
|
193
211
|
optimized_program: T.untyped,
|
@@ -199,10 +217,11 @@ module DSPy
|
|
199
217
|
proposal_statistics: T::Hash[Symbol, T.untyped],
|
200
218
|
best_score_name: T.nilable(String),
|
201
219
|
best_score_value: T.nilable(Float),
|
202
|
-
metadata: T::Hash[Symbol, T.untyped]
|
220
|
+
metadata: T::Hash[Symbol, T.untyped],
|
221
|
+
best_evaluation_result: T.nilable(DSPy::Evaluate::BatchEvaluationResult)
|
203
222
|
).void
|
204
223
|
end
|
205
|
-
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)
|
206
225
|
super(
|
207
226
|
optimized_program: optimized_program,
|
208
227
|
scores: scores,
|
@@ -215,6 +234,7 @@ module DSPy
|
|
215
234
|
@optimization_trace = optimization_trace.freeze
|
216
235
|
@bootstrap_statistics = bootstrap_statistics.freeze
|
217
236
|
@proposal_statistics = proposal_statistics.freeze
|
237
|
+
@best_evaluation_result = best_evaluation_result&.freeze
|
218
238
|
end
|
219
239
|
|
220
240
|
sig { returns(T::Hash[Symbol, T.untyped]) }
|
@@ -223,7 +243,8 @@ module DSPy
|
|
223
243
|
evaluated_candidates: @evaluated_candidates.map(&:to_h),
|
224
244
|
optimization_trace: @optimization_trace,
|
225
245
|
bootstrap_statistics: @bootstrap_statistics,
|
226
|
-
proposal_statistics: @proposal_statistics
|
246
|
+
proposal_statistics: @proposal_statistics,
|
247
|
+
best_evaluation_result: @best_evaluation_result&.to_h
|
227
248
|
})
|
228
249
|
end
|
229
250
|
end
|
@@ -384,6 +405,7 @@ module DSPy
|
|
384
405
|
best_score = 0.0
|
385
406
|
best_candidate = nil
|
386
407
|
best_program = nil
|
408
|
+
best_evaluation_result = nil
|
387
409
|
|
388
410
|
@mipro_config.num_trials.times do |trial_idx|
|
389
411
|
trials_completed = trial_idx + 1
|
@@ -400,7 +422,7 @@ module DSPy
|
|
400
422
|
|
401
423
|
begin
|
402
424
|
# Evaluate candidate
|
403
|
-
score, modified_program = evaluate_candidate(program, candidate, evaluation_set)
|
425
|
+
score, modified_program, evaluation_result = evaluate_candidate(program, candidate, evaluation_set)
|
404
426
|
|
405
427
|
# Update optimization state
|
406
428
|
update_optimization_state(optimization_state, candidate, score)
|
@@ -411,6 +433,7 @@ module DSPy
|
|
411
433
|
best_score = score
|
412
434
|
best_candidate = candidate
|
413
435
|
best_program = modified_program
|
436
|
+
best_evaluation_result = evaluation_result
|
414
437
|
end
|
415
438
|
|
416
439
|
emit_event('trial_complete', {
|
@@ -441,6 +464,7 @@ module DSPy
|
|
441
464
|
best_score: best_score,
|
442
465
|
best_candidate: best_candidate,
|
443
466
|
best_program: best_program,
|
467
|
+
best_evaluation_result: best_evaluation_result,
|
444
468
|
trials_completed: trials_completed,
|
445
469
|
optimization_state: optimization_state,
|
446
470
|
evaluated_candidates: @evaluated_candidates
|
@@ -611,7 +635,7 @@ module DSPy
|
|
611
635
|
program: T.untyped,
|
612
636
|
candidate: CandidateConfig,
|
613
637
|
evaluation_set: T::Array[DSPy::Example]
|
614
|
-
).returns([Float, T.untyped])
|
638
|
+
).returns([Float, T.untyped, DSPy::Evaluate::BatchEvaluationResult])
|
615
639
|
end
|
616
640
|
def evaluate_candidate(program, candidate, evaluation_set)
|
617
641
|
# Apply candidate configuration to program
|
@@ -623,7 +647,7 @@ module DSPy
|
|
623
647
|
# Store evaluation details
|
624
648
|
@evaluated_candidates << candidate
|
625
649
|
|
626
|
-
[evaluation_result.pass_rate, modified_program]
|
650
|
+
[evaluation_result.pass_rate, modified_program, evaluation_result]
|
627
651
|
end
|
628
652
|
|
629
653
|
# Apply candidate configuration to program
|
@@ -709,6 +733,7 @@ module DSPy
|
|
709
733
|
best_candidate = optimization_result[:best_candidate]
|
710
734
|
best_program = optimization_result[:best_program]
|
711
735
|
best_score = optimization_result[:best_score]
|
736
|
+
best_evaluation_result = optimization_result[:best_evaluation_result]
|
712
737
|
|
713
738
|
scores = { pass_rate: best_score }
|
714
739
|
|
@@ -736,12 +761,28 @@ module DSPy
|
|
736
761
|
best_score_value: best_score,
|
737
762
|
metadata: metadata,
|
738
763
|
evaluated_candidates: @evaluated_candidates,
|
739
|
-
optimization_trace: optimization_result[:optimization_state]
|
764
|
+
optimization_trace: serialize_optimization_trace(optimization_result[:optimization_state]),
|
740
765
|
bootstrap_statistics: bootstrap_result.statistics,
|
741
|
-
proposal_statistics: proposal_result.analysis
|
766
|
+
proposal_statistics: proposal_result.analysis,
|
767
|
+
best_evaluation_result: best_evaluation_result
|
742
768
|
)
|
743
769
|
end
|
744
770
|
|
771
|
+
# Serialize optimization trace for better JSON output
|
772
|
+
sig { params(optimization_state: T.nilable(T::Hash[Symbol, T.untyped])).returns(T::Hash[Symbol, T.untyped]) }
|
773
|
+
def serialize_optimization_trace(optimization_state)
|
774
|
+
return {} unless optimization_state
|
775
|
+
|
776
|
+
serialized_trace = optimization_state.dup
|
777
|
+
|
778
|
+
# Convert candidate objects to their hash representations
|
779
|
+
if serialized_trace[:candidates]
|
780
|
+
serialized_trace[:candidates] = serialized_trace[:candidates].map(&:to_h)
|
781
|
+
end
|
782
|
+
|
783
|
+
serialized_trace
|
784
|
+
end
|
785
|
+
|
745
786
|
# Helper methods
|
746
787
|
sig { params(program: T.untyped).returns(T.nilable(String)) }
|
747
788
|
def extract_current_instruction(program)
|
@@ -247,15 +247,17 @@ module DSPy
|
|
247
247
|
prediction = program.call(**example.input_values)
|
248
248
|
|
249
249
|
# Check if prediction matches expected output
|
250
|
+
prediction_hash = extract_output_fields_from_prediction(prediction, example.signature_class)
|
251
|
+
|
250
252
|
if metric
|
251
|
-
success = metric.call(example,
|
253
|
+
success = metric.call(example, prediction_hash)
|
252
254
|
else
|
253
|
-
success = example.matches_prediction?(
|
255
|
+
success = example.matches_prediction?(prediction_hash)
|
254
256
|
end
|
255
257
|
|
256
258
|
if success
|
257
259
|
# Create a new example with the successful prediction as reasoning/context
|
258
|
-
successful_example = create_successful_bootstrap_example(example,
|
260
|
+
successful_example = create_successful_bootstrap_example(example, prediction_hash)
|
259
261
|
successful << successful_example
|
260
262
|
|
261
263
|
emit_bootstrap_example_event(index, true, nil)
|
@@ -311,7 +313,7 @@ module DSPy
|
|
311
313
|
sig do
|
312
314
|
params(
|
313
315
|
original_example: DSPy::Example,
|
314
|
-
prediction: T.untyped
|
316
|
+
prediction: T::Hash[Symbol, T.untyped]
|
315
317
|
).returns(DSPy::Example)
|
316
318
|
end
|
317
319
|
def self.create_successful_bootstrap_example(original_example, prediction)
|
@@ -319,7 +321,7 @@ module DSPy
|
|
319
321
|
DSPy::Example.new(
|
320
322
|
signature_class: original_example.signature_class,
|
321
323
|
input: original_example.input_values,
|
322
|
-
expected: prediction
|
324
|
+
expected: prediction,
|
323
325
|
id: "bootstrap_#{original_example.id || SecureRandom.uuid}",
|
324
326
|
metadata: {
|
325
327
|
source: "bootstrap",
|
@@ -329,6 +331,29 @@ module DSPy
|
|
329
331
|
)
|
330
332
|
end
|
331
333
|
|
334
|
+
# Extract only output fields from prediction (exclude input fields)
|
335
|
+
sig do
|
336
|
+
params(
|
337
|
+
prediction: T.untyped,
|
338
|
+
signature_class: T.class_of(DSPy::Signature)
|
339
|
+
).returns(T::Hash[Symbol, T.untyped])
|
340
|
+
end
|
341
|
+
def self.extract_output_fields_from_prediction(prediction, signature_class)
|
342
|
+
prediction_hash = prediction.to_h
|
343
|
+
|
344
|
+
# Get output field names from signature
|
345
|
+
output_fields = signature_class.output_field_descriptors.keys
|
346
|
+
|
347
|
+
# Filter prediction to only include output fields
|
348
|
+
filtered_expected = {}
|
349
|
+
output_fields.each do |field_name|
|
350
|
+
if prediction_hash.key?(field_name)
|
351
|
+
filtered_expected[field_name] = prediction_hash[field_name]
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
filtered_expected
|
356
|
+
end
|
332
357
|
|
333
358
|
# Create default metric for examples
|
334
359
|
sig { params(examples: T::Array[T.untyped]).returns(T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))) }
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.rb
CHANGED
@@ -74,6 +74,20 @@ module DSPy
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
|
77
|
+
# Fiber-local LM context for temporary model overrides
|
78
|
+
FIBER_LM_KEY = :dspy_fiber_lm
|
79
|
+
|
80
|
+
def self.current_lm
|
81
|
+
Fiber[FIBER_LM_KEY] || config.lm
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.with_lm(lm)
|
85
|
+
previous_lm = Fiber[FIBER_LM_KEY]
|
86
|
+
Fiber[FIBER_LM_KEY] = lm
|
87
|
+
yield
|
88
|
+
ensure
|
89
|
+
Fiber[FIBER_LM_KEY] = previous_lm
|
90
|
+
end
|
77
91
|
end
|
78
92
|
|
79
93
|
require_relative 'dspy/module'
|
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.20.1
|
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-08-
|
10
|
+
date: 2025-08-27 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dry-configurable
|
@@ -79,6 +79,20 @@ dependencies:
|
|
79
79
|
- - "~>"
|
80
80
|
- !ruby/object:Gem::Version
|
81
81
|
version: 1.5.0
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: gemini-ai
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '4.3'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '4.3'
|
82
96
|
- !ruby/object:Gem::Dependency
|
83
97
|
name: sorbet-runtime
|
84
98
|
requirement: !ruby/object:Gem::Requirement
|
@@ -186,6 +200,7 @@ files:
|
|
186
200
|
- lib/dspy/lm/adapter.rb
|
187
201
|
- lib/dspy/lm/adapter_factory.rb
|
188
202
|
- lib/dspy/lm/adapters/anthropic_adapter.rb
|
203
|
+
- lib/dspy/lm/adapters/gemini_adapter.rb
|
189
204
|
- lib/dspy/lm/adapters/ollama_adapter.rb
|
190
205
|
- lib/dspy/lm/adapters/openai/schema_converter.rb
|
191
206
|
- lib/dspy/lm/adapters/openai_adapter.rb
|