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.
@@ -18,8 +18,13 @@ module DSPy
18
18
  module AutoMode
19
19
  extend T::Sig
20
20
 
21
- sig { returns(MIPROv2) }
22
- def self.light
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 { returns(MIPROv2) }
35
- def self.medium
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 { returns(MIPROv2) }
48
- def self.heavy
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, prediction.to_h)
253
+ success = metric.call(example, prediction_hash)
252
254
  else
253
- success = example.matches_prediction?(prediction.to_h)
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, prediction)
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.to_h,
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.19.1"
4
+ VERSION = "0.20.1"
5
5
  end
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.19.1
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-11 00:00:00.000000000 Z
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