dspy 0.3.1 → 0.5.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.
@@ -0,0 +1,380 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'sorbet-runtime'
4
+ require_relative '../instrumentation'
5
+ require_relative '../evaluate'
6
+ require_relative '../example'
7
+ require_relative 'data_handler'
8
+
9
+ module DSPy
10
+ module Teleprompt
11
+ # Bootstrap utilities for MIPROv2 optimization
12
+ # Handles few-shot example generation and candidate program evaluation
13
+ module Utils
14
+ extend T::Sig
15
+
16
+ # Configuration for bootstrap operations
17
+ class BootstrapConfig
18
+ extend T::Sig
19
+
20
+ sig { returns(Integer) }
21
+ attr_accessor :max_bootstrapped_examples
22
+
23
+ sig { returns(Integer) }
24
+ attr_accessor :max_labeled_examples
25
+
26
+ sig { returns(Integer) }
27
+ attr_accessor :num_candidate_sets
28
+
29
+ sig { returns(Integer) }
30
+ attr_accessor :max_errors
31
+
32
+ sig { returns(Integer) }
33
+ attr_accessor :num_threads
34
+
35
+ sig { returns(Float) }
36
+ attr_accessor :success_threshold
37
+
38
+ sig { returns(Integer) }
39
+ attr_accessor :minibatch_size
40
+
41
+ sig { void }
42
+ def initialize
43
+ @max_bootstrapped_examples = 4
44
+ @max_labeled_examples = 16
45
+ @num_candidate_sets = 10
46
+ @max_errors = 5
47
+ @num_threads = 1
48
+ @success_threshold = 0.8
49
+ @minibatch_size = 50
50
+ end
51
+ end
52
+
53
+ # Result of bootstrap operation
54
+ class BootstrapResult
55
+ extend T::Sig
56
+
57
+ sig { returns(T::Array[T::Array[DSPy::Example]]) }
58
+ attr_reader :candidate_sets
59
+
60
+ sig { returns(T::Array[DSPy::Example]) }
61
+ attr_reader :successful_examples
62
+
63
+ sig { returns(T::Array[DSPy::Example]) }
64
+ attr_reader :failed_examples
65
+
66
+ sig { returns(T::Hash[Symbol, T.untyped]) }
67
+ attr_reader :statistics
68
+
69
+ sig do
70
+ params(
71
+ candidate_sets: T::Array[T::Array[DSPy::Example]],
72
+ successful_examples: T::Array[DSPy::Example],
73
+ failed_examples: T::Array[DSPy::Example],
74
+ statistics: T::Hash[Symbol, T.untyped]
75
+ ).void
76
+ end
77
+ def initialize(candidate_sets:, successful_examples:, failed_examples:, statistics:)
78
+ @candidate_sets = candidate_sets.freeze
79
+ @successful_examples = successful_examples.freeze
80
+ @failed_examples = failed_examples.freeze
81
+ @statistics = statistics.freeze
82
+ end
83
+
84
+ sig { returns(Float) }
85
+ def success_rate
86
+ total = @successful_examples.size + @failed_examples.size
87
+ return 0.0 if total == 0
88
+ @successful_examples.size.to_f / total.to_f
89
+ end
90
+
91
+ sig { returns(Integer) }
92
+ def total_examples
93
+ @successful_examples.size + @failed_examples.size
94
+ end
95
+ end
96
+
97
+ # Create multiple candidate sets of few-shot examples through bootstrapping
98
+ sig do
99
+ params(
100
+ program: T.untyped,
101
+ trainset: T::Array[T.untyped],
102
+ config: BootstrapConfig,
103
+ metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
104
+ ).returns(BootstrapResult)
105
+ end
106
+ def self.create_n_fewshot_demo_sets(program, trainset, config: BootstrapConfig.new, metric: nil)
107
+ Instrumentation.instrument('dspy.optimization.bootstrap_start', {
108
+ trainset_size: trainset.size,
109
+ max_bootstrapped_examples: config.max_bootstrapped_examples,
110
+ num_candidate_sets: config.num_candidate_sets
111
+ }) do
112
+ # Convert to typed examples if needed
113
+ typed_examples = ensure_typed_examples(trainset)
114
+
115
+ # Generate successful examples through bootstrap
116
+ successful_examples, failed_examples = generate_successful_examples(
117
+ program,
118
+ typed_examples,
119
+ config,
120
+ metric
121
+ )
122
+
123
+ # Create candidate sets from successful examples
124
+ candidate_sets = create_candidate_sets(successful_examples, config)
125
+
126
+ # Gather statistics
127
+ statistics = {
128
+ total_trainset: trainset.size,
129
+ successful_count: successful_examples.size,
130
+ failed_count: failed_examples.size,
131
+ success_rate: successful_examples.size.to_f / (successful_examples.size + failed_examples.size),
132
+ candidate_sets_created: candidate_sets.size,
133
+ average_set_size: candidate_sets.empty? ? 0 : candidate_sets.map(&:size).sum.to_f / candidate_sets.size
134
+ }
135
+
136
+ emit_bootstrap_complete_event(statistics)
137
+
138
+ BootstrapResult.new(
139
+ candidate_sets: candidate_sets,
140
+ successful_examples: successful_examples,
141
+ failed_examples: failed_examples,
142
+ statistics: statistics
143
+ )
144
+ end
145
+ end
146
+
147
+ # Evaluate a candidate program on examples with proper error handling
148
+ sig do
149
+ params(
150
+ program: T.untyped,
151
+ examples: T::Array[T.untyped],
152
+ config: BootstrapConfig,
153
+ metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
154
+ ).returns(DSPy::Evaluate::BatchEvaluationResult)
155
+ end
156
+ def self.eval_candidate_program(program, examples, config: BootstrapConfig.new, metric: nil)
157
+ # Use minibatch evaluation for large datasets
158
+ if examples.size > config.minibatch_size
159
+ eval_candidate_program_minibatch(program, examples, config, metric)
160
+ else
161
+ eval_candidate_program_full(program, examples, config, metric)
162
+ end
163
+ end
164
+
165
+ # Minibatch evaluation for large datasets
166
+ sig do
167
+ params(
168
+ program: T.untyped,
169
+ examples: T::Array[T.untyped],
170
+ config: BootstrapConfig,
171
+ metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
172
+ ).returns(DSPy::Evaluate::BatchEvaluationResult)
173
+ end
174
+ def self.eval_candidate_program_minibatch(program, examples, config, metric)
175
+ Instrumentation.instrument('dspy.optimization.minibatch_evaluation', {
176
+ total_examples: examples.size,
177
+ minibatch_size: config.minibatch_size,
178
+ num_batches: (examples.size.to_f / config.minibatch_size).ceil
179
+ }) do
180
+ # Randomly sample a minibatch for evaluation
181
+ sample_size = [config.minibatch_size, examples.size].min
182
+ sampled_examples = examples.sample(sample_size)
183
+
184
+ eval_candidate_program_full(program, sampled_examples, config, metric)
185
+ end
186
+ end
187
+
188
+ # Full evaluation on all examples
189
+ sig do
190
+ params(
191
+ program: T.untyped,
192
+ examples: T::Array[T.untyped],
193
+ config: BootstrapConfig,
194
+ metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
195
+ ).returns(DSPy::Evaluate::BatchEvaluationResult)
196
+ end
197
+ def self.eval_candidate_program_full(program, examples, config, metric)
198
+ # Create evaluator with proper configuration
199
+ evaluator = DSPy::Evaluate.new(
200
+ program,
201
+ metric: metric || default_metric_for_examples(examples),
202
+ num_threads: config.num_threads,
203
+ max_errors: config.max_errors
204
+ )
205
+
206
+ # Run evaluation
207
+ evaluator.evaluate(examples, display_progress: false)
208
+ end
209
+
210
+ private
211
+
212
+ # Convert various example formats to typed examples
213
+ sig { params(examples: T::Array[T.untyped]).returns(T::Array[DSPy::Example]) }
214
+ def self.ensure_typed_examples(examples)
215
+ return examples if examples.all? { |ex| ex.is_a?(DSPy::Example) }
216
+
217
+ raise ArgumentError, "All examples must be DSPy::Example instances. Legacy format support has been removed. Please convert your examples to use the structured format with :input and :expected keys."
218
+ end
219
+
220
+ # Generate successful examples through program execution
221
+ sig do
222
+ params(
223
+ program: T.untyped,
224
+ examples: T::Array[DSPy::Example],
225
+ config: BootstrapConfig,
226
+ metric: T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))
227
+ ).returns([T::Array[DSPy::Example], T::Array[DSPy::Example]])
228
+ end
229
+ def self.generate_successful_examples(program, examples, config, metric)
230
+ successful = []
231
+ failed = []
232
+ error_count = 0
233
+
234
+ # Use DataHandler for efficient shuffling
235
+ data_handler = DataHandler.new(examples)
236
+ shuffled_examples = data_handler.shuffle(random_state: 42)
237
+
238
+ shuffled_examples.each_with_index do |example, index|
239
+ break if successful.size >= config.max_labeled_examples
240
+ break if error_count >= config.max_errors
241
+
242
+ begin
243
+ # Run program on example input
244
+ prediction = program.call(**example.input_values)
245
+
246
+ # Check if prediction matches expected output
247
+ if metric
248
+ success = metric.call(example, prediction.to_h)
249
+ else
250
+ success = example.matches_prediction?(prediction.to_h)
251
+ end
252
+
253
+ if success
254
+ # Create a new example with the successful prediction as reasoning/context
255
+ successful_example = create_successful_bootstrap_example(example, prediction)
256
+ successful << successful_example
257
+
258
+ emit_bootstrap_example_event(index, true, nil)
259
+ else
260
+ failed << example
261
+ emit_bootstrap_example_event(index, false, "Prediction did not match expected output")
262
+ end
263
+
264
+ rescue => error
265
+ error_count += 1
266
+ failed << example
267
+ emit_bootstrap_example_event(index, false, error.message)
268
+
269
+ # Log error but continue processing
270
+ DSPy.logger.warn("Bootstrap error on example #{index}: #{error.message}")
271
+
272
+ # Stop if too many errors
273
+ if error_count >= config.max_errors
274
+ DSPy.logger.error("Too many bootstrap errors (#{error_count}), stopping early")
275
+ break
276
+ end
277
+ end
278
+ end
279
+
280
+ [successful, failed]
281
+ end
282
+
283
+ # Create candidate sets from successful examples using efficient data handling
284
+ sig do
285
+ params(
286
+ successful_examples: T::Array[DSPy::Example],
287
+ config: BootstrapConfig
288
+ ).returns(T::Array[T::Array[DSPy::Example]])
289
+ end
290
+ def self.create_candidate_sets(successful_examples, config)
291
+ return [] if successful_examples.empty?
292
+
293
+ # Use DataHandler for efficient sampling
294
+ data_handler = DataHandler.new(successful_examples)
295
+ set_size = [config.max_bootstrapped_examples, successful_examples.size].min
296
+
297
+ # Create candidate sets efficiently
298
+ candidate_sets = data_handler.create_candidate_sets(
299
+ config.num_candidate_sets,
300
+ set_size,
301
+ random_state: 42 # For reproducible results
302
+ )
303
+
304
+ candidate_sets
305
+ end
306
+
307
+ # Create a bootstrap example that includes the successful prediction
308
+ sig do
309
+ params(
310
+ original_example: DSPy::Example,
311
+ prediction: T.untyped
312
+ ).returns(DSPy::Example)
313
+ end
314
+ def self.create_successful_bootstrap_example(original_example, prediction)
315
+ # Convert prediction to FewShotExample format
316
+ DSPy::Example.new(
317
+ signature_class: original_example.signature_class,
318
+ input: original_example.input_values,
319
+ expected: prediction.to_h,
320
+ id: "bootstrap_#{original_example.id || SecureRandom.uuid}",
321
+ metadata: {
322
+ source: "bootstrap",
323
+ original_expected: original_example.expected_values,
324
+ bootstrap_timestamp: Time.now.iso8601
325
+ }
326
+ )
327
+ end
328
+
329
+
330
+ # Create default metric for examples
331
+ sig { params(examples: T::Array[T.untyped]).returns(T.nilable(T.proc.params(arg0: T.untyped, arg1: T.untyped).returns(T::Boolean))) }
332
+ def self.default_metric_for_examples(examples)
333
+ if examples.first.is_a?(DSPy::Example)
334
+ proc { |example, prediction| example.matches_prediction?(prediction) }
335
+ else
336
+ nil
337
+ end
338
+ end
339
+
340
+ # Emit bootstrap completion event
341
+ sig { params(statistics: T::Hash[Symbol, T.untyped]).void }
342
+ def self.emit_bootstrap_complete_event(statistics)
343
+ Instrumentation.emit('dspy.optimization.bootstrap_complete', {
344
+ successful_count: statistics[:successful_count],
345
+ failed_count: statistics[:failed_count],
346
+ success_rate: statistics[:success_rate],
347
+ candidate_sets_created: statistics[:candidate_sets_created],
348
+ average_set_size: statistics[:average_set_size]
349
+ })
350
+ end
351
+
352
+ # Emit individual bootstrap example event
353
+ sig { params(index: Integer, success: T::Boolean, error: T.nilable(String)).void }
354
+ def self.emit_bootstrap_example_event(index, success, error)
355
+ Instrumentation.emit('dspy.optimization.bootstrap_example', {
356
+ example_index: index,
357
+ success: success,
358
+ error: error,
359
+ timestamp: Time.now.iso8601
360
+ })
361
+ end
362
+
363
+ # Infer signature class from examples
364
+ sig { params(examples: T::Array[T.untyped]).returns(T.nilable(T.class_of(Signature))) }
365
+ def self.infer_signature_class(examples)
366
+ return nil if examples.empty?
367
+
368
+ first_example = examples.first
369
+
370
+ if first_example.is_a?(DSPy::Example)
371
+ first_example.signature_class
372
+ elsif first_example.is_a?(Hash) && first_example[:signature_class]
373
+ first_example[:signature_class]
374
+ else
375
+ nil
376
+ end
377
+ end
378
+ end
379
+ end
380
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSPy
4
+ VERSION = "0.5.0"
5
+ end
data/lib/dspy.rb CHANGED
@@ -2,26 +2,131 @@
2
2
  require 'sorbet-runtime'
