dspy 0.25.0 → 0.26.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d45d1ee46193e0bad5339bb7a05732797fa4c2dcc7fafcbaef0bee1aa7ecd591
4
- data.tar.gz: 5ca7952a3119f1177ff034c55819e266a443d6fa48c975af15195f937560c10e
3
+ metadata.gz: 573bfc89c0ca4d6c58a3d68fa96e0b0e8fc1d8bc2c38eec9c67e245a51527532
4
+ data.tar.gz: a2b34e8127abafbdec6842c3db475e1abbfbcf06c0ca9bfcd3fa7ea8299d0d0f
5
5
  SHA512:
6
- metadata.gz: 0e29265f2bf13028265591281536baa229cab475dabaf0318045d40d4568eb4329b23b07c6f8b70c83588fb53e5059cb854ab8e68b5716e78f5368adad7a02bb
7
- data.tar.gz: 714a2864f33cf2f22e27e9962368c277f8f684b22e751fb25fc03c14db3e42c06e95ec035fe99c7fba5295f98440895831ab3db6c45fd8859a450f2a4fbe6b7d
6
+ metadata.gz: 88d9b7c29cf89d386ada79f8696f561b94087458715813b45ce2be18947163e840deabfe681d0f7594023fd09c84c1ff3746c87d19c2ebc8043103f5f98cbdc7
7
+ data.tar.gz: 5e5d518266d7dafe64abf3f3d6b083c6017feb2564743053c23bf9b6baa5a32be53eab299a57814b70ce05d236e3f5d659158679914ac097fd7fcc589b4ddb04
data/README.md CHANGED
@@ -73,7 +73,7 @@ puts result.confidence # => 0.85
73
73
  - **Prompt Objects** - Manipulate prompts as first-class objects instead of strings
74
74
  - **Typed Examples** - Type-safe training data with automatic validation
75
75
  - **Evaluation Framework** - Advanced metrics beyond simple accuracy with error-resilient pipelines
76
- - **MIPROv2 Optimization** - Automatic prompt optimization with storage and persistence
76
+ - **MIPROv2 Optimization** - Advanced Bayesian optimization with Gaussian Processes, multiple optimization strategies, and storage persistence
77
77
  - **GEPA Optimization** - Genetic-Pareto optimization for multi-objective prompt improvement
78
78
 
79
79
  **Production Features:**
@@ -128,7 +128,7 @@ For LLMs and AI assistants working with DSPy.rb:
128
128
  ### Optimization
129
129
  - **[Evaluation Framework](docs/src/optimization/evaluation.md)** - Advanced metrics beyond simple accuracy
130
130
  - **[Prompt Optimization](docs/src/optimization/prompt-optimization.md)** - Manipulate prompts as objects
131
- - **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Automatic optimization algorithms
131
+ - **[MIPROv2 Optimizer](docs/src/optimization/miprov2.md)** - Advanced Bayesian optimization with Gaussian Processes
132
132
  - **[GEPA Optimizer](docs/src/optimization/gepa.md)** - Genetic-Pareto optimization for multi-objective prompt optimization
133
133
 
134
134
  ### Production Features
@@ -159,7 +159,7 @@ bundle install
159
159
 
160
160
  #### System Dependencies for Ubuntu/Pop!_OS
161
161
 
162
- If you need to compile the `polars-df` dependency from source (used for data processing in evaluations), install these system packages:
162
+ If you need to compile the `numo-narray` dependency from source (used for numerical computing in Bayesian optimization), install these system packages:
163
163
 
164
164
  ```bash
165
165
  # Update package list
@@ -171,15 +171,14 @@ sudo apt-get install ruby-full ruby-dev
171
171
  # Install essential build tools
172
172
  sudo apt-get install build-essential
173
173
 
174
- # Install Rust and Cargo (required for polars-df compilation)
175
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
176
- source $HOME/.cargo/env
174
+ # Install BLAS and LAPACK libraries (required for numo-narray)
175
+ sudo apt-get install libopenblas-dev liblapack-dev
177
176
 
178
- # Install CMake (often needed for Rust projects)
179
- sudo apt-get install cmake
177
+ # Install additional development libraries
178
+ sudo apt-get install libffi-dev libssl-dev
180
179
  ```
