dspy 0.28.2 → 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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -3
  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 +564 -65
  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 +42 -4
  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,14 @@
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
8
+ autorequire:
8
9
  bindir: bin
9
10
  cert_chain: []
10
- date: 2025-10-13 00:00:00.000000000 Z
11
+ date: 2025-10-19 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: dry-configurable
@@ -51,6 +52,20 @@ dependencies:
51
52
  - - "~>"
52
53
  - !ruby/object:Gem::Version
53
54
  version: '2.29'
55
+ - !ruby/object:Gem::Dependency
56
+ name: concurrent-ruby
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.3'
54
69
  - !ruby/object:Gem::Dependency
55
70
  name: openai
56
71
  requirement: !ruby/object:Gem::Requirement
@@ -207,6 +222,8 @@ files:
207
222
  - lib/dspy/chain_of_thought.rb
208
223
  - lib/dspy/code_act.rb
209
224
  - lib/dspy/context.rb
225
+ - lib/dspy/datasets.rb
226
+ - lib/dspy/datasets/ade.rb
210
227
  - lib/dspy/error_formatter.rb
211
228
  - lib/dspy/errors.rb
212
229
  - lib/dspy/evaluate.rb
@@ -257,6 +274,7 @@ files:
257
274
  - lib/dspy/propose/dataset_summary_generator.rb
258
275
  - lib/dspy/propose/grounded_proposer.rb
259
276
  - lib/dspy/re_act.rb
277
+ - lib/dspy/reflection_lm.rb
260
278
  - lib/dspy/registry/registry_manager.rb
261
279
  - lib/dspy/registry/signature_registry.rb
262
280
  - lib/dspy/schema_adapters.rb
@@ -268,7 +286,6 @@ files:
268
286
  - lib/dspy/teleprompt/data_handler.rb
269
287
  - lib/dspy/teleprompt/gepa.rb
270
288
  - lib/dspy/teleprompt/mipro_v2.rb
271
- - lib/dspy/teleprompt/simple_optimizer.rb
272
289
  - lib/dspy/teleprompt/teleprompter.rb
273
290
  - lib/dspy/teleprompt/utils.rb
274
291
  - lib/dspy/tools.rb
@@ -281,10 +298,30 @@ files:
281
298
  - lib/dspy/type_system/sorbet_json_schema.rb
282
299
  - lib/dspy/utils/serialization.rb
283
300
  - lib/dspy/version.rb
301
+ - lib/gepa.rb
302
+ - lib/gepa/api.rb
303
+ - lib/gepa/core/engine.rb
304
+ - lib/gepa/core/evaluation_batch.rb
305
+ - lib/gepa/core/result.rb
306
+ - lib/gepa/core/state.rb
307
+ - lib/gepa/logging.rb
308
+ - lib/gepa/logging/experiment_tracker.rb
309
+ - lib/gepa/logging/logger.rb
310
+ - lib/gepa/proposer/base.rb
311
+ - lib/gepa/proposer/merge_proposer.rb
312
+ - lib/gepa/proposer/reflective_mutation/base.rb
313
+ - lib/gepa/proposer/reflective_mutation/reflective_mutation.rb
314
+ - lib/gepa/strategies/batch_sampler.rb
315
+ - lib/gepa/strategies/candidate_selector.rb
316
+ - lib/gepa/strategies/component_selector.rb
317
+ - lib/gepa/strategies/instruction_proposal.rb
318
+ - lib/gepa/telemetry.rb
319
+ - lib/gepa/utils/pareto.rb
284
320
  homepage: https://github.com/vicentereig/dspy.rb
285
321
  licenses:
286
322
  - MIT
287
323
  metadata: {}
324
+ post_install_message:
288
325
  rdoc_options: []
289
326
  require_paths:
290
327
  - lib
@@ -299,7 +336,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
299
336
  - !ruby/object:Gem::Version
300
337
  version: '0'
301
338
  requirements: []
302
- rubygems_version: 3.6.5
339
+ rubygems_version: 3.0.3.1
340
+ signing_key:
303
341
  specification_version: 4
304
342
  summary: The Ruby framework for programming—rather than prompting—language models.
305
343
  test_files: []