dspy 0.25.1 → 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 +4 -4
- data/README.md +9 -10
- data/lib/dspy/optimizers/gaussian_process.rb +141 -0
- data/lib/dspy/teleprompt/mipro_v2.rb +157 -60
- data/lib/dspy/version.rb +1 -1
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 573bfc89c0ca4d6c58a3d68fa96e0b0e8fc1d8bc2c38eec9c67e245a51527532
|
4
|
+
data.tar.gz: a2b34e8127abafbdec6842c3db475e1abbfbcf06c0ca9bfcd3fa7ea8299d0d0f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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** -
|
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)** -
|
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 `
|
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
|
175
|
-
|
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
|
179
|
-
sudo apt-get install
|
177
|
+
# Install additional development libraries
|
178
|
+
sudo apt-get install libffi-dev libssl-dev
|
180
179
|
```
|
181
180
|
|
182
|
-
**Note**: The `
|
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
|
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
|
@@ -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
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
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
|
-
|
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
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
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:
|
175
|
-
few_shot_examples:
|
176
|
-
|
177
|
-
|
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 = "#{
|
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 <<
|
486
|
-
instruction
|
487
|
-
few_shot_examples
|
488
|
-
|
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 <<
|
494
|
-
instruction
|
495
|
-
few_shot_examples
|
496
|
-
|
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 <<
|
503
|
-
instruction
|
504
|
-
few_shot_examples
|
505
|
-
|
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 <<
|
516
|
-
instruction
|
517
|
-
few_shot_examples
|
518
|
-
|
519
|
-
|
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
|
-
#
|
628
|
-
|
629
|
-
|
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&.
|
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
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.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-
|
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:
|
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.
|
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.
|
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
|