3
3
  require 'dry-configurable'
4
4
  require 'dry/logger'
5
+ require 'securerandom'
6
+
7
+ require_relative 'dspy/version'
5
8
 
6
9
  module DSPy
7
10
  extend Dry::Configurable
11
+
12
+ # Timestamp format options for instrumentation events
13
+ class TimestampFormat < T::Enum
14
+ enums do
15
+ ISO8601 = new('iso8601')
16
+ RFC3339_NANO = new('rfc3339_nano')
17
+ UNIX_NANO = new('unix_nano')
18
+ end
19
+ end
20
+
8
21
  setting :lm
9
22
  setting :logger, default: Dry.Logger(:dspy, formatter: :string)
23
+
24
+ # Nested instrumentation configuration using proper dry-configurable syntax
25
+ setting :instrumentation do
26
+ # Core settings
27
+ setting :enabled, default: false
28
+ setting :subscribers, default: []
29
+ setting :sampling_rate, default: 1.0
30
+ setting :trace_level, default: :standard
31
+ setting :async_processing, default: false
32
+ setting :buffer_size, default: 1000
33
+ setting :flush_interval, default: 30
34
+ setting :error_reporting, default: false
35
+ setting :error_service, default: nil
36
+ setting :sampling_rules, default: {}
37
+ setting :timestamp_format, default: TimestampFormat::ISO8601
38
+
39
+ # Nested correlation ID configuration
40
+ setting :correlation_id do
41
+ setting :enabled, default: false
42
+ setting :header, default: 'X-Correlation-ID'
43
+ setting :generator, default: -> { SecureRandom.uuid }
44
+ end
45
+
46
+ # Nested logger configuration
47
+ setting :logger do
48
+ setting :level, default: :info
49
+ setting :include_payloads, default: true
50
+ setting :correlation_id, default: true
51
+ setting :sampling, default: {}
52
+ setting :sampling_conditions, default: {}
53
+ end
54
+
55
+ # Nested OpenTelemetry configuration
56
+ setting :otel do
57
+ setting :tracer_name, default: 'dspy-ruby'
58
+ setting :service_name, default: 'dspy-application'
59
+ setting :service_version, default: DSPy::VERSION
60
+ setting :endpoint, default: -> { ENV['OTEL_EXPORTER_OTLP_ENDPOINT'] }
61
+ end
62
+
63
+ # Nested New Relic configuration
64
+ setting :newrelic do
65
+ setting :app_name, default: 'DSPy Application'
66
+ setting :license_key, default: -> { ENV['NEW_RELIC_LICENSE_KEY'] }
67
+ setting :custom_attributes, default: {}
68
+ end
69
+
70
+ # Nested Langfuse configuration
71
+ setting :langfuse do
72
+ setting :public_key, default: -> { ENV['LANGFUSE_PUBLIC_KEY'] }
73
+ setting :secret_key, default: -> { ENV['LANGFUSE_SECRET_KEY'] }
74
+ setting :host, default: -> { ENV['LANGFUSE_HOST'] }
75
+ setting :track_tokens, default: true
76
+ setting :track_costs, default: true
77
+ setting :track_prompts, default: true
78
+ end
79
+ end
10
80
 
