dspy 0.9.1 → 0.10.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 306fa376ea024edd2fc75d7bc1af6a42e60770e9566b4dd114414a40c685f2a7
4
- data.tar.gz: 74f321fdd09a5761d17481af846dcd85dc3a372f573f79659c4fcfc761e92a18
3
+ metadata.gz: b721c6c31be4c7a18cb488a7646ff356d658618a5a64ff36f50be9f731d9755a
4
+ data.tar.gz: 2e13ea6bf39e6a89872d604f22c7b197bf50924058824ff2a3483d859f1ba744
5
5
  SHA512:
6
- metadata.gz: 5efb12df9c365114696857067bb4f869ce2329c0977666331846a5f070aca050312992c176fddb60ab701b5d31b017d23182d7a4e813e80cba152d155fc2091c
7
- data.tar.gz: 2fd7f831b6fab1624d2de7702d54d6377a4a96fff4cb0230eb810285828b764033288bc3180e2aa2cf92b1a1026fa6552cdbf851174216a3fc1a651419accc59
6
+ metadata.gz: b6966d789e0ebad1e6ee6c65deaef2dec1d67d483908e7c0847df84998b4f167ad346d20de974af7194db890fcccabe38f3b38ab33dde95b430fdcd3b86a6045
7
+ data.tar.gz: 373e489777b036968ad5152aabc01141212c0409c849d89337075f1b2000de7504b8e2eaccca9755c4e52195b1d05d0667f9e4a8fe54c621cd90ba37b18837b0
data/README.md CHANGED
@@ -67,6 +67,30 @@ Then run:
67
67
  bundle install
68
68
  ```
69
69
 
70
+ #### System Dependencies for Ubuntu/Pop!_OS
71
+
72
+ If you need to compile the `polars-df` dependency from source (used for data processing in evaluations), install these system packages:
73
+
74
+ ```bash
75
+ # Update package list
76
+ sudo apt-get update
77
+
78
+ # Install Ruby development files (if not already installed)
79
+ sudo apt-get install ruby-full ruby-dev
80
+
81
+ # Install essential build tools
82
+ sudo apt-get install build-essential
83
+
84
+ # Install Rust and Cargo (required for polars-df compilation)
85
+ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
86
+ source $HOME/.cargo/env
87
+
88
+ # Install CMake (often needed for Rust projects)
89
+ sudo apt-get install cmake
90
+ ```
91
+
92
+ **Note**: The `polars-df` gem compilation can take 15-20 minutes. Pre-built binaries are available for most platforms, so compilation is only needed if a pre-built binary isn't available for your system.
93
+
70
94
  ### Your First DSPy Program
71
95
 
72
96
  ```ruby
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DSPy
4
+ class Error < StandardError; end
5
+
6
+ class ConfigurationError < Error
7
+ def self.missing_lm(module_name)
8
+ new(<<~MESSAGE)
9
+ No language model configured for #{module_name} module.
10
+
11
+ To fix this, configure a language model either globally:
12
+
13
+ DSPy.configure do |config|
14
+ config.lm = DSPy::LM.new("openai/gpt-4", api_key: ENV["OPENAI_API_KEY"])
15
+ end
16
+
17
+ Or on the module instance:
18
+
19
+ module_instance.configure do |config|
20
+ config.lm = DSPy::LM.new("anthropic/claude-3", api_key: ENV["ANTHROPIC_API_KEY"])
21
+ end
22
+ MESSAGE
23
+ end
24
+ end
25
+ end
data/lib/dspy/example.rb CHANGED
@@ -192,8 +192,28 @@ module DSPy
192
192
  # String representation for debugging
193
193
  sig { returns(String) }
194
194
  def to_s
195
- "DSPy::Example(#{@signature_class.name}) input=#{input_values} expected=#{expected_values}"
195
+ "DSPy::Example(#{@signature_class.name}) input=#{format_hash(input_values)} expected=#{format_hash(expected_values)}"
196
196
  end
197
+
198
+ private
199
+
200
+ # Format hash without escaping Unicode characters
201
+ sig { params(hash: T::Hash[Symbol, T.untyped]).returns(String) }
202
+ def format_hash(hash)
203
+ pairs = hash.map do |k, v|
204
+ value_str = case v
205
+ when String
206
+ # Don't escape Unicode characters
207
+ "\"#{v}\""
208
+ else
209
+ v.inspect
210
+ end
211
+ ":#{k} => #{value_str}"
212
+ end
213
+ "{#{pairs.join(", ")}}"
214
+ end
215
+
216
+ public
197
217
 
