dspy 0.28.1 → 0.28.2
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/lib/dspy/callbacks.rb +222 -0
- data/lib/dspy/chain_of_thought.rb +2 -1
- data/lib/dspy/lm.rb +5 -4
- data/lib/dspy/module.rb +33 -0
- data/lib/dspy/predict.rb +7 -0
- data/lib/dspy/prompt.rb +90 -20
- data/lib/dspy/propose/dataset_summary_generator.rb +177 -0
- data/lib/dspy/propose/grounded_proposer.rb +208 -61
- data/lib/dspy/teleprompt/bootstrap_strategy.rb +26 -0
- data/lib/dspy/teleprompt/mipro_v2.rb +81 -56
- data/lib/dspy/teleprompt/simple_optimizer.rb +40 -34
- data/lib/dspy/teleprompt/utils.rb +343 -41
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +1 -0
- metadata +19 -2
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'sorbet-runtime'
|
4
|
+
require 'fileutils'
|
4
5
|
require_relative '../evaluate'
|
5
6
|
require_relative '../example'
|
6
7
|
require_relative 'data_handler'
|
@@ -12,6 +13,167 @@ module DSPy
|
|
12
13
|
module Utils
|
13
14
|
extend T::Sig
|
14
15
|
|
16
|
+
# Wrapper class that provides Python-compatible signature API
|
17
|
+
# Wraps a Predict instance to provide signature access and modification
|
18
|
+
class SignatureWrapper
|
19
|
+
extend T::Sig
|
20
|
+
|
21
|
+
sig { returns(T.untyped) }
|
22
|
+
attr_reader :predictor
|
23
|
+
|
24
|
+
sig { params(predictor: T.untyped).void }
|
25
|
+
def initialize(predictor)
|
26
|
+
@predictor = predictor
|
27
|
+
end
|
28
|
+
|
29
|
+
sig { returns(String) }
|
30
|
+
def instructions
|
31
|
+
# Get instructions from the predictor's prompt
|
32
|
+
@predictor.prompt.instruction
|
33
|
+
end
|
34
|
+
|
35
|
+
sig { params(new_instructions: String).returns(SignatureWrapper) }
|
36
|
+
def with_instructions(new_instructions)
|
37
|
+
# Return a new wrapper that will apply new instructions when set
|
38
|
+
updated_wrapper = SignatureWrapper.new(@predictor)
|
39
|
+
updated_wrapper.instance_variable_set(:@pending_instructions, new_instructions)
|
40
|
+
updated_wrapper
|
41
|
+
end
|
42
|
+
|
43
|
+
sig { returns(T.nilable(String)) }
|
44
|
+
def pending_instructions
|
45
|
+
@pending_instructions
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Get signature information from a predictor (Python compatibility)
|
50
|
+
# Returns a wrapper that provides Python-like signature API
|
51
|
+
#
|
52
|
+
# @param predictor [Predict] The predictor to get signature from
|
53
|
+
# @return [SignatureWrapper] Wrapper providing signature access
|
54
|
+
sig { params(predictor: T.untyped).returns(SignatureWrapper) }
|
55
|
+
def self.get_signature(predictor)
|
56
|
+
SignatureWrapper.new(predictor)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Set signature on a predictor (Python compatibility)
|
60
|
+
# Updates the predictor's prompt with new instructions
|
61
|
+
#
|
62
|
+
# @param predictor [Predict] The predictor to update
|
63
|
+
# @param updated_signature [SignatureWrapper] The updated signature wrapper
|
64
|
+
sig { params(predictor: T.untyped, updated_signature: SignatureWrapper).void }
|
65
|
+
def self.set_signature(predictor, updated_signature)
|
66
|
+
# Extract pending instructions from the wrapper
|
67
|
+
new_instructions = updated_signature.pending_instructions
|
68
|
+
|
69
|
+
if new_instructions
|
70
|
+
# Update the predictor's prompt with new instructions
|
71
|
+
# We mutate the prompt's instruction directly for MIPROv2 compatibility
|
72
|
+
predictor.prompt.instance_variable_set(:@instruction, new_instructions)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a minibatch from the trainset using random sampling
|
77
|
+
# This function is compatible with Python DSPy's MIPROv2 implementation
|
78
|
+
#
|
79
|
+
# @param trainset [Array] The training dataset to sample from
|
80
|
+
# @param batch_size [Integer] The desired size of the minibatch (default: 50)
|
81
|
+
# @param rng [Random, nil] Optional random number generator for reproducible sampling
|
82
|
+
# @return [Array] A randomly sampled subset of the trainset
|
83
|
+
sig do
|
84
|
+
params(
|
85
|
+
trainset: T::Array[T.untyped],
|
86
|
+
batch_size: Integer,
|
87
|
+
rng: T.nilable(Random)
|
88
|
+
).returns(T::Array[T.untyped])
|
89
|
+
end
|
90
|
+
def self.create_minibatch(trainset, batch_size = 50, rng = nil)
|
91
|
+
# Ensure batch_size isn't larger than the size of the dataset
|
92
|
+
actual_batch_size = [batch_size, trainset.size].min
|
93
|
+
|
94
|
+
# Randomly sample from trainset
|
95
|
+
# If RNG is provided, use it for reproducible sampling
|
96
|
+
if rng
|
97
|
+
trainset.sample(actual_batch_size, random: rng)
|
98
|
+
else
|
99
|
+
trainset.sample(actual_batch_size)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
# Get program with highest average score from minibatch trials
|
104
|
+
# Used as a helper function for Bayesian + minibatching optimizers
|
105
|
+
#
|
106
|
+
# @param param_score_dict [Hash] Maps combo keys to arrays of [score, program, params] tuples
|
107
|
+
# @param fully_evaled_param_combos [Array] List of combo keys that have been fully evaluated
|
108
|
+
# @return [Array] Returns [program, mean_score, combo_key, params]
|
109
|
+
sig do
|
110
|
+
params(
|
111
|
+
param_score_dict: T::Hash[String, T::Array[T::Array[T.untyped]]],
|
112
|
+
fully_evaled_param_combos: T::Array[String]
|
113
|
+
).returns([T.untyped, Float, String, T::Hash[Symbol, T.untyped]])
|
114
|
+
end
|
115
|
+
def self.get_program_with_highest_avg_score(param_score_dict, fully_evaled_param_combos)
|
116
|
+
# Calculate the mean for each combination of categorical parameters, based on past trials
|
117
|
+
results = []
|
118
|
+
param_score_dict.each do |key, values|
|
119
|
+
scores = values.map { |v| v[0] }
|
120
|
+
mean = scores.sum.to_f / scores.size
|
121
|
+
program = values[0][1]
|
122
|
+
params = values[0][2]
|
123
|
+
results << [key, mean, program, params]
|
124
|
+
end
|
125
|
+
|
126
|
+
# Sort results by the mean in descending order
|
127
|
+
sorted_results = results.sort_by { |_key, mean, _program, _params| -mean }
|
128
|
+
|
129
|
+
# Find the combination with the highest mean, skip fully evaluated ones
|
130
|
+
sorted_results.each do |key, mean, program, params|
|
131
|
+
next if fully_evaled_param_combos.include?(key)
|
132
|
+
return [program, mean, key, params]
|
133
|
+
end
|
134
|
+
|
135
|
+
# If no valid program is found, return the last valid one
|
136
|
+
_key, mean, program, params = sorted_results.last
|
137
|
+
[program, mean, _key, params]
|
138
|
+
end
|
139
|
+
|
140
|
+
# Save a candidate program to the log directory
|
141
|
+
# Used during optimization to save intermediate trial results
|
142
|
+
#
|
143
|
+
# @param program [Module] The program to save
|
144
|
+
# @param log_dir [String, nil] The directory to save to (returns nil if nil)
|
145
|
+
# @param trial_num [Integer] The trial number for naming the file
|
146
|
+
# @param note [String, nil] Optional note to append to filename
|
147
|
+
# @return [String, nil] The path where program was saved, or nil if log_dir is nil
|
148
|
+
sig do
|
149
|
+
params(
|
150
|
+
program: T.untyped,
|
151
|
+
log_dir: T.nilable(String),
|
152
|
+
trial_num: Integer,
|
153
|
+
note: T.nilable(String)
|
154
|
+
).returns(T.nilable(String))
|
155
|
+
end
|
156
|
+
def self.save_candidate_program(program, log_dir, trial_num, note: nil)
|
157
|
+
return nil if log_dir.nil?
|
158
|
+
|
159
|
+
# Ensure the directory exists
|
160
|
+
eval_programs_dir = File.join(log_dir, "evaluated_programs")
|
161
|
+
FileUtils.mkdir_p(eval_programs_dir) unless Dir.exist?(eval_programs_dir)
|
162
|
+
|
163
|
+
# Define the save path for the program
|
164
|
+
filename = if note
|
165
|
+
"program_#{trial_num}_#{note}.json"
|
166
|
+
else
|
167
|
+
"program_#{trial_num}.json"
|
168
|
+
end
|
169
|
+
save_path = File.join(eval_programs_dir, filename)
|
170
|
+
|
171
|
+
# Save the program
|
172
|
+
program.save(save_path)
|
173
|
+
|
174
|
+
save_path
|
175
|
+
end
|
176
|
+
|
15
177
|
# Configuration for bootstrap operations
|
16
178
|
class BootstrapConfig
|
17
179
|
extend T::Sig
|
@@ -50,6 +212,9 @@ module DSPy
|
|
50
212
|
end
|
51
213
|
|
52
214
|
# Result of bootstrap operation
|
215
|
+
# @deprecated This class is deprecated and kept only for backward compatibility.
|
216
|
+
# The new create_n_fewshot_demo_sets returns a Hash{predictor_idx => [[demos]]}
|
217
|
+
# instead of this BootstrapResult object. Use the dict interface directly.
|
53
218
|
class BootstrapResult
|
54
219
|
extend T::Sig
|
55
220
|
|
@@ -93,58 +258,195 @@ module DSPy
|
|
93
258
|
end
|
94
259
|
end
|
95
260
|
|
96
|
-
# Create multiple candidate sets of few-shot
|
261
|
+
# Create multiple candidate sets of few-shot demonstrations using different bootstrap strategies
|
262
|
+
#
|
263
|
+
# This is the Python-compatible implementation that uses a seed-based loop to create
|
264
|
+
# demo sets using 4 strategies: ZeroShot (-3), LabeledOnly (-2), Unshuffled (-1), and Shuffled (>=0)
|
265
|
+
#
|
266
|
+
# @param student [DSPy::Module] The student program to bootstrap
|
267
|
+
# @param num_candidate_sets [Integer] Number of demo sets to create (accounts for special seeds)
|
268
|
+
# @param trainset [Array<DSPy::Example>] Training examples
|
269
|
+
# @param max_bootstrapped_demos [Integer] Maximum bootstrapped demonstrations per set
|
270
|
+
# @param max_labeled_demos [Integer] Maximum labeled demonstrations to prepend
|
271
|
+
# @param min_num_samples [Integer] Minimum number of samples for shuffled strategy
|
272
|
+
# @param metric [Proc] Optional metric to validate bootstrapped examples
|
273
|
+
# @param teacher_settings [Hash] Settings for teacher program (future use)
|
274
|
+
# @param seed [Integer] Random seed for reproducibility
|
275
|
+
# @param include_non_bootstrapped [Boolean] Include ZeroShot and LabeledOnly strategies
|
276
|
+
# @param labeled_sample [Boolean] Whether to sample labeled examples randomly
|
277
|
+
# @return [Hash{Integer => Array<Array<DSPy::FewShotExample>>}] Map of predictor index to demo sets
|
97
278
|
sig do
|
98
279
|
params(
|
99
|
-
|
280
|
+
student: T.untyped,
|
281
|
+
num_candidate_sets: Integer,
|
100
282
|
trainset: T::Array[T.untyped],
|
101
|
-
|
102
|
-
|
103
|
-
|
283
|
+
max_bootstrapped_demos: Integer,
|
284
|
+
max_labeled_demos: Integer,
|
285
|
+
min_num_samples: Integer,
|
286
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean)),
|
287
|
+
teacher_settings: T::Hash[Symbol, T.untyped],
|
288
|
+
seed: T.nilable(Integer),
|
289
|
+
include_non_bootstrapped: T::Boolean,
|
290
|
+
labeled_sample: T::Boolean
|
291
|
+
).returns(T::Hash[Integer, T::Array[T::Array[DSPy::FewShotExample]]])
|
104
292
|
end
|
105
|
-
def self.create_n_fewshot_demo_sets(
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
293
|
+
def self.create_n_fewshot_demo_sets(
|
294
|
+
student,
|
295
|
+
num_candidate_sets,
|
296
|
+
trainset,
|
297
|
+
max_bootstrapped_demos: 3,
|
298
|
+
max_labeled_demos: 3,
|
299
|
+
min_num_samples: 1,
|
300
|
+
metric: nil,
|
301
|
+
teacher_settings: {},
|
302
|
+
seed: nil,
|
303
|
+
include_non_bootstrapped: true,
|
304
|
+
labeled_sample: true
|
305
|
+
)
|
306
|
+
demo_candidates = Hash.new { |h, k| h[k] = [] }
|
307
|
+
rng = seed ? Random.new(seed) : Random.new
|
308
|
+
|
309
|
+
# Get number of predictors (simplified: assume single predictor)
|
310
|
+
num_predictors = 1
|
311
|
+
|
312
|
+
# Adjust for 3 special seeds (-3, -2, -1)
|
313
|
+
adjusted_num_sets = num_candidate_sets - 3
|
314
|
+
|
315
|
+
# Loop from -3 to adjusted_num_sets (exclusive)
|
316
|
+
(-3...adjusted_num_sets).each do |current_seed|
|
317
|
+
case current_seed
|
318
|
+
when -3 # ZeroShot strategy
|
319
|
+
next unless include_non_bootstrapped
|
320
|
+
# Empty demo sets for all predictors
|
321
|
+
num_predictors.times { |idx| demo_candidates[idx] << [] }
|
322
|
+
|
323
|
+
when -2 # LabeledOnly strategy
|
324
|
+
next unless include_non_bootstrapped && max_labeled_demos > 0
|
325
|
+
# Sample or take labeled examples
|
326
|
+
labeled_demos = create_labeled_demos(trainset, max_labeled_demos, labeled_sample, rng)
|
327
|
+
num_predictors.times { |idx| demo_candidates[idx] << labeled_demos }
|
328
|
+
|
329
|
+
when -1 # Unshuffled strategy
|
330
|
+
# Bootstrap without shuffle
|
331
|
+
bootstrapped_demos = create_bootstrapped_demos(
|
332
|
+
student, trainset, max_bootstrapped_demos, max_labeled_demos, metric
|
333
|
+
)
|
334
|
+
num_predictors.times { |idx| demo_candidates[idx] << bootstrapped_demos }
|
335
|
+
|
336
|
+
else # Shuffled strategies (seed >= 0)
|
337
|
+
# Shuffle trainset with current seed
|
338
|
+
seed_rng = Random.new(current_seed)
|
339
|
+
shuffled_trainset = trainset.shuffle(random: seed_rng)
|
340
|
+
|
341
|
+
# Random demo count between min and max
|
342
|
+
num_demos = seed_rng.rand(min_num_samples..max_bootstrapped_demos)
|
343
|
+
|
344
|
+
# Bootstrap with shuffled data
|
345
|
+
bootstrapped_demos = create_bootstrapped_demos(
|
346
|
+
student, shuffled_trainset, num_demos, max_labeled_demos, metric
|
347
|
+
)
|
348
|
+
num_predictors.times { |idx| demo_candidates[idx] << bootstrapped_demos }
|
349
|
+
end
|
350
|
+
end
|
123
351
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
# Gather statistics
|
128
|
-
statistics = {
|
129
|
-
total_trainset: trainset.size,
|
130
|
-
successful_count: successful_examples.size,
|
131
|
-
failed_count: failed_examples.size,
|
132
|
-
success_rate: successful_examples.size.to_f / (successful_examples.size + failed_examples.size),
|
133
|
-
candidate_sets_created: candidate_sets.size,
|
134
|
-
average_set_size: candidate_sets.empty? ? 0 : candidate_sets.map(&:size).sum.to_f / candidate_sets.size
|
135
|
-
}
|
352
|
+
demo_candidates
|
353
|
+
end
|
136
354
|
|
137
|
-
|
355
|
+
# Create labeled demonstrations from trainset examples
|
356
|
+
sig do
|
357
|
+
params(
|
358
|
+
trainset: T::Array[T.untyped],
|
359
|
+
max_labeled: Integer,
|
360
|
+
labeled_sample: T::Boolean,
|
361
|
+
rng: Random
|
362
|
+
).returns(T::Array[DSPy::FewShotExample])
|
363
|
+
end
|
364
|
+
def self.create_labeled_demos(trainset, max_labeled, labeled_sample, rng)
|
365
|
+
examples = if labeled_sample
|
366
|
+
trainset.sample([max_labeled, trainset.size].min, random: rng)
|
367
|
+
else
|
368
|
+
trainset.take(max_labeled)
|
369
|
+
end
|
138
370
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
statistics: statistics
|
371
|
+
examples.map do |ex|
|
372
|
+
DSPy::FewShotExample.new(
|
373
|
+
input: ex.input_values,
|
374
|
+
output: ex.expected_values
|
144
375
|
)
|
145
376
|
end
|
146
377
|
end
|
147
378
|
|
379
|
+
# Create bootstrapped demonstrations by executing student on trainset
|
380
|
+
sig do
|
381
|
+
params(
|
382
|
+
student: T.untyped,
|
383
|
+
trainset: T::Array[T.untyped],
|
384
|
+
max_bootstrapped: Integer,
|
385
|
+
max_labeled: Integer,
|
386
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
|
387
|
+
).returns(T::Array[DSPy::FewShotExample])
|
388
|
+
end
|
389
|
+
def self.create_bootstrapped_demos(student, trainset, max_bootstrapped, max_labeled, metric)
|
390
|
+
successful_demos = []
|
391
|
+
|
392
|
+
# Execute student on trainset to bootstrap demonstrations
|
393
|
+
trainset.each do |example|
|
394
|
+
break if successful_demos.size >= max_bootstrapped
|
395
|
+
|
396
|
+
begin
|
397
|
+
# Call student with input
|
398
|
+
prediction = student.call(**example.input_values)
|
399
|
+
prediction_hash = prediction.respond_to?(:to_h) ? prediction.to_h : prediction
|
400
|
+
|
401
|
+
# Check if prediction matches expected output
|
402
|
+
success = if metric
|
403
|
+
metric.call(example, prediction_hash)
|
404
|
+
else
|
405
|
+
example.matches_prediction?(prediction_hash)
|
406
|
+
end
|
407
|
+
|
408
|
+
if success
|
409
|
+
# Extract only output fields from prediction
|
410
|
+
output_fields = extract_output_fields_for_demo(prediction_hash, example.signature_class)
|
411
|
+
|
412
|
+
demo = DSPy::FewShotExample.new(
|
413
|
+
input: example.input_values,
|
414
|
+
output: output_fields
|
415
|
+
)
|
416
|
+
successful_demos << demo
|
417
|
+
end
|
418
|
+
rescue => e
|
419
|
+
# Continue on errors
|
420
|
+
DSPy.logger.warn("Bootstrap error: #{e.message}") if DSPy.logger
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# Prepend labeled examples if requested
|
425
|
+
if max_labeled > 0
|
426
|
+
labeled = trainset.take(max_labeled).map do |ex|
|
427
|
+
DSPy::FewShotExample.new(
|
428
|
+
input: ex.input_values,
|
429
|
+
output: ex.expected_values
|
430
|
+
)
|
431
|
+
end
|
432
|
+
successful_demos = labeled + successful_demos
|
433
|
+
end
|
434
|
+
|
435
|
+
successful_demos
|
436
|
+
end
|
437
|
+
|
438
|
+
# Extract only output fields from prediction hash
|
439
|
+
sig do
|
440
|
+
params(
|
441
|
+
prediction_hash: T::Hash[Symbol, T.untyped],
|
442
|
+
signature_class: T.class_of(DSPy::Signature)
|
443
|
+
).returns(T::Hash[Symbol, T.untyped])
|
444
|
+
end
|
445
|
+
def self.extract_output_fields_for_demo(prediction_hash, signature_class)
|
446
|
+
output_field_names = signature_class.output_field_descriptors.keys
|
447
|
+
prediction_hash.slice(*output_field_names)
|
448
|
+
end
|
449
|
+
|
148
450
|
# Evaluate a candidate program on examples with proper error handling
|
149
451
|
sig do
|
150
452
|
params(
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.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.28.
|
4
|
+
version: 0.28.2
|
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-
|
10
|
+
date: 2025-10-13 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
12
|
- !ruby/object:Gem::Dependency
|
13
13
|
name: dry-configurable
|
@@ -121,6 +121,20 @@ dependencies:
|
|
121
121
|
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: '0.3'
|
124
|
+
- !ruby/object:Gem::Dependency
|
125
|
+
name: sorbet-baml
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
127
|
+
requirements:
|
128
|
+
- - "~>"
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0.1'
|
131
|
+
type: :runtime
|
132
|
+
prerelease: false
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - "~>"
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '0.1'
|
124
138
|
- !ruby/object:Gem::Dependency
|
125
139
|
name: numo-narray
|
126
140
|
requirement: !ruby/object:Gem::Requirement
|
@@ -189,6 +203,7 @@ extra_rdoc_files: []
|
|
189
203
|
files:
|
190
204
|
- README.md
|
191
205
|
- lib/dspy.rb
|
206
|
+
- lib/dspy/callbacks.rb
|
192
207
|
- lib/dspy/chain_of_thought.rb
|
193
208
|
- lib/dspy/code_act.rb
|
194
209
|
- lib/dspy/context.rb
|
@@ -239,6 +254,7 @@ files:
|
|
239
254
|
- lib/dspy/predict.rb
|
240
255
|
- lib/dspy/prediction.rb
|
241
256
|
- lib/dspy/prompt.rb
|
257
|
+
- lib/dspy/propose/dataset_summary_generator.rb
|
242
258
|
- lib/dspy/propose/grounded_proposer.rb
|
243
259
|
- lib/dspy/re_act.rb
|
244
260
|
- lib/dspy/registry/registry_manager.rb
|
@@ -248,6 +264,7 @@ files:
|
|
248
264
|
- lib/dspy/storage/program_storage.rb
|
249
265
|
- lib/dspy/storage/storage_manager.rb
|
250
266
|
- lib/dspy/structured_outputs_prompt.rb
|
267
|
+
- lib/dspy/teleprompt/bootstrap_strategy.rb
|
251
268
|
- lib/dspy/teleprompt/data_handler.rb
|
252
269
|
- lib/dspy/teleprompt/gepa.rb
|
253
270
|
- lib/dspy/teleprompt/mipro_v2.rb
|