11
81
  def self.logger
12
82
  config.logger
13
83
  end
84
+
85
+ # Validation methods for instrumentation configuration
86
+ def self.validate_instrumentation!
87
+ config = self.config.instrumentation
88
+
89
+ raise ArgumentError, "Sampling rate must be between 0.0 and 1.0" unless config.sampling_rate.between?(0.0, 1.0)
90
+ raise ArgumentError, "Buffer size must be positive" unless config.buffer_size > 0
91
+ raise ArgumentError, "Flush interval must be positive" unless config.flush_interval > 0
92
+ raise ArgumentError, "Invalid trace level" unless [:minimal, :standard, :detailed].include?(config.trace_level)
93
+ raise ArgumentError, "Invalid timestamp format" unless config.timestamp_format.is_a?(TimestampFormat)
94
+
95
+ if config.enabled && config.subscribers.empty?
96
+ raise ArgumentError, "Must specify at least one subscriber when instrumentation is enabled"
97
+ end
98
+
99
+ # Validate subscribers are valid symbols
100
+ invalid_subscribers = config.subscribers - [:logger, :otel, :newrelic, :langfuse]
101
+ unless invalid_subscribers.empty?
102
+ raise ArgumentError, "Invalid subscribers: #{invalid_subscribers.join(', ')}"
103
+ end
104
+ end
14
105
  end