181
180
 
182
- **Note**: The `polars-df` gem compilation can take 15-20 minutes. Pre-built binaries are available for most platforms, so compilation is only needed if a pre-built binary isn't available for your system.
181
+ **Note**: The `numo-narray` gem typically compiles quickly (1-2 minutes). Pre-built binaries are available for most platforms, so compilation is only needed if a pre-built binary isn't available for your system.
183
182
 
184
183
  ## Recent Achievements
185
184
 
@@ -190,7 +189,7 @@ DSPy.rb has rapidly evolved from experimental to production-ready:
190
189
  - ✅ **Type-Safe Strategy Configuration** - Provider-optimized automatic strategy selection
191
190
  - ✅ **Core Module System** - Predict, ChainOfThought, ReAct, CodeAct with type safety
192
191
  - ✅ **Production Observability** - OpenTelemetry, New Relic, and Langfuse integration
193
- - ✅ **Optimization Framework** - MIPROv2 algorithm with storage & persistence
192
+ - ✅ **Advanced Optimization** - MIPROv2 with Bayesian optimization, Gaussian Processes, and multiple strategies
194
193
 
195
194
  ### Recent Advances
196
195
  - ✅ **Enhanced Langfuse Integration (v0.25.0)** - Comprehensive OpenTelemetry span reporting with proper input/output, hierarchical nesting, accurate timing, and observation types
@@ -14,7 +14,7 @@ module DSPy
14
14
  class AsyncSpanProcessor
15
15
  # Default configuration values
16
16
  DEFAULT_QUEUE_SIZE = 1000
17
- DEFAULT_EXPORT_INTERVAL = 1.0 # seconds
17
+ DEFAULT_EXPORT_INTERVAL = 60.0 # seconds
18
18
  DEFAULT_EXPORT_BATCH_SIZE = 100
19
19
  DEFAULT_SHUTDOWN_TIMEOUT = 10.0 # seconds
20
20
  DEFAULT_MAX_RETRIES = 3
