dspy 0.28.2 → 0.29.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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -4
  3. data/lib/dspy/code_act.rb +14 -1
  4. data/lib/dspy/datasets/ade.rb +90 -0
  5. data/lib/dspy/datasets.rb +8 -0
  6. data/lib/dspy/lm.rb +4 -8
  7. data/lib/dspy/mixins/struct_builder.rb +17 -25
  8. data/lib/dspy/module.rb +12 -1
  9. data/lib/dspy/observability/async_span_processor.rb +67 -93
  10. data/lib/dspy/observability.rb +43 -1
  11. data/lib/dspy/predict.rb +10 -0
  12. data/lib/dspy/propose/dataset_summary_generator.rb +36 -3
  13. data/lib/dspy/propose/grounded_proposer.rb +118 -11
  14. data/lib/dspy/re_act.rb +13 -0
  15. data/lib/dspy/reflection_lm.rb +36 -0
  16. data/lib/dspy/teleprompt/gepa.rb +448 -2803
  17. data/lib/dspy/teleprompt/mipro_v2.rb +839 -91
  18. data/lib/dspy/teleprompt/utils.rb +8 -3
  19. data/lib/dspy/version.rb +2 -2
  20. data/lib/dspy.rb +3 -2
  21. data/lib/gepa/api.rb +61 -0
  22. data/lib/gepa/core/engine.rb +226 -0
  23. data/lib/gepa/core/evaluation_batch.rb +26 -0
  24. data/lib/gepa/core/result.rb +92 -0
  25. data/lib/gepa/core/state.rb +231 -0
  26. data/lib/gepa/logging/experiment_tracker.rb +54 -0
  27. data/lib/gepa/logging/logger.rb +57 -0
  28. data/lib/gepa/logging.rb +9 -0
  29. data/lib/gepa/proposer/base.rb +27 -0
  30. data/lib/gepa/proposer/merge_proposer.rb +424 -0
  31. data/lib/gepa/proposer/reflective_mutation/base.rb +48 -0
  32. data/lib/gepa/proposer/reflective_mutation/reflective_mutation.rb +188 -0
  33. data/lib/gepa/strategies/batch_sampler.rb +91 -0
  34. data/lib/gepa/strategies/candidate_selector.rb +97 -0
  35. data/lib/gepa/strategies/component_selector.rb +57 -0
  36. data/lib/gepa/strategies/instruction_proposal.rb +120 -0
  37. data/lib/gepa/telemetry.rb +122 -0
  38. data/lib/gepa/utils/pareto.rb +119 -0
  39. data/lib/gepa.rb +21 -0
  40. metadata +38 -3
  41. data/lib/dspy/teleprompt/simple_optimizer.rb +0 -503
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'set'
5
+ require 'sorbet-runtime'
6
+
7
+ module GEPA
8
+ module Utils
9
+ module Pareto
10
+ extend T::Sig
11
+
12
+ sig { params(value: T.untyped).returns(T.untyped) }
13
+ def self.json_default(value)
14
+ value.is_a?(Hash) ? value.transform_keys(&:to_s) : JSON.parse(value.to_json)
15
+ rescue StandardError
16
+ { value: value.to_s }
17
+ end
18
+
19
+ sig { params(values: T::Array[Float]).returns(Integer) }
20
+ def self.idxmax(values)
21
+ raise ArgumentError, 'values must not be empty' if values.empty?
22
+
23
+ values.each_with_index.max_by { |score, _i| score }&.last || 0
24
+ end
25
+
26
+ sig do
27
+ params(
28
+ program_at_pareto_front_valset: T::Array[T.untyped],
29
+ scores: T.nilable(T::Hash[Integer, Float])
30
+ ).returns(T::Array[T.untyped])
31
+ end
32
+ def self.remove_dominated_programs(program_at_pareto_front_valset, scores: nil)
33
+ normalized_fronts = program_at_pareto_front_valset.map { |front| front.to_a }
34
+
35
+ frequency = Hash.new(0)
36
+ normalized_fronts.each do |front|
37
+ front.each { |program_idx| frequency[program_idx] += 1 }
38
+ end
39
+
40
+ all_programs = frequency.keys
41
+ scores ||= all_programs.to_h { |idx| [idx, 1.0] }
42
+
43
+ sorted_programs = all_programs.sort_by { |idx| scores.fetch(idx, 0.0) }
44
+
45
+ dominated = Set.new
46
+ loop do
47
+ found = false
48
+ sorted_programs.each do |candidate|
49
+ next if dominated.include?(candidate)
50
+ next unless dominated?(candidate, sorted_programs.to_set, dominated, normalized_fronts)
51
+
52
+ dominated.add(candidate)
53
+ found = true
54
+ break
55
+ end
56
+ break unless found
57
+ end
58
+
59
+ dominators = sorted_programs.reject { |idx| dominated.include?(idx) }
60
+ dominators_set = dominators.to_set
61
+
62
+ normalized_fronts.map do |front|
63
+ front.select { |idx| dominators_set.include?(idx) }
64
+ end
65
+ end
66
+
67
+ sig do
68
+ params(
69
+ pareto_front_programs: T::Array[T.untyped],
70
+ train_val_weighted_scores: T::Hash[Integer, Float]
71
+ ).returns(T::Array[Integer])
72
+ end
73
+ def self.find_dominator_programs(pareto_front_programs, train_val_weighted_scores)
74
+ cleaned_frontiers = remove_dominated_programs(pareto_front_programs, scores: train_val_weighted_scores)
75
+ cleaned_frontiers.flat_map(&:to_a).uniq
76
+ end
77
+
78
+ sig do
79
+ params(
80
+ pareto_front_programs: T::Array[T.untyped],
81
+ weighted_scores: T::Hash[Integer, Float],
82
+ rng: Random
83
+ ).returns(Integer)
84
+ end
85
+ def self.select_program_candidate_from_pareto_front(pareto_front_programs, weighted_scores, rng)
86
+ cleaned_frontiers = remove_dominated_programs(pareto_front_programs, scores: weighted_scores)
87
+ frequency = Hash.new(0)
88
+ cleaned_frontiers.each do |front|
89
+ front.each { |idx| frequency[idx] += 1 }
90
+ end
91
+ raise ArgumentError, 'pareto front is empty' if frequency.empty?
92
+
93
+ sampling_list = frequency.flat_map { |idx, freq| [idx] * freq }
94
+ sampling_list[rng.rand(sampling_list.length)]
95
+ end
96
+
97
+ class << self
98
+ extend T::Sig
99
+ private
100
+
101
+ sig do
102
+ params(
103
+ candidate: Integer,
104
+ program_set: Set,
105
+ dominated: Set,
106
+ pareto_fronts: T::Array[T::Array[Integer]]
107
+ ).returns(T::Boolean)
108
+ end
109
+ def dominated?(candidate, program_set, dominated, pareto_fronts)
110
+ candidate_fronts = pareto_fronts.select { |front| front.include?(candidate) }
111
+ candidate_fronts.all? do |front|
112
+ remaining = front.reject { |idx| idx == candidate || dominated.include?(idx) }
113
+ remaining.any? { |other| program_set.include?(other) }
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
data/lib/gepa.rb ADDED
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'gepa/telemetry'
4
+ require_relative 'gepa/logging'
5
+ require_relative 'gepa/utils/pareto'
6
+ require_relative 'gepa/strategies/batch_sampler'
7
+ require_relative 'gepa/strategies/candidate_selector'
8
+ require_relative 'gepa/strategies/component_selector'
9
+ require_relative 'gepa/strategies/instruction_proposal'
10
+ require_relative 'gepa/core/evaluation_batch'
11
+ require_relative 'gepa/core/result'
12
+ require_relative 'gepa/core/state'
13
+ require_relative 'gepa/core/engine'
14
+ require_relative 'gepa/proposer/base'
15
+ require_relative 'gepa/proposer/reflective_mutation/base'
16
+ require_relative 'gepa/proposer/reflective_mutation/reflective_mutation'
17
+ require_relative 'gepa/proposer/merge_proposer'
18
+ require_relative 'gepa/api'
19
+
20
+ module GEPA
21
+ 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.28.2
4
+ version: 0.29.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-10-13 00:00:00.000000000 Z
10
+ date: 2025-10-20 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.29'
54
+ - !ruby/object:Gem::Dependency
55
+ name: concurrent-ruby
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.3'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: openai
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +221,8 @@ files:
207
221
  - lib/dspy/chain_of_thought.rb