15
106
 
16
107
  require_relative 'dspy/module'
17
108
  require_relative 'dspy/field'
18
109
  require_relative 'dspy/signature'
110
+ require_relative 'dspy/few_shot_example'
111
+ require_relative 'dspy/prompt'
112
+ require_relative 'dspy/example'
19
113
  require_relative 'dspy/lm'
20
114
  require_relative 'dspy/predict'
21
115
  require_relative 'dspy/chain_of_thought'
22
116
  require_relative 'dspy/re_act'
117
+ require_relative 'dspy/evaluate'
118
+ require_relative 'dspy/teleprompt/teleprompter'
119
+ require_relative 'dspy/teleprompt/utils'
120
+ require_relative 'dspy/teleprompt/data_handler'
121
+ require_relative 'dspy/propose/grounded_proposer'
122
+ require_relative 'dspy/teleprompt/simple_optimizer'
123
+ require_relative 'dspy/teleprompt/mipro_v2'
23
124
  require_relative 'dspy/subscribers/logger_subscriber'
24
125
  require_relative 'dspy/tools'
25
126
  require_relative 'dspy/instrumentation'
127
+ require_relative 'dspy/storage/program_storage'
128
+ require_relative 'dspy/storage/storage_manager'
129
+ require_relative 'dspy/registry/signature_registry'
130
+ require_relative 'dspy/registry/registry_manager'
26
131
 
