dspy 0.34.2 → 0.34.4

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -16
  3. data/lib/dspy/chain_of_thought.rb +3 -2
  4. data/lib/dspy/context.rb +70 -21
  5. data/lib/dspy/evals/version.rb +1 -1
  6. data/lib/dspy/evals.rb +42 -31
  7. data/lib/dspy/events.rb +2 -3
  8. data/lib/dspy/example.rb +1 -1
  9. data/lib/dspy/lm/adapter.rb +39 -0
  10. data/lib/dspy/lm/json_strategy.rb +28 -67
  11. data/lib/dspy/lm/message.rb +1 -1
  12. data/lib/dspy/lm/response.rb +2 -2
  13. data/lib/dspy/lm/usage.rb +35 -10
  14. data/lib/dspy/lm.rb +22 -51
  15. data/lib/dspy/mixins/type_coercion.rb +256 -35
  16. data/lib/dspy/module.rb +203 -31
  17. data/lib/dspy/predict.rb +33 -6
  18. data/lib/dspy/prediction.rb +25 -58
  19. data/lib/dspy/prompt.rb +52 -76
  20. data/lib/dspy/propose/dataset_summary_generator.rb +1 -1
  21. data/lib/dspy/propose/grounded_proposer.rb +3 -3
  22. data/lib/dspy/re_act.rb +159 -196
  23. data/lib/dspy/registry/signature_registry.rb +3 -3
  24. data/lib/dspy/ruby_llm/lm/adapters/ruby_llm_adapter.rb +1 -27
  25. data/lib/dspy/schema/sorbet_json_schema.rb +7 -6
  26. data/lib/dspy/schema/version.rb +1 -1
  27. data/lib/dspy/schema_adapters.rb +1 -1
  28. data/lib/dspy/signature.rb +4 -5
  29. data/lib/dspy/storage/program_storage.rb +2 -2
  30. data/lib/dspy/structured_outputs_prompt.rb +4 -4
  31. data/lib/dspy/teleprompt/utils.rb +2 -2
  32. data/lib/dspy/tools/github_cli_toolset.rb +7 -7
  33. data/lib/dspy/tools/text_processing_toolset.rb +2 -2
  34. data/lib/dspy/tools/toolset.rb +1 -1
  35. data/lib/dspy/utils/serialization.rb +2 -6
  36. data/lib/dspy/version.rb +1 -1
  37. data/lib/dspy.rb +50 -5
  38. metadata +7 -26
  39. data/lib/dspy/events/subscriber_mixin.rb +0 -79
  40. data/lib/dspy/events/subscribers.rb +0 -43
  41. data/lib/dspy/memory/embedding_engine.rb +0 -68
  42. data/lib/dspy/memory/in_memory_store.rb +0 -216
  43. data/lib/dspy/memory/local_embedding_engine.rb +0 -244
  44. data/lib/dspy/memory/memory_compactor.rb +0 -298
  45. data/lib/dspy/memory/memory_manager.rb +0 -266
  46. data/lib/dspy/memory/memory_record.rb +0 -163
  47. data/lib/dspy/memory/memory_store.rb +0 -90
  48. data/lib/dspy/memory.rb +0 -30
  49. data/lib/dspy/tools/memory_toolset.rb +0 -117
@@ -198,7 +198,7 @@ module DSPy
198
198
  emit_save_complete_event(saved_program)
199
199
  saved_program
200
200
 
201
- rescue => error
201
+ rescue StandardError => error
202
202
  emit_save_error_event(program_id, error)
203
203
  raise
204
204
  end
@@ -223,7 +223,7 @@ module DSPy
223
223
  emit_load_complete_event(saved_program)
224
224
  saved_program
225
225
 
226
- rescue => error
226
+ rescue StandardError => error
227
227
  emit_load_error_event(program_id, error)
228
228
  nil
229
229
  end
@@ -16,12 +16,12 @@ module DSPy
16
16
  output_schema: T::Hash[Symbol, T.untyped],
17
17
  few_shot_examples: T::Array[T.untyped],
18
18
  signature_class_name: T.nilable(String),
19
- schema_format: Symbol,
19
+ schema_format: T.nilable(Symbol),
20
20
  signature_class: T.nilable(T.class_of(Signature)),
21
- data_format: Symbol
21
+ data_format: T.nilable(Symbol)
22
22
  ).void
23
23
  end
24
- def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil, data_format: :json)
24
+ def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: nil, signature_class: nil, data_format: nil)
25
25
  normalized_examples = few_shot_examples.map do |example|