198
218
  sig { returns(String) }
199
219
  def inspect
@@ -15,6 +15,9 @@ module DSPy
15
15
  # Prepares base instrumentation payload for prediction-based modules
16
16
  sig { params(signature_class: T.class_of(DSPy::Signature), input_values: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
17
17
  def prepare_base_instrumentation_payload(signature_class, input_values)
18
+ # Validate LM is configured before accessing its properties
19
+ raise DSPy::ConfigurationError.missing_lm(self.class.name) if lm.nil?
20
+
18
21
  {
19
22
  signature_class: signature_class.name,
20
23
  model: lm.model,
@@ -0,0 +1,464 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module DSPy
5
+ class Prediction
6
+ extend T::Sig
7
+ extend T::Generic
8
+ include T::Props
9
+ include T::Props::Serializable
10
+
11
+ # The underlying struct that holds the actual data
12
+ sig { returns(T.untyped) }
13
+ attr_reader :_struct
14
+
15
+ # Schema information for type conversion
16
+ sig { returns(T.nilable(T::Class[T::Struct])) }
17
+ attr_reader :_schema
18
+
19
+ sig do
20
+ params(
21
+ schema: T.nilable(T.any(T::Class[T::Struct], T::Types::Base)),
22
+ attributes: T.untyped
23
+ ).void
24
+ end
25
+ def initialize(schema = nil, **attributes)
26
+ @_schema = extract_struct_class(schema)
27
+
28
+ # Convert attributes based on schema if provided
29
+ converted_attributes = if @_schema
30
+ convert_attributes_with_schema(attributes)
31
+ else
32
+ attributes
33
+ end
34
+
35
+ # Create a dynamic struct to hold the data
36
+ struct_class = create_dynamic_struct(converted_attributes)
37
+ @_struct = struct_class.new(**converted_attributes)
38
+ end
39
+
40
+ # Delegate all method calls to the underlying struct
41
+ sig { params(method: Symbol, args: T.untyped, block: T.untyped).returns(T.untyped) }
42
+ def method_missing(method, *args, &block)
43
+ if @_struct.respond_to?(method)
44
+ @_struct.send(method, *args, &block)
45
+ else
46
+ super
47
+ end
48
+ end
49
+
50
+ sig { params(method: Symbol, include_all: T::Boolean).returns(T::Boolean) }
51
+ def respond_to_missing?(method, include_all = false)
52
+ @_struct.respond_to?(method, include_all) || super
53
+ end
54
+
55
+ sig { returns(T::Hash[Symbol, T.untyped]) }
56
+ def to_h
57
+ @_struct.to_h
58
+ end
59
+
60
+ private
61
+
62
+ sig { params(schema: T.untyped).returns(T.nilable(T::Class[T::Struct])) }
63
+ def extract_struct_class(schema)
64
+ case schema
65
+ when Class
66
+ schema if schema < T::Struct
67
+ when T::Types::Simple
68
+ schema.raw_type if schema.raw_type < T::Struct
69
+ else
70
+ nil
71
+ end
72
+ end
73
+
74
+ sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, T.untyped]) }
75
+ def convert_attributes_with_schema(attributes)
76
+ return attributes unless @_schema
77
+
78
+ converted = {}
79
+
80
+ # Get discriminator mappings for T.any() fields
81
+ discriminator_mappings = detect_discriminator_fields(@_schema)
82
+
83
+ # First, add all the fields from the schema with defaults if not provided
84
+ @_schema.props.each do |field_name, prop_info|
85
+ # Skip if attribute was provided
86
+ next if attributes.key?(field_name)
87
+
88
+ # Apply default value if available
89
+ default_value = prop_info[:default]
90
+ if !default_value.nil?
91
+ if default_value.is_a?(Proc)
92
+ converted[field_name] = default_value.call
93
+ else
94
+ converted[field_name] = default_value
95
+ end
96
+ elsif prop_info[:fully_optional]
97
+ # For optional fields without defaults, set to nil
98
+ converted[field_name] = nil
99
+ end
100
+ end
101
+
102
+ attributes.each do |key, value|
103
+ prop_info = @_schema.props[key]
104
+
105
+ if prop_info && discriminator_mappings[key]
106
+ # This is a T.any() field with a discriminator
107
+ discriminator_field, type_mapping = discriminator_mappings[key]
108
+ discriminator_value = attributes[discriminator_field]
109
+ prop_type = prop_info[:type_object] || prop_info[:type]
110
+
111
+ converted[key] = convert_union_type(value, discriminator_value, type_mapping, prop_type)
112
+ elsif prop_info
113
+ prop_type = prop_info[:type_object] || prop_info[:type]
114
+
115
+ # Handle nil values explicitly
116
+ if value.nil?
117
+ # Check if there's a default value
118
+ default_value = prop_info[:default]
119
+ if !default_value.nil?
120
+ converted[key] = default_value.is_a?(Proc) ? default_value.call : default_value
121
+ else
122
+ converted[key] = nil
123
+ end
124
+ elsif is_enum_type?(prop_type) && value.is_a?(String)
125
+ # Convert string to enum
126
+ converted[key] = prop_type.raw_type.deserialize(value)
127
+ elsif value.is_a?(Hash) && needs_struct_conversion?(prop_type)
128
+ # Regular struct field that needs conversion
129
+ converted[key] = convert_to_struct(value, prop_type)
130
+ elsif value.is_a?(Array) && needs_array_conversion?(prop_type)
131
+ # Array field that might contain structs
132
+ converted[key] = convert_array_elements(value, prop_type)
133
+ else
134
+ converted[key] = value
135
+ end
136
+ else
137
+ converted[key] = value
138
+ end
139
+ end
140
+
141
+ converted
142
+ end
143
+
144
+ sig { params(schema: T::Class[T::Struct]).returns(T::Hash[Symbol, [Symbol, T::Hash[String, T.untyped]]]) }
145
+ def detect_discriminator_fields(schema)
146
+ discriminator_mappings = {}
147
+ props = schema.props.to_a
148
+
149
+ props.each_with_index do |(prop_name, prop_info), index|
150
+ prop_type = prop_info[:type_object] || prop_info[:type]
151
+ next unless is_union_type?(prop_type)
152
+
153
+ # Look for preceding String or Enum field as potential discriminator
154
+ if index > 0
155
+ prev_prop_name, prev_prop_info = props[index - 1]
156
+ prev_prop_type = prev_prop_info[:type_object] || prev_prop_info[:type]
157
+ if prev_prop_type && (is_string_type?(prev_prop_type) || is_enum_type?(prev_prop_type))
158
+ # This String/Enum field might be a discriminator
159
+ type_mapping = build_type_mapping_from_union(prop_type, prev_prop_type)
160
+ discriminator_mappings[prop_name] = [prev_prop_name, type_mapping]
161
+ end
162
+ end
163
+ end
164
+
165
+ discriminator_mappings
166
+ end
167
+
168
+ sig { params(type: T.untyped).returns(T::Boolean) }
169
+ def is_union_type?(type)
170
+ type.is_a?(T::Types::Union) && !is_nilable_type?(type)
171
+ end
172
+
173
+ sig { params(type: T.untyped).returns(T::Boolean) }
174
+ def is_nilable_type?(type)
175
+ type.is_a?(T::Types::Union) && type.types.any? { |t| t == T::Utils.coerce(NilClass) }
176
+ end
177
+
178
+ sig { params(type: T.untyped).returns(T::Boolean) }
179
+ def is_string_type?(type)
180
+ case type
181
+ when T::Types::Simple
182
+ type.raw_type == String
183
+ else
184
+ false
185
+ end
186
+ end
187
+
188
+ sig { params(type: T.untyped).returns(T::Boolean) }
189
+ def is_enum_type?(type)
190
+ return false if type.nil?
191
+ return false unless type.is_a?(T::Types::Simple)
192
+
193
+ begin
194
+ raw_type = type.raw_type
195
+ return false unless raw_type.is_a?(Class)
196
+ result = raw_type < T::Enum
197
+ return result == true # Force conversion to boolean
198
+ rescue StandardError
199
+ return false
200
+ end
201
+ end
202
+
203
+ sig { params(union_type: T::Types::Union, discriminator_type: T.untyped).returns(T::Hash[String, T.untyped]) }
204
+ def build_type_mapping_from_union(union_type, discriminator_type)
205
+ mapping = {}
206
+
207
+ if is_enum_type?(discriminator_type)
208
+ # For enum discriminators, try to map enum values to struct types
209
+ enum_class = discriminator_type.raw_type
210
+ union_type.types.each do |type|
211
+ next if type == T::Utils.coerce(NilClass)
212
+
213
+ if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
214
+ struct_class = type.raw_type
215
+ struct_name = struct_class.name.split("::").last
216
+
217
+ # Try to find matching enum value by name
218
+ enum_class.values.each do |enum_value|
219
+ enum_name = enum_value.instance_variable_get(:@const_name).to_s
220
+ if enum_name == struct_name
221
+ # Exact match
222
+ mapping[enum_value.serialize] = struct_class
223
+ elsif enum_name.downcase == struct_name.downcase
224
+ # Case-insensitive match
225
+ mapping[enum_value.serialize] = struct_class
226
+ end
227
+ end
228
+
229
+ # Also add snake_case mapping as fallback
230
+ discriminator_value = struct_name
231
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
232
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
233
+ .downcase
234
+ mapping[discriminator_value] = struct_class
235
+ end
236
+ end
237
+ else
238
+ # String discriminators use snake_case convention
239
+ union_type.types.each do |type|
240
+ next if type == T::Utils.coerce(NilClass)
241
+
242
+ if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
243
+ struct_class = type.raw_type
244
+ # Convert class name to snake_case for discriminator value
245
+ discriminator_value = struct_class.name
246
+ .split("::").last
247
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
248
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
249
+ .downcase
250
+
251
+ mapping[discriminator_value] = struct_class
252
+ end
253
+ end
254
+ end
255
+
256
+ mapping
257
+ end
258
+
259
+ sig do
260
+ params(
261
+ value: T.untyped,
262
+ discriminator_value: T.untyped,
263
+ type_mapping: T::Hash[String, T.untyped],
264
+ union_type: T.untyped
265
+ ).returns(T.untyped)
266
+ end
267
+ def convert_union_type(value, discriminator_value, type_mapping, union_type)
268
+ return value unless value.is_a?(Hash)
269
+
270
+ # Handle enum discriminators
271
+ discriminator_str = case discriminator_value
272
+ when T::Enum
273
+ discriminator_value.serialize
274
+ when String
275
+ discriminator_value
276
+ else
277
+ return value
278
+ end
279
+
280
+ struct_class = type_mapping[discriminator_str]
281
+ return value unless struct_class
282
+
283
+ # Convert the Hash to the appropriate struct type
284
+ struct_class.new(**value)
285
+ rescue TypeError, ArgumentError
286
+ # If conversion fails, return the original value
287
+ value
288
+ end
289
+
290
+ sig { params(type: T.untyped).returns(T::Boolean) }
291
+ def needs_struct_conversion?(type)
292
+ case type
293
+ when T::Types::Simple
294
+ type.raw_type < T::Struct
295
+ when T::Types::Union
296
+ # Check if any type in the union is a struct
297
+ type.types.any? { |t| needs_struct_conversion?(t) }
298
+ else
299
+ false
300
+ end
301
+ end
302
+
303
+ sig { params(value: T::Hash[Symbol, T.untyped], type: T.untyped).returns(T.untyped) }
304
+ def convert_to_struct(value, type)
305
+ case type
306
+ when T::Types::Simple
307
+ struct_class = type.raw_type
308
+ # Convert nested hash values to structs if needed
309
+ converted_hash = {}
310
+
311
+ # First, apply defaults for missing fields
312
+ struct_class.props.each do |field_name, prop_info|
313
+ next if value.key?(field_name)
314
+
315
+ default_value = prop_info[:default]
316
+ if !default_value.nil?
317
+ converted_hash[field_name] = default_value.is_a?(Proc) ? default_value.call : default_value
318
+ end
319
+ end
320
+
321
+ value.each do |k, v|
322
+ prop_info = struct_class.props[k]
323
+ if prop_info
324
+ prop_type = prop_info[:type_object] || prop_info[:type]
325
+ if v.is_a?(String) && is_enum_type?(prop_type)
326
+ # Convert string to enum
327
+ converted_hash[k] = prop_type.raw_type.deserialize(v)
328
+ elsif v.is_a?(Hash) && needs_struct_conversion?(prop_type)
329
+ converted_hash[k] = convert_to_struct(v, prop_type)
330
+ elsif v.is_a?(Array) && needs_array_conversion?(prop_type)
331
+ converted_hash[k] = convert_array_elements(v, prop_type)
332
+ else
333
+ converted_hash[k] = v
334
+ end
335
+ else
336
+ converted_hash[k] = v
337
+ end
338
+ end
339
+ begin
340
+ struct_class.new(**converted_hash)
341
+ rescue => e
342
+ # Return original value if conversion fails
343
+ value
344
+ end
345
+ when T::Types::Union
346
+ # For unions without discriminator, try each type
347
+ type.types.each do |t|
348
+ next if t == T::Utils.coerce(NilClass)
349
+
350
+ begin
351
+ return convert_to_struct(value, t) if needs_struct_conversion?(t)
352
+ rescue TypeError, ArgumentError
353
+ # Try next type
354
+ end
355
+ end
356
+ value
357
+ else
358
+ value
359
+ end
360
+ rescue TypeError, ArgumentError
361
+ value
362
+ end
363
+
364
+ sig { params(type: T.untyped).returns(T::Boolean) }
365
+ def needs_array_conversion?(type)
366
+ case type
367
+ when T::Types::TypedArray
368
+ needs_struct_conversion?(type.type)
369
+ else
370
+ false
371
+ end
372
+ end
373
+
374
+ sig { params(array: T::Array[T.untyped], type: T.untyped).returns(T::Array[T.untyped]) }
375
+ def convert_array_elements(array, type)
376
+ return array unless type.is_a?(T::Types::TypedArray)
377
+
378
+ element_type = type.type
379
+ # Check if elements need any conversion (structs or enums)
380
+ return array unless needs_struct_conversion?(element_type) || is_enum_type?(element_type)
381
+
382
+ array.map do |element|
383
+ if element.is_a?(Hash)
384
+ # For union types, we need to infer which struct type based on the hash structure
385
+ if is_union_type?(element_type) && !is_nilable_type?(element_type)
386
+ convert_hash_to_union_struct(element, element_type)
387
+ else
388
+ convert_to_struct(element, element_type)
389
+ end
390
+ elsif element.is_a?(String) && is_enum_type?(element_type)
391
+ # Convert string to enum
392
+ element_type.raw_type.deserialize(element)
393
+ else
394
+ element
395
+ end
396
+ end
397
+ end
398
+
399
+ sig { params(hash: T::Hash[Symbol, T.untyped], union_type: T::Types::Union).returns(T.untyped) }
400
+ def convert_hash_to_union_struct(hash, union_type)
401
+ # Try to match the hash structure to one of the union types
402
+ union_type.types.each do |type|
403
+ next if type == T::Utils.coerce(NilClass)
404
+
405
+ if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
406
+ struct_class = type.raw_type
407
+
408
+ # Check if all required fields of this struct are present in the hash
409
+ required_fields = struct_class.props.reject { |_, info| info[:fully_optional] }.keys
410
+ if required_fields.all? { |field| hash.key?(field) }
411
+ begin
412
+ # Need to convert nested values too
413
+ converted_hash = {}
414
+ hash.each do |k, v|
415
+ prop_info = struct_class.props[k]
416
+ if prop_info
417
+ prop_type = prop_info[:type_object] || prop_info[:type]
418
+ if v.is_a?(String) && is_enum_type?(prop_type)
419
+ converted_hash[k] = prop_type.raw_type.deserialize(v)
420
+ elsif v.is_a?(Hash) && needs_struct_conversion?(prop_type)
421
+ converted_hash[k] = convert_to_struct(v, prop_type)
422
+ elsif v.is_a?(Array) && needs_array_conversion?(prop_type)
423
+ converted_hash[k] = convert_array_elements(v, prop_type)
424
+ else
425
+ converted_hash[k] = v
426
+ end
427
+ else
428
+ converted_hash[k] = v
429
+ end
430
+ end
431
+ return struct_class.new(**converted_hash)
432
+ rescue TypeError, ArgumentError
433
+ # This struct didn't match, try the next one
434
+ end
435
+ end
436
+ end
437
+ end
438
+
439
+ # If no struct matched, return the original hash
440
+ hash
441
+ end
442
+
443
+ sig { params(attributes: T::Hash[Symbol, T.untyped]).returns(T::Class[T::Struct]) }
444
+ def create_dynamic_struct(attributes)
445
+ # If we have a schema, include all fields from it in the dynamic struct
446
+ all_fields = if @_schema
447
+ # Merge schema fields with provided attributes
448
+ schema_fields = @_schema.props.keys.to_h { |k| [k, nil] }
449
+ schema_fields.merge(attributes)
450
+ else
451
+ attributes
452
+ end
453
+
454
+ Class.new(T::Struct) do
455
+ const :_prediction_marker, T::Boolean, default: true
456
+
457
+ all_fields.each do |key, value|
458
+ # Use T.untyped for dynamic properties
459
+ const key, T.untyped
460
+ end
461
+ end
462
+ end
463
+ end
464
+ end
@@ -147,6 +147,11 @@ module DSPy
147
147
  }