@@ -0,0 +1,141 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ require 'numo/narray'
5
+ require 'sorbet-runtime'
6
+
7
+ module DSPy
8
+ module Optimizers
9
+ # Pure Ruby Gaussian Process implementation for Bayesian optimization
10
+ # No external LAPACK/BLAS dependencies required
11
+ class GaussianProcess
12
+ extend T::Sig
13
+
14
+ sig { params(length_scale: Float, signal_variance: Float, noise_variance: Float).void }
15
+ def initialize(length_scale: 1.0, signal_variance: 1.0, noise_variance: 1e-6)
16
+ @length_scale = length_scale
17
+ @signal_variance = signal_variance
18
+ @noise_variance = noise_variance
19
+ @fitted = T.let(false, T::Boolean)
20
+ end
21
+
22
+ sig { params(x1: T::Array[T::Array[Float]], x2: T::Array[T::Array[Float]]).returns(Numo::DFloat) }
23
+ def rbf_kernel(x1, x2)
24
+ # Convert to Numo arrays
25
+ x1_array = Numo::DFloat[*x1]
26
+ x2_array = Numo::DFloat[*x2]
27
+
28
+ # Compute squared Euclidean distances manually
29
+ n1, n2 = x1_array.shape[0], x2_array.shape[0]
30
+ sqdist = Numo::DFloat.zeros(n1, n2)
31
+
32
+ (0...n1).each do |i|
33
+ (0...n2).each do |j|
34
+ diff = x1_array[i, true] - x2_array[j, true]
35
+ sqdist[i, j] = (diff ** 2).sum
36
+ end
37
+ end
38
+
39
+ # RBF kernel: σ² * exp(-0.5 * d² / ℓ²)
40
+ @signal_variance * Numo::NMath.exp(-0.5 * sqdist / (@length_scale ** 2))
41
+ end
42
+
43
+ sig { params(x_train: T::Array[T::Array[Float]], y_train: T::Array[Float]).void }
44
+ def fit(x_train, y_train)
45
+ @x_train = x_train
46
+ @y_train = Numo::DFloat[*y_train]
47
+
48
+ # Compute kernel matrix
49
+ k_matrix = rbf_kernel(x_train, x_train)
50
+
51
+ # Add noise to diagonal for numerical stability
52
+ n = k_matrix.shape[0]
53
+ (0...n).each { |i| k_matrix[i, i] += @noise_variance }
54
+
55
+ # Store inverted kernel matrix using simple LU decomposition
56
+ @k_inv = matrix_inverse(k_matrix)
57
+ @alpha = @k_inv.dot(@y_train)
58
+
59
+ @fitted = true
60
+ end
61
+
62
+ sig { params(x_test: T::Array[T::Array[Float]], return_std: T::Boolean).returns(T.any(Numo::DFloat, [Numo::DFloat, Numo::DFloat])) }
63
+ def predict(x_test, return_std: false)
64
+ raise "Gaussian Process not fitted" unless @fitted
65
+
66
+ # Kernel between training and test points
67
+ k_star = rbf_kernel(T.must(@x_train), x_test)
68
+
69
+ # Predictive mean
70
+ mean = k_star.transpose.dot(@alpha)
71
+
72
+ return mean unless return_std
73
+
74
+ # Predictive variance (simplified for small matrices)
75
+ k_star_star = rbf_kernel(x_test, x_test)
76
+ var_matrix = k_star_star - k_star.transpose.dot(@k_inv).dot(k_star)
77
+ var = var_matrix.diagonal
78
+
79
+ # Ensure positive variance (element-wise maximum)
80
+ var = var.map { |v| [v, 1e-12].max }
81
+ std = Numo::NMath.sqrt(var)
82
+
83
+ [mean, std]
84
+ end
85
+
86
+ private
87
+
88
+ sig { returns(T.nilable(T::Array[T::Array[Float]])) }
89
+ attr_reader :x_train
90
+
91
+ sig { returns(T.nilable(Numo::DFloat)) }
92
+ attr_reader :y_train, :k_inv, :alpha
93
+
94
+ # Simple matrix inversion using Gauss-Jordan elimination
95
+ # Only suitable for small matrices (< 100x100)
96
+ sig { params(matrix: Numo::DFloat).returns(Numo::DFloat) }
97
+ def matrix_inverse(matrix)
98
+ n = matrix.shape[0]
99
+ raise "Matrix must be square" unless matrix.shape[0] == matrix.shape[1]
100
+
101
+ # Create augmented matrix [A|I]
102
+ augmented = Numo::DFloat.zeros(n, 2*n)
103
+ augmented[true, 0...n] = matrix.copy
104
+ (0...n).each { |i| augmented[i, n+i] = 1.0 }
105
+
106
+ # Gauss-Jordan elimination
107
+ (0...n).each do |i|
108
+ # Find pivot
109
+ max_row = i
110
+ (i+1...n).each do |k|
111
+ if augmented[k, i].abs > augmented[max_row, i].abs
112
+ max_row = k
113
+ end
114
+ end
115
+
116
+ # Swap rows if needed
117
+ if max_row != i
118
+ temp = augmented[i, true].copy
119
+ augmented[i, true] = augmented[max_row, true]
120
+ augmented[max_row, true] = temp
121
+ end
122
+
123
+ # Make diagonal element 1
124
+ pivot = augmented[i, i]
125
+ raise "Matrix is singular" if pivot.abs < 1e-12
126
+ augmented[i, true] /= pivot
127
+
128
+ # Eliminate column
129
+ (0...n).each do |j|
130
+ next if i == j
131
+ factor = augmented[j, i]
132
+ augmented[j, true] -= factor * augmented[i, true]
133
+ end
134
+ end
135
+
136
+ # Extract inverse matrix
137
+ augmented[true, n...2*n]
138
+ end
139
+ end
140
+ end
141
+ end
@@ -5,9 +5,28 @@ require 'sorbet-runtime'
5
5
  require_relative 'teleprompter'