208
222
  - lib/dspy/code_act.rb
209
223
  - lib/dspy/context.rb
224
+ - lib/dspy/datasets.rb
225
+ - lib/dspy/datasets/ade.rb
210
226
  - lib/dspy/error_formatter.rb
211
227
  - lib/dspy/errors.rb
212
228
  - lib/dspy/evaluate.rb
@@ -257,6 +273,7 @@ files:
257
273
  - lib/dspy/propose/dataset_summary_generator.rb
258
274
  - lib/dspy/propose/grounded_proposer.rb
259
275
  - lib/dspy/re_act.rb
276
+ - lib/dspy/reflection_lm.rb
260
277
  - lib/dspy/registry/registry_manager.rb
261
278
  - lib/dspy/registry/signature_registry.rb
262
279
  - lib/dspy/schema_adapters.rb
@@ -268,7 +285,6 @@ files:
268
285
  - lib/dspy/teleprompt/data_handler.rb
269
286
  - lib/dspy/teleprompt/gepa.rb
270
287
  - lib/dspy/teleprompt/mipro_v2.rb
271
- - lib/dspy/teleprompt/simple_optimizer.rb
272
288
  - lib/dspy/teleprompt/teleprompter.rb
273
289
  - lib/dspy/teleprompt/utils.rb
274
290
  - lib/dspy/tools.rb
@@ -281,6 +297,25 @@ files:
281
297
  - lib/dspy/type_system/sorbet_json_schema.rb
282
298
  - lib/dspy/utils/serialization.rb
283
299
  - lib/dspy/version.rb
300
+ - lib/gepa.rb
301
+ - lib/gepa/api.rb
302
+ - lib/gepa/core/engine.rb
303
+ - lib/gepa/core/evaluation_batch.rb
304
+ - lib/gepa/core/result.rb
305
+ - lib/gepa/core/state.rb
306
+ - lib/gepa/logging.rb
307
+ - lib/gepa/logging/experiment_tracker.rb
308
+ - lib/gepa/logging/logger.rb
309
+ - lib/gepa/proposer/base.rb
310
+ - lib/gepa/proposer/merge_proposer.rb
311
+ - lib/gepa/proposer/reflective_mutation/base.rb
312
+ - lib/gepa/proposer/reflective_mutation/reflective_mutation.rb
313
+ - lib/gepa/strategies/batch_sampler.rb
314
+ - lib/gepa/strategies/candidate_selector.rb
315
+ - lib/gepa/strategies/component_selector.rb
316
+ - lib/gepa/strategies/instruction_proposal.rb
317
+ - lib/gepa/telemetry.rb
318
+ - lib/gepa/utils/pareto.rb
284
319
  homepage: https://github.com/vicentereig/dspy.rb
285
320
  licenses:
286
321
  - MIT