26
26
  case example
27
27
  when FewShotExample
@@ -80,7 +80,7 @@ module DSPy
80
80
 
81
81
  sections << "## Input Values"
82
82
  sections << "```json"
83
- sections << JSON.pretty_generate(serialize_for_json(input_values))
83
+ sections << JSON.pretty_generate(DSPy::Utils::Serialization.deep_serialize(input_values))
84
84
  sections << "```"
85
85
 
86
86
  sections.join("\n")
@@ -360,7 +360,7 @@ module DSPy
360
360
  )
361
361
  successful_demos << demo
362
362
  end
363
- rescue => e
363
+ rescue StandardError => e
364
364
  # Continue on errors
365
365
  DSPy.logger.warn("Bootstrap error: #{e.message}") if DSPy.logger
366
366
  end
@@ -513,7 +513,7 @@ module DSPy
513
513
  emit_bootstrap_example_event(index, false, "Prediction did not match expected output")
514
514
  end
515
515
 
516
- rescue => error
516
+ rescue StandardError => error
517
517
  error_count += 1
518
518
  failed << example
519
519
  emit_bootstrap_example_event(index, false, error.message)
@@ -105,7 +105,7 @@ module DSPy
105
105
  else
106
106
  "Failed to list issues: #{result[:error]}"
107
107
  end
108
- rescue => e
108
+ rescue StandardError => e
109
109
  "Error listing issues: #{e.message}"
110
110
  end
111
111
 
@@ -140,7 +140,7 @@ module DSPy
140
140
  else
141
141
  "Failed to list pull requests: #{result[:error]}"
142
142
  end
143
- rescue => e
143
+ rescue StandardError => e
144
144
  "Error listing pull requests: #{e.message}"
145
145
  end
146
146
 
@@ -159,7 +159,7 @@ module DSPy
159
159
  else
160
160
  "Failed to get issue: #{result[:error]}"
161
161
  end
162
- rescue => e
162
+ rescue StandardError => e
163
163
  "Error getting issue: #{e.message}"
164
164
  end
165
165
 
@@ -178,7 +178,7 @@ module DSPy
178
178
  else
179
179
  "Failed to get pull request: #{result[:error]}"
180
180
  end
181
- rescue => e
181
+ rescue StandardError => e
182
182
  "Error getting pull request: #{e.message}"
183
183
  end
184
184
 
@@ -214,7 +214,7 @@ module DSPy
214
214
  else
215
215
  "API request failed: #{result[:error]}"
216
216
  end
217
- rescue => e
217
+ rescue StandardError => e
218
218
  "Error making API request: #{e.message}"
219
219
  end
220
220
 
@@ -231,7 +231,7 @@ module DSPy
231
231
  else
232
232
  "Failed to fetch traffic views: #{result[:error]}"
233
233
  end
234
- rescue => e
234
+ rescue StandardError => e
235
235
  "Error fetching traffic views: #{e.message}"
236
236
  end
237
237
 
@@ -248,7 +248,7 @@ module DSPy
248
248
  else
249
249
  "Failed to fetch traffic clones: #{result[:error]}"
250
250
  end
251
- rescue => e
251
+ rescue StandardError => e
252
252
  "Error fetching traffic clones: #{e.message}"
253
253
  end
254
254
 
@@ -52,7 +52,7 @@ module DSPy
52
52
  else
53
53
  result
54
54
  end
55
- rescue => e
55
+ rescue StandardError => e
56
56
  "Error running grep: #{e.message}"
57
57
  end
58
58
 
@@ -92,7 +92,7 @@ module DSPy
92
92
  else
93
93
  result
94
94
  end
95
- rescue => e
95
+ rescue StandardError => e
96
96
  "Error running ripgrep: #{e.message}"
97
97
  end
98
98
 
@@ -187,7 +187,7 @@ module DSPy
187
187
 
188
188
  @instance.send(@method_name, **kwargs)
189
189
  end
190
- rescue => e
190
+ rescue StandardError => e
191
191
  "Error: #{e.message}"
192
192
  end
193
193
  end
@@ -13,6 +13,8 @@ module DSPy
13
13
  when T::Struct
14
14
  # Use the serialize method to convert to a plain hash
15
15
  deep_serialize(obj.serialize)
16
+ when T::Enum
17
+ obj.serialize
16
18
  when Hash
17
19
  # Recursively serialize hash values
18
20
  obj.transform_values { |v| deep_serialize(v) }
@@ -24,12 +26,6 @@ module DSPy
24
26
  obj