6
6
  require_relative 'utils'
7
7
  require_relative '../propose/grounded_proposer'
8
+ require_relative '../optimizers/gaussian_process'
8
9
 
9
10
  module DSPy
10
11
  module Teleprompt
12
+ # Enum for candidate configuration types
13
+ class CandidateType < T::Enum
14
+ enums do
15
+ Baseline = new("baseline")
16
+ InstructionOnly = new("instruction_only")
17
+ FewShotOnly = new("few_shot_only")
18
+ Combined = new("combined")
19
+ end
20
+ end
21
+
22
+ # Enum for optimization strategies
23
+ class OptimizationStrategy < T::Enum
24
+ enums do
25
+ Greedy = new("greedy")
26
+ Adaptive = new("adaptive")
27
+ Bayesian = new("bayesian")
28
+ end
29
+ end
11
30
  # MIPROv2: Multi-prompt Instruction Proposal with Retrieval Optimization
12
31
  # State-of-the-art prompt optimization combining bootstrap sampling,
13
32
  # instruction generation, and Bayesian optimization
@@ -141,40 +160,38 @@ module DSPy
141
160
  # Candidate configuration for optimization trials
142
161
  class CandidateConfig
143
162
  extend T::Sig
163
+ include Dry::Configurable
144
164
 
145
- sig { returns(String) }
146
- attr_reader :instruction
147
-
148
- sig { returns(T::Array[T.untyped]) }
149
- attr_reader :few_shot_examples
150
-
151
- sig { returns(T::Hash[Symbol, T.untyped]) }
152
- attr_reader :metadata
165
+ # Configuration settings
166
+ setting :instruction, default: ""
167
+ setting :few_shot_examples, default: []
168
+ setting :type, default: CandidateType::Baseline
169
+ setting :metadata, default: {}
153
170
 
154
171
  sig { returns(String) }
155
- attr_reader :config_id
156
-
157
- sig do
158
- params(
159
- instruction: String,
160
- few_shot_examples: T::Array[T.untyped],
161
- metadata: T::Hash[Symbol, T.untyped]
162
- ).void
172
+ def config_id
173
+ @config_id ||= generate_config_id
163
174
  end
164
- def initialize(instruction:, few_shot_examples:, metadata: {})
165
- @instruction = instruction
166
- @few_shot_examples = few_shot_examples
167
- @metadata = metadata.freeze
175
+
176
+ sig { void }
177
+ def finalize!
178
+ # Freeze settings after configuration to prevent mutation
179
+ config.instruction = config.instruction.freeze
180
+ config.few_shot_examples = config.few_shot_examples.freeze
181
+ config.metadata = config.metadata.freeze
182
+
183
+ # Generate ID after finalization
168
184
  @config_id = generate_config_id
169
185
  end
170
186
 
171
187
  sig { returns(T::Hash[Symbol, T.untyped]) }
172
188
  def to_h
173
189
  {
174
- instruction: @instruction,
175
- few_shot_examples: @few_shot_examples.size,
176
- metadata: @metadata,
177
- config_id: @config_id
190
+ instruction: config.instruction,
191
+ few_shot_examples: config.few_shot_examples.size,
192
+ type: config.type.serialize,
193
+ metadata: config.metadata,
194
+ config_id: config_id
178
195
  }
179
196
  end
180
197
 
@@ -182,7 +199,7 @@ module DSPy
182
199
 
183
200
  sig { returns(String) }
184
201
  def generate_config_id