148
148
  end
149
149
 
150
+ sig { returns(T.nilable(T.class_of(T::Struct))) }
151
+ def input_schema
152
+ @input_struct_class
153
+ end
154
+
150
155
  sig { returns(T::Hash[Symbol, T.untyped]) }
151
156
  def output_json_schema
152
157
  return {} unless @output_struct_class
@@ -169,6 +174,11 @@ module DSPy
169
174
  }
170
175
  end
171
176
 
177
+ sig { returns(T.nilable(T.class_of(T::Struct))) }
178
+ def output_schema
179
+ @output_struct_class
180
+ end
181
+
172
182
  private
173
183
 
174
184
  sig { params(type: T.untyped).returns(T::Hash[Symbol, T.untyped]) }
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.9.1"
4
+ VERSION = "0.10.1"
5
5
  end
data/lib/dspy.rb CHANGED
@@ -5,6 +5,7 @@ require 'dry/logger'
5
5
  require 'securerandom'
6
6
 
7
7
  require_relative 'dspy/version'
8
+ require_relative 'dspy/errors'
8
9
 
9
10
  module DSPy
10
11
  extend Dry::Configurable
@@ -123,6 +124,7 @@ require_relative 'dspy/prompt'
123
124
  require_relative 'dspy/example'
124
125
  require_relative 'dspy/lm'
