dspy 0.28.1 → 0.29.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 +2 -3
- data/lib/dspy/callbacks.rb +222 -0
- data/lib/dspy/chain_of_thought.rb +2 -1
- data/lib/dspy/code_act.rb +14 -1
- data/lib/dspy/datasets/ade.rb +90 -0
- data/lib/dspy/datasets.rb +8 -0
- data/lib/dspy/lm.rb +9 -12
- data/lib/dspy/mixins/struct_builder.rb +17 -25
- data/lib/dspy/module.rb +45 -1
- data/lib/dspy/observability/async_span_processor.rb +67 -93
- data/lib/dspy/observability.rb +43 -1
- data/lib/dspy/predict.rb +17 -0
- data/lib/dspy/prompt.rb +90 -20
- data/lib/dspy/propose/dataset_summary_generator.rb +210 -0
- data/lib/dspy/propose/grounded_proposer.rb +320 -66
- data/lib/dspy/re_act.rb +13 -0
- data/lib/dspy/reflection_lm.rb +36 -0
- data/lib/dspy/teleprompt/bootstrap_strategy.rb +26 -0
- data/lib/dspy/teleprompt/gepa.rb +448 -2803
- data/lib/dspy/teleprompt/mipro_v2.rb +624 -100
- data/lib/dspy/teleprompt/utils.rb +349 -42
- data/lib/dspy/version.rb +2 -2
- data/lib/dspy.rb +4 -2
- data/lib/gepa/api.rb +61 -0
- data/lib/gepa/core/engine.rb +226 -0
- data/lib/gepa/core/evaluation_batch.rb +26 -0
- data/lib/gepa/core/result.rb +92 -0
- data/lib/gepa/core/state.rb +231 -0
- data/lib/gepa/logging/experiment_tracker.rb +54 -0
- data/lib/gepa/logging/logger.rb +57 -0
- data/lib/gepa/logging.rb +9 -0
- data/lib/gepa/proposer/base.rb +27 -0
- data/lib/gepa/proposer/merge_proposer.rb +424 -0
- data/lib/gepa/proposer/reflective_mutation/base.rb +48 -0
- data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +188 -0
- data/lib/gepa/strategies/batch_sampler.rb +91 -0
- data/lib/gepa/strategies/candidate_selector.rb +97 -0
- data/lib/gepa/strategies/component_selector.rb +57 -0
- data/lib/gepa/strategies/instruction_proposal.rb +120 -0
- data/lib/gepa/telemetry.rb +122 -0
- data/lib/gepa/utils/pareto.rb +119 -0
- data/lib/gepa.rb +21 -0
- metadata +59 -4
- data/lib/dspy/teleprompt/simple_optimizer.rb +0 -497
@@ -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,200 @@ 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
|
+
# Determine number of predictors exposed by the student module
|
310
|
+
num_predictors = if student.respond_to?(:predictors)
|
311
|
+
predictors = Array(student.predictors)
|
312
|
+
predictors.empty? ? 1 : predictors.size
|
313
|
+
else
|
314
|
+
1
|
315
|
+
end
|
123
316
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
317
|
+
# Adjust for 3 special seeds (-3, -2, -1)
|
318
|
+
adjusted_num_sets = num_candidate_sets - 3
|
319
|
+
|
320
|
+
# Loop from -3 to adjusted_num_sets (exclusive)
|
321
|
+
(-3...adjusted_num_sets).each do |current_seed|
|
322
|
+
case current_seed
|
323
|
+
when -3 # ZeroShot strategy
|
324
|
+
next unless include_non_bootstrapped
|
325
|
+
# Empty demo sets for all predictors
|
326
|
+
num_predictors.times { |idx| demo_candidates[idx] << [] }
|
327
|
+
|
328
|
+
when -2 # LabeledOnly strategy
|
329
|
+
next unless include_non_bootstrapped && max_labeled_demos > 0
|
330
|
+
# Sample or take labeled examples
|
331
|
+
labeled_demos = create_labeled_demos(trainset, max_labeled_demos, labeled_sample, rng)
|
332
|
+
num_predictors.times { |idx| demo_candidates[idx] << labeled_demos }
|
333
|
+
|
334
|
+
when -1 # Unshuffled strategy
|
335
|
+
# Bootstrap without shuffle
|
336
|
+
bootstrapped_demos = create_bootstrapped_demos(
|
337
|
+
student, trainset, max_bootstrapped_demos, max_labeled_demos, metric
|
338
|
+
)
|
339
|
+
num_predictors.times { |idx| demo_candidates[idx] << bootstrapped_demos }
|
340
|
+
|
341
|
+
else # Shuffled strategies (seed >= 0)
|
342
|
+
# Shuffle trainset with current seed
|
343
|
+
seed_rng = Random.new(current_seed)
|
344
|
+
shuffled_trainset = trainset.shuffle(random: seed_rng)
|
345
|
+
|
346
|
+
# Random demo count between min and max
|
347
|
+
num_demos = seed_rng.rand(min_num_samples..max_bootstrapped_demos)
|
348
|
+
|
349
|
+
# Bootstrap with shuffled data
|
350
|
+
bootstrapped_demos = create_bootstrapped_demos(
|
351
|
+
student, shuffled_trainset, num_demos, max_labeled_demos, metric
|
352
|
+
)
|
353
|
+
num_predictors.times { |idx| demo_candidates[idx] << bootstrapped_demos }
|
354
|
+
end
|
355
|
+
end
|
136
356
|
|
137
|
-
|
357
|
+
demo_candidates
|
358
|
+
end
|
138
359
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
360
|
+
# Create labeled demonstrations from trainset examples
|
361
|
+
sig do
|
362
|
+
params(
|
363
|
+
trainset: T::Array[T.untyped],
|
364
|
+
max_labeled: Integer,
|
365
|
+
labeled_sample: T::Boolean,
|
366
|
+
rng: Random
|
367
|
+
).returns(T::Array[DSPy::FewShotExample])
|
368
|
+
end
|
369
|
+
def self.create_labeled_demos(trainset, max_labeled, labeled_sample, rng)
|
370
|
+
examples = if labeled_sample
|
371
|
+
trainset.sample([max_labeled, trainset.size].min, random: rng)
|
372
|
+
else
|
373
|
+
trainset.take(max_labeled)
|
374
|
+
end
|
375
|
+
|
376
|
+
examples.map do |ex|
|
377
|
+
DSPy::FewShotExample.new(
|
378
|
+
input: ex.input_values,
|
379
|
+
output: ex.expected_values
|
144
380
|
)
|
145
381
|
end
|
146
382
|
end
|
147
383
|
|
384
|
+
# Create bootstrapped demonstrations by executing student on trainset
|
385
|
+
sig do
|
386
|
+
params(
|
387
|
+
student: T.untyped,
|
388
|
+
trainset: T::Array[T.untyped],
|
389
|
+
max_bootstrapped: Integer,
|
390
|
+
max_labeled: Integer,
|
391
|
+
metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
|
392
|
+
).returns(T::Array[DSPy::FewShotExample])
|
393
|
+
end
|
394
|
+
def self.create_bootstrapped_demos(student, trainset, max_bootstrapped, max_labeled, metric)
|
395
|
+
successful_demos = []
|
396
|
+
|
397
|
+
# Execute student on trainset to bootstrap demonstrations
|
398
|
+
trainset.each do |example|
|
399
|
+
break if successful_demos.size >= max_bootstrapped
|
400
|
+
|
401
|
+
begin
|
402
|
+
# Call student with input
|
403
|
+
prediction = student.call(**example.input_values)
|
404
|
+
prediction_hash = prediction.respond_to?(:to_h) ? prediction.to_h : prediction
|
405
|
+
|
406
|
+
# Check if prediction matches expected output
|
407
|
+
success = if metric
|
408
|
+
metric.call(example, prediction_hash)
|
409
|
+
else
|
410
|
+
example.matches_prediction?(prediction_hash)
|
411
|
+
end
|
412
|
+
|
413
|
+
if success
|
414
|
+
# Extract only output fields from prediction
|
415
|
+
output_fields = extract_output_fields_for_demo(prediction_hash, example.signature_class)
|
416
|
+
|
417
|
+
demo = DSPy::FewShotExample.new(
|
418
|
+
input: example.input_values,
|
419
|
+
output: output_fields
|
420
|
+
)
|
421
|
+
successful_demos << demo
|
422
|
+
end
|
423
|
+
rescue => e
|
424
|
+
# Continue on errors
|
425
|
+
DSPy.logger.warn("Bootstrap error: #{e.message}") if DSPy.logger
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
# Prepend labeled examples if requested
|
430
|
+
if max_labeled > 0
|
431
|
+
labeled = trainset.take(max_labeled).map do |ex|
|
432
|
+
DSPy::FewShotExample.new(
|
433
|
+
input: ex.input_values,
|
434
|
+
output: ex.expected_values
|
435
|
+
)
|
436
|
+
end
|
437
|
+
successful_demos = labeled + successful_demos
|
438
|
+
end
|
439
|
+
|
440
|
+
successful_demos
|
441
|
+
end
|
442
|
+
|
443
|
+
# Extract only output fields from prediction hash
|
444
|
+
sig do
|
445
|
+
params(
|
446
|
+
prediction_hash: T::Hash[Symbol, T.untyped],
|
447
|
+
signature_class: T.class_of(DSPy::Signature)
|
448
|
+
).returns(T::Hash[Symbol, T.untyped])
|
449
|
+
end
|
450
|
+
def self.extract_output_fields_for_demo(prediction_hash, signature_class)
|
451
|
+
output_field_names = signature_class.output_field_descriptors.keys
|
452
|
+
prediction_hash.slice(*output_field_names)
|
453
|
+
end
|
454
|
+
|
148
455
|
# Evaluate a candidate program on examples with proper error handling
|
149
456
|
sig do
|
150
457
|
params(
|
@@ -404,4 +711,4 @@ module DSPy
|
|
404
711
|
end
|
405
712
|
end
|
406
713
|
end
|
407
|
-
end
|
714
|
+
end
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.rb
CHANGED
@@ -12,6 +12,7 @@ require_relative 'dspy/observability/observation_type'
|
|
12
12
|
require_relative 'dspy/context'
|
13
13
|
require_relative 'dspy/events'
|
14
14
|
require_relative 'dspy/events/types'
|
15
|
+
require_relative 'dspy/reflection_lm'
|
15
16
|
|
16
17
|
module DSPy
|
17
18
|
extend Dry::Configurable
|
@@ -191,12 +192,14 @@ module DSPy
|
|
191
192
|
end
|
192
193
|
end
|
193
194
|
|
195
|
+
require_relative 'dspy/callbacks'
|
194
196
|
require_relative 'dspy/module'
|
195
197
|
require_relative 'dspy/field'
|
196
198
|
require_relative 'dspy/signature'
|
197
199
|
require_relative 'dspy/few_shot_example'
|
198
200
|
require_relative 'dspy/prompt'
|
199
201
|
require_relative 'dspy/example'
|
202
|
+
require_relative 'dspy/datasets'
|
200
203
|
require_relative 'dspy/lm'
|
201
204
|
require_relative 'dspy/image'
|
202
205
|
require_relative 'dspy/prediction'
|
@@ -210,10 +213,9 @@ require_relative 'dspy/evaluate'
|
|
210
213
|
require_relative 'dspy/teleprompt/teleprompter'
|
211
214
|
require_relative 'dspy/teleprompt/utils'
|
212
215
|
require_relative 'dspy/teleprompt/data_handler'
|
216
|
+
require_relative 'dspy/teleprompt/gepa'
|
213
217
|
require_relative 'dspy/propose/grounded_proposer'
|
214
|
-
require_relative 'dspy/teleprompt/simple_optimizer'
|
215
218
|
require_relative 'dspy/teleprompt/mipro_v2'
|
216
|
-
require_relative 'dspy/teleprompt/gepa'
|
217
219
|
require_relative 'dspy/tools'
|
218
220
|
require_relative 'dspy/memory'
|
219
221
|
require_relative 'dspy/storage/program_storage'
|
data/lib/gepa/api.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
require_relative 'core/engine'
|
6
|
+
require_relative 'core/result'
|
7
|
+
|
8
|
+
module GEPA
|
9
|
+
extend T::Sig
|
10
|
+
module_function
|
11
|
+
|
12
|
+
sig do
|
13
|
+
params(
|
14
|
+
seed_candidate: T::Hash[String, String],
|
15
|
+
trainset: T::Array[T.untyped],
|
16
|
+
valset: T::Array[T.untyped],
|
17
|
+
adapter: T.untyped,
|
18
|
+
reflective_proposer: T.untyped,
|
19
|
+
merge_proposer: T.nilable(T.untyped),
|
20
|
+
logger: T.untyped,
|
21
|
+
experiment_tracker: T.untyped,
|
22
|
+
max_metric_calls: Integer,
|
23
|
+
telemetry: T.nilable(T.untyped)
|
24
|
+
).returns(GEPA::Core::Result)
|
25
|
+
end
|
26
|
+
def optimize(
|
27
|
+
seed_candidate:,
|
28
|
+
trainset:,
|
29
|
+
valset:,
|
30
|
+
adapter:,
|
31
|
+
reflective_proposer:,
|
32
|
+
merge_proposer: nil,
|
33
|
+
logger:,
|
34
|
+
experiment_tracker:,
|
35
|
+
max_metric_calls:,
|
36
|
+
telemetry: nil
|
37
|
+
)
|
38
|
+
evaluator = proc { |dataset, candidate| adapter.evaluate(dataset, candidate) }
|
39
|
+
|
40
|
+
engine = GEPA::Core::Engine.new(
|
41
|
+
run_dir: nil,
|
42
|
+
evaluator: evaluator,
|
43
|
+
valset: valset,
|
44
|
+
seed_candidate: seed_candidate,
|
45
|
+
max_metric_calls: max_metric_calls,
|
46
|
+
perfect_score: Float::INFINITY,
|
47
|
+
seed: 0,
|
48
|
+
reflective_proposer: reflective_proposer,
|
49
|
+
merge_proposer: merge_proposer,
|
50
|
+
logger: logger,
|
51
|
+
experiment_tracker: experiment_tracker,
|
52
|
+
telemetry: telemetry || GEPA::Telemetry,
|
53
|
+
track_best_outputs: false,
|
54
|
+
display_progress_bar: false,
|
55
|
+
raise_on_exception: true
|
56
|
+
)
|
57
|
+
|
58
|
+
state = engine.run
|
59
|
+
GEPA::Core::Result.from_state(state)
|
60
|
+
end
|
61
|
+
end
|