185
- content = "#{@instruction}_#{@few_shot_examples.size}_#{@metadata.hash}"
202
+ content = "#{config.instruction}_#{config.few_shot_examples.size}_#{config.type.serialize}_#{config.metadata.hash}"
186
203
  Digest::SHA256.hexdigest(content)[0, 12]
187
204
  end
188
205
  end
@@ -263,6 +280,7 @@ module DSPy
263
280
  end
264
281
  def initialize(metric: nil, config: nil)
265
282
  @mipro_config = config || MIPROv2Config.new
283
+ # Call parent teleprompter initializer, which handles dry-configurable internally
266
284
  super(metric: metric, config: @mipro_config)
267
285
 
268
286
  @proposer = DSPy::Propose::GroundedProposer.new(config: @mipro_config.proposer_config)
@@ -416,8 +434,8 @@ module DSPy
416
434
  emit_event('trial_start', {
417
435
  trial_number: trials_completed,
418
436
  candidate_id: candidate.config_id,
419
- instruction_preview: candidate.instruction[0, 50],
420
- num_few_shot: candidate.few_shot_examples.size
437
+ instruction_preview: candidate.config.instruction[0, 50],
438
+ num_few_shot: candidate.config.few_shot_examples.size
421
439
  })
422
440
 
423
441
  begin
@@ -482,28 +500,31 @@ module DSPy
482
500
  candidates = []
483
501
 
484
502
  # Base configuration (no modifications)
485
- candidates << CandidateConfig.new(
486
- instruction: "",
487
- few_shot_examples: [],
488
- metadata: { type: "baseline" }
489
- )
503
+ candidates << create_candidate_config do |config|
504
+ config.instruction = ""
505
+ config.few_shot_examples = []
506
+ config.type = CandidateType::Baseline
507
+ config.metadata = {}
508
+ end
490
509
 
491
510
  # Instruction-only candidates
492
511
  proposal_result.candidate_instructions.each_with_index do |instruction, idx|
493
- candidates << CandidateConfig.new(
494
- instruction: instruction,
495
- few_shot_examples: [],
496
- metadata: { type: "instruction_only", proposal_rank: idx }
497
- )
512
+ candidates << create_candidate_config do |config|
513
+ config.instruction = instruction
514
+ config.few_shot_examples = []
515
+ config.type = CandidateType::InstructionOnly
516
+ config.metadata = { proposal_rank: idx }
517
+ end
498
518
  end
499
519
 
500
520
  # Few-shot only candidates
501
521
  bootstrap_result.candidate_sets.each_with_index do |candidate_set, idx|
502
- candidates << CandidateConfig.new(
503
- instruction: "",
504
- few_shot_examples: candidate_set,
505
- metadata: { type: "few_shot_only", bootstrap_rank: idx }
506
- )
522
+ candidates << create_candidate_config do |config|
523
+ config.instruction = ""
524
+ config.few_shot_examples = candidate_set
525
+ config.type = CandidateType::FewShotOnly
526
+ config.metadata = { bootstrap_rank: idx }
527
+ end
507
528
  end
508
529
 
509
530
  # Combined candidates (instruction + few-shot)
@@ -512,15 +533,15 @@ module DSPy
512
533
 
513
534
  top_instructions.each_with_index do |instruction, i_idx|
514
535
  top_bootstrap_sets.each_with_index do |candidate_set, b_idx|
515
- candidates << CandidateConfig.new(
516
- instruction: instruction,
517
- few_shot_examples: candidate_set,
518
- metadata: {
519
- type: "combined",
536
+ candidates << create_candidate_config do |config|
537
+ config.instruction = instruction
538
+ config.few_shot_examples = candidate_set
539
+ config.type = CandidateType::Combined
540
+ config.metadata = {
520
541
  instruction_rank: i_idx,
521
542
  bootstrap_rank: b_idx
522
543
  }
523
- )
544
+ end
524
545
  end
525
546
  end
526
547
 
@@ -624,9 +645,85 @@ module DSPy
624
645
  ).returns(CandidateConfig)