125
126
  require_relative 'dspy/strategy'
127
+ require_relative 'dspy/prediction'
126
128
  require_relative 'dspy/predict'
127
129
  require_relative 'dspy/chain_of_thought'
128
130
  require_relative 'dspy/re_act'
@@ -144,3 +146,20 @@ require_relative 'dspy/registry/signature_registry'
144
146
  require_relative 'dspy/registry/registry_manager'
145
147
 
146
148
  # LoggerSubscriber will be lazy-initialized when first accessed
149
+
150
+ # Detect potential gem conflicts and warn users
151
+ # DSPy uses the official openai gem, warn if ruby-openai (community version) is detected
152
+ if defined?(OpenAI) && defined?(OpenAI::Client) && !defined?(OpenAI::Internal)
153
+ warn <<~WARNING
154
+ WARNING: ruby-openai gem detected. This may cause conflicts with DSPy's OpenAI integration.
155
+
156
+ DSPy uses the official 'openai' gem. The community 'ruby-openai' gem uses the same
157
+ OpenAI namespace and will cause conflicts.
158
+
159
+ To fix this, remove 'ruby-openai' from your Gemfile and use the official gem instead:
160
+ - Remove: gem 'ruby-openai'
161
+ - Keep: gem 'openai' (official SDK that DSPy uses)
162
+
163
+ The official gem provides better compatibility and is actively maintained by OpenAI.
164
+ WARNING
165
+ 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.9.1
4
+ version: 0.10.1
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-07-16 00:00:00.000000000 Z
11
+ date: 2025-07-20 00:00:00.000000000 Z
11
12
  dependencies:
12
13
  - !ruby/object:Gem::Dependency
13
14
  name: dry-configurable
@@ -161,6 +162,7 @@ files:
161
162
  - lib/dspy.rb
162
163
  - lib/dspy/chain_of_thought.rb
163
164
  - lib/dspy/code_act.rb
165
+ - lib/dspy/errors.rb
164
166
  - lib/dspy/evaluate.rb
165
167
  - lib/dspy/example.rb
166
168
  - lib/dspy/few_shot_example.rb
@@ -196,6 +198,7 @@ files:
196
198
  - lib/dspy/mixins/type_coercion.rb
197
199
  - lib/dspy/module.rb
198
200
  - lib/dspy/predict.rb
201
+ - lib/dspy/prediction.rb
199
202
  - lib/dspy/prompt.rb
200
203
  - lib/dspy/propose/grounded_proposer.rb
201
204
  - lib/dspy/re_act.rb
@@ -225,6 +228,7 @@ homepage: https://github.com/vicentereig/dspy.rb
225
228
  licenses:
226
229
  - MIT
227
230
  metadata: {}
231
+ post_install_message:
228
232
  rdoc_options: []
229
233
  require_paths:
230
234
  - lib
@@ -239,7 +243,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
243
  - !ruby/object:Gem::Version
240
244
  version: '0'
241
245
  requirements: []
242
- rubygems_version: 3.6.5
246
+ rubygems_version: 3.5.22
247
+ signing_key:
243
248
  specification_version: 4
244
249
  summary: Ruby port of DSPy 2.6
245
250
  test_files: []