27
132
  # LoggerSubscriber will be lazy-initialized when first accessed
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.3.1
4
+ version: 0.5.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-06-27 00:00:00.000000000 Z
10
+ date: 2025-07-04 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: dry-configurable
@@ -94,47 +94,47 @@ dependencies:
94
94
  - !ruby/object:Gem::Version
95
95
  version: 1.1.0
96
96
  - !ruby/object:Gem::Dependency
97
- name: ruby_llm
97
+ name: sorbet-runtime
98
98
  requirement: !ruby/object:Gem::Requirement
99
99
  requirements:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
- version: '1.0'
102
+ version: '0.5'
103
103
  type: :runtime
104
104
  prerelease: false
105
105
  version_requirements: !ruby/object:Gem::Requirement
106
106
  requirements:
107
107
  - - "~>"
108
108
  - !ruby/object:Gem::Version
109
- version: '1.0'
109
+ version: '0.5'
110
110
  - !ruby/object:Gem::Dependency
111
- name: sorbet-runtime
111
+ name: sorbet-schema
112
112
  requirement: !ruby/object:Gem::Requirement
113
113
  requirements:
114
114
  - - "~>"
115
115
  - !ruby/object:Gem::Version
116
- version: '0.5'
116
+ version: '0.3'
117
117
  type: :runtime