625
646
  end
626
647
  def select_candidate_bayesian(candidates, state, trial_idx)
627
- # For now, use adaptive selection with Bayesian-inspired exploration
628
- # In a full implementation, this would use Gaussian processes or similar
629
- select_candidate_adaptive(candidates, state, trial_idx)
648
+ # Need at least 3 observations to fit GP, otherwise fall back to adaptive
649
+ return select_candidate_adaptive(candidates, state, trial_idx) if state[:scores].size < 3
650
+
651
+ # Get scored candidates for training the GP
652
+ scored_candidates = candidates.select { |c| state[:scores].key?(c.config_id) }
653
+ return select_candidate_adaptive(candidates, state, trial_idx) if scored_candidates.size < 3
654
+
655
+ begin
656
+ # Encode candidates as numerical features
657
+ all_candidate_features = encode_candidates_for_gp(candidates)
658
+ scored_features = encode_candidates_for_gp(scored_candidates)
659
+ scored_targets = scored_candidates.map { |c| state[:scores][c.config_id].to_f }
660
+
661
+ # Train Gaussian Process
662
+ gp = DSPy::Optimizers::GaussianProcess.new(
663
+ length_scale: 1.0,
664
+ signal_variance: 1.0,
665
+ noise_variance: 0.01
666
+ )
667
+ gp.fit(scored_features, scored_targets)
668
+
669
+ # Predict mean and uncertainty for all candidates
670
+ means, stds = gp.predict(all_candidate_features, return_std: true)
671
+
672
+ # Upper Confidence Bound (UCB) acquisition function
673
+ kappa = 2.0 * Math.sqrt(Math.log(trial_idx + 1)) # Exploration parameter
674
+ acquisition_scores = means.to_a.zip(stds.to_a).map { |m, s| m + kappa * s }
675
+
676
+ # Select candidate with highest acquisition score
677
+ best_idx = acquisition_scores.each_with_index.max_by { |score, _| score }[1]
678
+ candidates[best_idx]
679
+
680
+ rescue => e
681
+ # If GP fails for any reason, fall back to adaptive selection
682
+ DSPy.logger.warn("Bayesian optimization failed: #{e.message}. Falling back to adaptive selection.")
683
+ select_candidate_adaptive(candidates, state, trial_idx)
684
+ end
685
+ end
686
+
687
+ private
688
+
689
+ # Helper method to create CandidateConfig with dry-configurable syntax
690
+ sig do
691
+ params(
692
+ block: T.proc.params(config: Dry::Configurable::Config).void
693
+ ).returns(CandidateConfig)
694
+ end
695
+ def create_candidate_config(&block)
696
+ candidate = CandidateConfig.new
697
+ candidate.configure(&block)
698
+ candidate.finalize!
699
+ candidate
700
+ end
701
+
702
+ # Encode candidates as numerical features for Gaussian Process
703
+ sig { params(candidates: T::Array[CandidateConfig]).returns(T::Array[T::Array[Float]]) }
704
+ def encode_candidates_for_gp(candidates)
705
+ # Simple encoding: use hash of config as features
706
+ # In practice, this could be more sophisticated (e.g., instruction embeddings)
707
+ candidates.map do |candidate|
708
+ # Create deterministic numerical features from the candidate config
709
+ config_hash = candidate.config_id.hash.abs
710
+
711
+ # Extract multiple features to create a feature vector
712
+ features = []
713
+ features << (config_hash % 1000).to_f / 1000.0 # Feature 1: hash mod 1000, normalized
714
+ features << ((config_hash / 1000) % 1000).to_f / 1000.0 # Feature 2: different part of hash
715
+ features << ((config_hash / 1_000_000) % 1000).to_f / 1000.0 # Feature 3: high bits
716
+
717
+ # Add instruction length if available
718
+ instruction = candidate.config.instruction
719
+ if instruction && !instruction.empty?
720
+ features << [instruction.length.to_f / 100.0, 2.0].min # Instruction length, capped at 200 chars
721
+ else
722
+ features << 0.5 # Default value
723
+ end
724
+
725
+ features
726
+ end
630
727
  end