25
27
  end
26
28
  end
27
-
28
- # Serializes an object to JSON with proper T::Struct handling
29
- sig { params(obj: T.untyped).returns(String) }
30
- def self.to_json(obj)
31
- deep_serialize(obj).to_json
32
- end
33
29
  end
34
30
  end
35
31
  end
data/lib/dspy/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DSPy
4
- VERSION = "0.34.2"
4
+ VERSION = "0.34.4"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -141,7 +141,7 @@ module DSPy
141
141
  # Events are instant moments in time, not ongoing operations
142
142
  span = DSPy::Observability.start_span(event_name, flattened_attributes)
143
143
  DSPy::Observability.finish_span(span) if span
144
- rescue => e
144
+ rescue StandardError => e
145
145
  # Log error but don't let it break the event system
146
146
  # Use emit_log directly to avoid infinite recursion
147
147
  emit_log('event.span_creation_error', {
@@ -159,13 +159,61 @@ module DSPy
159
159
  if value.is_a?(Hash)
160
160
  flatten_attributes(value, new_key, result)
161
161
  else
162
- result[new_key] = value
162
+ result[new_key] = sanitize_event_attribute_value(value)
163
163
  end
164
164
  end
165
165
 
166
166
  result
167
167
  end
168
168
 
169
+ def self.sanitize_event_attribute_value(value)
170
+ return value if primitive_event_attribute_value?(value)
171
+
172
+ if value.is_a?(Array)
173
+ return value if homogeneous_primitive_array?(value)
174
+ return JSON.generate(value.map { |item| normalize_event_json_value(item) })
175
+ end
176
+
177
+ if value.is_a?(Hash)
178
+ return JSON.generate(normalize_event_json_value(value))
179
+ end
180
+
181
+ if value.respond_to?(:to_h)
182
+ return JSON.generate(normalize_event_json_value(value.to_h))
183
+ end
184
+
185
+ value.respond_to?(:to_json) ? value.to_json : value.to_s
186
+ rescue StandardError
187
+ value.to_s
188
+ end
189
+
190
+ def self.primitive_event_attribute_value?(value)
191
+ value.nil? || value.is_a?(String) || value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
192
+ end
193
+
194
+ def self.homogeneous_primitive_array?(value)
195
+ return true if value.empty?
196
+ return false unless value.all? { |item| primitive_event_attribute_value?(item) }
197
+
198
+ value.map(&:class).uniq.size == 1
199
+ end
200
+
201
+ def self.normalize_event_json_value(value)
202
+ if primitive_event_attribute_value?(value)
203
+ value
204
+ elsif value.is_a?(Array)
205
+ value.map { |item| normalize_event_json_value(item) }
206
+ elsif value.is_a?(Hash)
207
+ value.each_with_object({}) do |(k, v), acc|
208
+ acc[k.to_s] = normalize_event_json_value(v)
209
+ end
210
+ elsif value.respond_to?(:to_h)
211
+ normalize_event_json_value(value.to_h)
212
+ else
213
+ value.to_s
214
+ end
215
+ end
216
+
169
217
  def self.create_logger
170
218
  env = ENV['RACK_ENV'] || ENV['RAILS_ENV'] || 'development'
171
219
  log_output = ENV['DSPY_LOG'] # Allow override
@@ -221,8 +269,6 @@ require_relative 'dspy/lm'
221
269
  require_relative 'dspy/image'
222
270
  require_relative 'dspy/prediction'
223
271
  require_relative 'dspy/predict'
224
- require_relative 'dspy/events/subscribers'
225
- require_relative 'dspy/events/subscriber_mixin'
226
272
  require_relative 'dspy/chain_of_thought'
227
273
  require_relative 'dspy/re_act'
228
274
  require_relative 'dspy/evals'
@@ -248,7 +294,6 @@ begin
248
294
  rescue LoadError
249
295
  end
250
296
  require_relative 'dspy/tools'
251
- require_relative 'dspy/memory'
252
297
 
253
298
  begin
254
299
  require 'dspy/datasets'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dspy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.34.2
4
+ version: 0.34.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vicente Reig Rincón de Arellano
@@ -100,6 +100,9 @@ dependencies:
100
100
  - - "~>"
101
101
  - !ruby/object:Gem::Version
102
102
  version: '0.5'
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ version: 0.5.1
103
106
  type: :runtime
104
107
  prerelease: false
105
108
  version_requirements: !ruby/object:Gem::Requirement
@@ -107,6 +110,9 @@ dependencies:
107
110
  - - "~>"
108
111
  - !ruby/object:Gem::Version
109
112
  version: '0.5'
113
+ - - ">="
114
+ - !ruby/object:Gem::Version
115
+ version: 0.5.1
110
116
  - !ruby/object:Gem::Dependency
111
117
  name: sorbet-toon
112
118
  requirement: !ruby/object:Gem::Requirement
@@ -135,20 +141,6 @@ dependencies:
135
141
  - - "~>"
136
142
  - !ruby/object:Gem::Version
137
143
  version: 1.0.0
138
- - !ruby/object:Gem::Dependency
139
- name: informers
140
- requirement: !ruby/object:Gem::Requirement
141
- requirements:
142
- - - "~>"
143
- - !ruby/object:Gem::Version
144
- version: '1.2'
145
- type: :runtime
146
- prerelease: false
147
- version_requirements: !ruby/object:Gem::Requirement
148
- requirements:
149
- - - "~>"
150
- - !ruby/object:Gem::Version
151
- version: '1.2'
152
144
  description: The Ruby framework for programming with large language models. DSPy.rb
153
145
  brings structured LLM programming to Ruby developers. Instead of wrestling with
154
146
  prompt strings and parsing responses, you define typed signatures using idiomatic
@@ -170,8 +162,6 @@ files:
170
162
  - lib/dspy/evals.rb
171
163
  - lib/dspy/evals/version.rb
172
164
  - lib/dspy/events.rb
173
- - lib/dspy/events/subscriber_mixin.rb
174
- - lib/dspy/events/subscribers.rb
175
165
  - lib/dspy/events/types.rb
176
166
  - lib/dspy/example.rb
177
167
  - lib/dspy/ext/struct_descriptions.rb
@@ -189,14 +179,6 @@ files:
189
179
  - lib/dspy/lm/response.rb
190
180
  - lib/dspy/lm/usage.rb
191
181
  - lib/dspy/lm/vision_models.rb
192
- - lib/dspy/memory.rb
193
- - lib/dspy/memory/embedding_engine.rb
194
- - lib/dspy/memory/in_memory_store.rb
195
- - lib/dspy/memory/local_embedding_engine.rb
196
- - lib/dspy/memory/memory_compactor.rb
197
- - lib/dspy/memory/memory_manager.rb
198
- - lib/dspy/memory/memory_record.rb
199
- - lib/dspy/memory/memory_store.rb
200
182
  - lib/dspy/mixins/instruction_updatable.rb
201
183
  - lib/dspy/mixins/struct_builder.rb
202
184
  - lib/dspy/mixins/type_coercion.rb
@@ -238,7 +220,6 @@ files:
238
220
  - lib/dspy/tools.rb
239
221
  - lib/dspy/tools/base.rb
240
222
  - lib/dspy/tools/github_cli_toolset.rb
241
- - lib/dspy/tools/memory_toolset.rb
242
223
  - lib/dspy/tools/schema.rb
243
224
  - lib/dspy/tools/text_processing_toolset.rb
244
225
  - lib/dspy/tools/toolset.rb
@@ -1,79 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sorbet-runtime'
4
-
5
- module DSPy
6
- module Events
7
- # Mixin for adding class-level event subscriptions
8
- # Provides a clean way to subscribe to events at the class level
9
- # instead of requiring instance-based subscriptions
10
- #
11
- # Usage:
12
- # class MyTracker
13
- # include DSPy::Events::SubscriberMixin
14
- #
15
- # add_subscription('llm.*') do |name, attrs|
16
- # # Handle LLM events globally for this class
17
- # end
18
- # end
19
- module SubscriberMixin
20
- extend T::Sig
21
-
22
- def self.included(base)
23
- base.extend(ClassMethods)
24
- base.class_eval do
25
- @event_subscriptions = []
26
- @subscription_mutex = Mutex.new
27
-
28
- # Initialize subscriptions when the class is first loaded
29
- @subscriptions_initialized = false
30
- end
31
- end
32
-
33
- module ClassMethods
34
- extend T::Sig
35
-
36
- # Add a class-level event subscription
37
- sig { params(pattern: String, block: T.proc.params(arg0: String, arg1: T::Hash[T.any(String, Symbol), T.untyped]).void).returns(String) }
38
- def add_subscription(pattern, &block)
39
- subscription_mutex.synchronize do
40
- subscription_id = DSPy.events.subscribe(pattern, &block)
41
- event_subscriptions << subscription_id
42
- subscription_id
43
- end
44
- end
45
-
46
- # Remove all subscriptions for this class
47
- sig { void }
48
- def unsubscribe_all
49
- subscription_mutex.synchronize do
50
- event_subscriptions.each { |id| DSPy.events.unsubscribe(id) }
51
- event_subscriptions.clear
52
- end
53
- end
54
-
55
- # Get list of active subscription IDs
56
- sig { returns(T::Array[String]) }
57
- def subscriptions
58
- subscription_mutex.synchronize do
59
- event_subscriptions.dup
60
- end
61
- end
62
-
63
- private
64
-
65
- # Thread-safe access to subscriptions array
66
- sig { returns(T::Array[String]) }
67
- def event_subscriptions
68
- @event_subscriptions ||= []
69
- end
70
-
71
- # Thread-safe access to mutex
72
- sig { returns(Mutex) }
73
- def subscription_mutex
74
- @subscription_mutex ||= Mutex.new
75
- end
76
- end
77
- end
78
- end
79
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DSPy
4
- module Events
5
- # Base subscriber class for event-driven patterns
6
- # This provides the foundation for creating custom event subscribers
7
- #
8
- # Example usage:
9
- # class MySubscriber < DSPy::Events::BaseSubscriber
10
- # def subscribe
11
- # add_subscription('llm.*') do |event_name, attributes|
12
- # # Handle LLM events
13
- # end
14
- # end
15
- # end
16
- #
17
- # subscriber = MySubscriber.new
18
- # # subscriber will start receiving events
19
- # subscriber.unsubscribe # Clean up when done
20
- class BaseSubscriber
21
- def initialize
22
- @subscriptions = []
23
- end
24
-
25
- def subscribe
26
- raise NotImplementedError, "Subclasses must implement #subscribe"
27
- end
28
-
29
- def unsubscribe
30
- @subscriptions.each { |id| DSPy.events.unsubscribe(id) }
31
- @subscriptions.clear
32
- end
33
-
34
- protected
35
-
36
- def add_subscription(pattern, &block)
37
- subscription_id = DSPy.events.subscribe(pattern, &block)
38
- @subscriptions << subscription_id
39
- subscription_id
40
- end
41
- end
42
- end
43
- end
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'sorbet-runtime'
4
-
5
- module DSPy
6
- module Memory
7
- # Abstract base class for embedding engines
8
- class EmbeddingEngine
9
- extend T::Sig
10
- extend T::Helpers
11
- abstract!
12
-
13
- # Generate embeddings for a single text
14
- sig { abstract.params(text: String).returns(T::Array[Float]) }
15
- def embed(text); end
16
-
17
- # Generate embeddings for multiple texts (batch processing)
18
- sig { abstract.params(texts: T::Array[String]).returns(T::Array[T::Array[Float]]) }
19
- def embed_batch(texts); end
20
-
21
- # Get the dimension of embeddings produced by this engine
22
- sig { abstract.returns(Integer) }
23
- def embedding_dimension; end
24
-
25
- # Get the model name/identifier
26
- sig { abstract.returns(String) }
27
- def model_name; end
28
-
29
- # Check if the engine is ready to use
30
- sig { returns(T::Boolean) }
31
- def ready?
32
- true
33
- end
34
-
35
- # Get engine statistics
36
- sig { returns(T::Hash[Symbol, T.untyped]) }
37
- def stats
38
- {
39
- model_name: model_name,
40
- embedding_dimension: embedding_dimension,
41
- ready: ready?
42
- }
43
- end
44
-
45
- # Normalize a vector to unit length
46
- sig { params(vector: T::Array[Float]).returns(T::Array[Float]) }
47
- def normalize_vector(vector)
48
- magnitude = Math.sqrt(vector.sum { |x| x * x })
49
- return vector if magnitude == 0.0
50
- vector.map { |x| x / magnitude }
51
- end
52
-
53
- # Calculate cosine similarity between two vectors
54
- sig { params(a: T::Array[Float], b: T::Array[Float]).returns(Float) }
55
- def cosine_similarity(a, b)
56
- return 0.0 if a.empty? || b.empty? || a.size != b.size
57
-
58
- dot_product = a.zip(b).sum { |x, y| x * y }
59
- magnitude_a = Math.sqrt(a.sum { |x| x * x })
60
- magnitude_b = Math.sqrt(b.sum { |x| x * x })
61
-
62
- return 0.0 if magnitude_a == 0.0 || magnitude_b == 0.0
63
-
64
- dot_product / (magnitude_a * magnitude_b)
65
- end
66
- end
67
- end
68
- end