118
118
  prerelease: false
119
119
  version_requirements: !ruby/object:Gem::Requirement
120
120
  requirements:
121
121
  - - "~>"
122
122
  - !ruby/object:Gem::Version
123
- version: '0.5'
123
+ version: '0.3'
124
124
  - !ruby/object:Gem::Dependency
125
- name: sorbet-schema
125
+ name: polars-df
126
126
  requirement: !ruby/object:Gem::Requirement
127
127
  requirements:
128
128
  - - "~>"
129
129
  - !ruby/object:Gem::Version
130
- version: '0.3'
130
+ version: 0.20.0
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.3'
137
+ version: 0.20.0
138
138
  description: A Ruby implementation of DSPy, a framework for programming with large
139
139
  language models
140
140
  email:
@@ -146,6 +146,9 @@ files:
146
146
  - README.md
147
147
  - lib/dspy.rb
148
148
  - lib/dspy/chain_of_thought.rb
149
+ - lib/dspy/evaluate.rb
150
+ - lib/dspy/example.rb
151
+ - lib/dspy/few_shot_example.rb
149
152
  - lib/dspy/field.rb
150
153
  - lib/dspy/instrumentation.rb
151
154
  - lib/dspy/instrumentation/token_tracker.rb
@@ -154,17 +157,34 @@ files:
154
157
  - lib/dspy/lm/adapter_factory.rb
155
158
  - lib/dspy/lm/adapters/anthropic_adapter.rb
156
159
  - lib/dspy/lm/adapters/openai_adapter.rb
157
- - lib/dspy/lm/adapters/ruby_llm_adapter.rb
158
160
  - lib/dspy/lm/errors.rb
159
161
  - lib/dspy/lm/response.rb
162
+ - lib/dspy/mixins/instrumentation_helpers.rb
163
+ - lib/dspy/mixins/struct_builder.rb
164
+ - lib/dspy/mixins/type_coercion.rb
160
165
  - lib/dspy/module.rb
161
166
  - lib/dspy/predict.rb
167
+ - lib/dspy/prompt.rb
168
+ - lib/dspy/propose/grounded_proposer.rb
162
169
  - lib/dspy/re_act.rb
170
+ - lib/dspy/registry/registry_manager.rb
171
+ - lib/dspy/registry/signature_registry.rb
163
172
  - lib/dspy/schema_adapters.rb
164
173
  - lib/dspy/signature.rb
174
+ - lib/dspy/storage/program_storage.rb
175
+ - lib/dspy/storage/storage_manager.rb
176
+ - lib/dspy/subscribers/langfuse_subscriber.rb
165
177
  - lib/dspy/subscribers/logger_subscriber.rb
178
+ - lib/dspy/subscribers/newrelic_subscriber.rb
179
+ - lib/dspy/subscribers/otel_subscriber.rb
180
+ - lib/dspy/teleprompt/data_handler.rb
181
+ - lib/dspy/teleprompt/mipro_v2.rb
182
+ - lib/dspy/teleprompt/simple_optimizer.rb
183
+ - lib/dspy/teleprompt/teleprompter.rb
184
+ - lib/dspy/teleprompt/utils.rb
166
185
  - lib/dspy/tools.rb
167
186
  - lib/dspy/tools/base.rb
187
+ - lib/dspy/version.rb
168
188
  homepage: https://github.com/vicentereig/dspy.rb
169
189
  licenses:
170
190
  - MIT