631
728
 
632
729
  # Evaluate a candidate configuration
@@ -656,13 +753,13 @@ module DSPy
656
753
  modified_program = program
657
754
 
658
755
  # Apply instruction if provided
659
- if !candidate.instruction.empty? && program.respond_to?(:with_instruction)
660
- modified_program = modified_program.with_instruction(candidate.instruction)
756
+ if !candidate.config.instruction.empty? && program.respond_to?(:with_instruction)
757
+ modified_program = modified_program.with_instruction(candidate.config.instruction)
661
758
  end
662
759
 
663
760
  # Apply few-shot examples if provided
664
- if candidate.few_shot_examples.any? && program.respond_to?(:with_examples)
665
- few_shot_examples = candidate.few_shot_examples.map do |example|
761
+ if candidate.config.few_shot_examples.any? && program.respond_to?(:with_examples)
762
+ few_shot_examples = candidate.config.few_shot_examples.map do |example|
666
763
  DSPy::FewShotExample.new(
667
764
  input: example.input_values,
668
765
  output: example.expected_values,
@@ -715,8 +812,8 @@ module DSPy
715
812
  sig { params(candidate: CandidateConfig).returns(Float) }
716
813
  def calculate_diversity_score(candidate)
717
814
  # Simple diversity metric based on instruction length and few-shot count
718
- instruction_diversity = candidate.instruction.length / 200.0
719
- few_shot_diversity = candidate.few_shot_examples.size / 10.0
815
+ instruction_diversity = candidate.config.instruction.length / 200.0
816
+ few_shot_diversity = candidate.config.few_shot_examples.size / 10.0
720
817
 
721
818
  [instruction_diversity + few_shot_diversity, 1.0].min
722
819
  end
@@ -747,9 +844,9 @@ module DSPy
747
844
  metadata = {
748
845
  optimizer: "MIPROv2",
749
846
  auto_mode: infer_auto_mode,
750
- best_instruction: best_candidate&.instruction || "",
751
- best_few_shot_count: best_candidate&.few_shot_examples&.size || 0,
752
- best_candidate_type: best_candidate&.metadata&.fetch(:type, "unknown"),
847
+ best_instruction: best_candidate&.config&.instruction || "",
848
+ best_few_shot_count: best_candidate&.config&.few_shot_examples&.size || 0,
849
+ best_candidate_type: best_candidate&.config&.type&.serialize || "unknown",
753
850
  optimization_timestamp: Time.now.iso8601
754
851
  }
755
852
 
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.25.0"
4
+ VERSION = "0.26.0"
5
5
  end
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.25.0
4
+ version: 0.26.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-09-07 00:00:00.000000000 Z
10
+ date: 2025-09-09 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -122,19 +122,19 @@ dependencies:
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0.3'
124
124
  - !ruby/object:Gem::Dependency
125
- name: polars-df
125
+ name: numo-narray
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: 0.20.0
130
+ version: '0.9'
131
131
  type: :runtime
132
132
  prerelease: false
133
133
  version_requirements: !ruby/object:Gem::Requirement
134
134
  requirements:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
- version: 0.20.0
137
+ version: '0.9'
138
138
  - !ruby/object:Gem::Dependency
139
139
  name: informers
140
140
  requirement: !ruby/object:Gem::Requirement
@@ -239,6 +239,7 @@ files:
239
239
  - lib/dspy/module.rb
240
240
  - lib/dspy/observability.rb
241
241
  - lib/dspy/observability/async_span_processor.rb
242
+ - lib/dspy/optimizers/gaussian_process.rb
242
243
  - lib/dspy/predict.rb
243
244
  - lib/dspy/prediction.rb
244
245
  - lib/dspy/prompt.rb