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
data/lib/dspy/prompt.rb CHANGED
@@ -5,6 +5,7 @@ require 'sorbet-runtime'
5
5
  require 'sorbet/toon'
6
6
 
7
7
  require_relative 'few_shot_example'
8
+ require_relative 'utils/serialization'
8
9
  require_relative 'schema/sorbet_toon_adapter'
9
10
 
10
11
  module DSPy
@@ -26,24 +27,14 @@ module DSPy
26
27
  sig { returns(T.nilable(String)) }
27
28
  attr_reader :signature_class_name
28
29
 
29
- # Returns the effective schema format
30
- # Precedence: instance variable (if not :json default) > config.lm > :json
31
30
  sig { returns(Symbol) }
32
31
  def schema_format
33
- # If @schema_format was explicitly set to something other than :json, respect it
34
- return @schema_format if @schema_format && @schema_format != :json
35
-
36
- # Otherwise, read from config if available
37
- DSPy.config.lm&.schema_format || @schema_format || :json
32
+ @schema_format || :json
38
33
  end
39
34
 
40
35
  sig { returns(Symbol) }
41
36
  def data_format
42
- return @data_format if @data_format && @data_format != :json
43
-
44
- lm = DSPy.config.lm
45
- lm_format = lm&.respond_to?(:data_format) ? lm.data_format : nil
46
- lm_format || @data_format || :json
37
+ @data_format || :json
47
38
  end
48
39
 
49
40
  sig { returns(T.nilable(T.class_of(Signature))) }
@@ -56,20 +47,20 @@ module DSPy
56
47
  output_schema: T::Hash[Symbol, T.untyped],
57
48
  few_shot_examples: T::Array[FewShotExample],
58
49
  signature_class_name: T.nilable(String),
59
- schema_format: Symbol,
50
+ schema_format: T.nilable(Symbol),
60
51
  signature_class: T.nilable(T.class_of(Signature)),
61
- data_format: Symbol
52
+ data_format: T.nilable(Symbol)
62
53
  ).void
63
54
  end
64
- def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: :json, signature_class: nil, data_format: :json)
55
+ def initialize(instruction:, input_schema:, output_schema:, few_shot_examples: [], signature_class_name: nil, schema_format: nil, signature_class: nil, data_format: nil)
65
56
  @instruction = instruction
66
57
  @few_shot_examples = few_shot_examples.freeze
67
58
  @input_schema = input_schema.freeze
68
59
  @output_schema = output_schema.freeze
69
60
  @signature_class_name = signature_class_name
70
- @schema_format = schema_format
61
+ @schema_format = resolve_schema_format(schema_format)
71
62
  @signature_class = signature_class
72
- @data_format = data_format
63
+ @data_format = resolve_data_format(data_format)
73
64
  end
74
65
 
75
66
  # Immutable update methods for optimization
@@ -107,6 +98,34 @@ module DSPy
107
98
  with_examples(combined_examples)
108
99
  end
109
100
 
101
+ sig { params(new_schema_format: Symbol).returns(Prompt) }
102
+ def with_schema_format(new_schema_format)
103
+ self.class.new(
104
+ instruction: @instruction,
105
+ input_schema: @input_schema,
106
+ output_schema: @output_schema,
107
+ few_shot_examples: @few_shot_examples,
108
+ signature_class_name: @signature_class_name,
109
+ schema_format: new_schema_format,
110
+ signature_class: @signature_class,
111
+ data_format: @data_format
112
+ )
113
+ end
114
+
115
+ sig { params(new_data_format: Symbol).returns(Prompt) }
116
+ def with_data_format(new_data_format)
117
+ self.class.new(
118
+ instruction: @instruction,
119
+ input_schema: @input_schema,
120
+ output_schema: @output_schema,
121
+ few_shot_examples: @few_shot_examples,
122
+ signature_class_name: @signature_class_name,
123
+ schema_format: @schema_format,
124
+ signature_class: @signature_class,
125
+ data_format: new_data_format
126
+ )
127
+ end
128
+
110
129
  # Core prompt rendering methods
111
130
  sig { returns(String) }
112
131
  def render_system_prompt
@@ -223,7 +242,7 @@ module DSPy
223
242
  else
224
243
  sections << "## Input Values"
225
244
  sections << "```json"
226
- sections << JSON.pretty_generate(serialize_for_json(input_values))
245
+ sections << JSON.pretty_generate(DSPy::Utils::Serialization.deep_serialize(input_values))
227
246
  sections << "```"
228
247
  sections << ""
229
248
  sections << "Respond with the corresponding output schema fields wrapped in a ```json ``` block,"
@@ -233,15 +252,6 @@ module DSPy
233
252
  sections.join("\n")
234
253
  end
235
254
 
236
- # Generate messages for LM adapter
237
- sig { params(input_values: T::Hash[Symbol, T.untyped]).returns(T::Array[T::Hash[Symbol, String]]) }
238
- def to_messages(input_values)
239
- [
240
- { role: 'system', content: render_system_prompt },
241
- { role: 'user', content: render_user_prompt(input_values) }
242
- ]
243
- end
244
-
245
255
  # Serialization for persistence and optimization
246
256
  sig { returns(T::Hash[Symbol, T.untyped]) }
247
257
  def to_h
@@ -280,10 +290,6 @@ module DSPy
280
290
  ).returns(Prompt)
281
291
  end
282
292
  def self.from_signature(signature_class, schema_format: nil, data_format: nil)
283
- lm = DSPy.config.lm
284
- schema_format ||= lm&.schema_format || :json
285
- data_format ||= (lm&.respond_to?(:data_format) ? lm.data_format : nil) || :json
286
-
287
293
  new(
288
294
  instruction: signature_class.description || "Complete this task.",
289
295
  input_schema: signature_class.input_json_schema,
@@ -371,51 +377,6 @@ module DSPy
371
377
  "# Please install: gem install sorbet-baml"
372
378
  end
373
379
 
374
- # Recursively serialize complex objects for JSON representation
375
- sig { params(obj: T.untyped).returns(T.untyped) }
376
- def serialize_for_json(obj)
377
- case obj
378
- when T::Struct
379
- # Convert T::Struct to hash using to_h method if available
380
- if obj.respond_to?(:to_h)
381
- serialize_for_json(obj.to_h)
382
- else
383
- # Fallback: serialize using struct properties
384
- serialize_struct_to_hash(obj)
385
- end
386
- when Hash
387
- # Recursively serialize hash values
388
- obj.transform_values { |v| serialize_for_json(v) }
389
- when Array
390
- # Recursively serialize array elements
391
- obj.map { |item| serialize_for_json(item) }
392
- when T::Enum
393
- # Serialize enums to their string representation
394
- obj.serialize
395
- else
396
- # For basic types (String, Integer, Float, Boolean, etc.), return as-is
397
- obj
398
- end
399
- end
400
-
401
- # Fallback method to serialize T::Struct to hash when to_h is not available
402
- sig { params(struct_obj: T::Struct).returns(T::Hash[Symbol, T.untyped]) }
403
- def serialize_struct_to_hash(struct_obj)
404
- result = {}
405
-
406
- # Use struct's props method to get all properties
407
- if struct_obj.class.respond_to?(:props)
408
- struct_obj.class.props.each do |prop_name, _prop_info|
409
- if struct_obj.respond_to?(prop_name)
410
- value = struct_obj.public_send(prop_name)
411
- result[prop_name] = serialize_for_json(value)
412
- end
413
- end
414
- end
415
-
416
- result
417
- end
418
-
419
380
  def toon_data_format_enabled?
420
381
  data_format == :toon && @signature_class
421
382
  end
@@ -506,5 +467,20 @@ module DSPy
506
467
  base = 'value' if base.empty?
507
468
  "example_#{base}"
508
469
  end
470
+
471
+ def resolve_schema_format(schema_format)
472
+ return schema_format unless schema_format.nil?
473
+
474
+ lm = DSPy.respond_to?(:current_lm) ? DSPy.current_lm : DSPy.config.lm
475
+ lm&.schema_format || :json
476
+ end
477
+
478
+ def resolve_data_format(data_format)
479
+ return data_format unless data_format.nil?
480
+
481
+ lm = DSPy.respond_to?(:current_lm) ? DSPy.current_lm : DSPy.config.lm
482
+ lm_format = lm&.respond_to?(:data_format) ? lm.data_format : nil
483
+ lm_format || :json
484
+ end
509
485
  end
510
486
  end
@@ -157,7 +157,7 @@ module DSPy
157
157
 
158
158
  observations += output.observations
159
159
  end
160
- rescue => e
160
+ rescue StandardError => e
161
161
  if verbose
162
162
  puts "Error during observation refinement: #{e.message}. Using observations from past round for summary."
163
163
  end
@@ -160,7 +160,7 @@ module DSPy
160
160
  DSPy.current_lm,
161
161
  verbose: @config.verbose
162
162
  )
163
- rescue => e
163
+ rescue StandardError => e
164
164
  DSPy.logger.warn("Failed to generate dataset summary: #{e.message}")
165
165
  @dataset_summary = nil
166
166
  end
@@ -189,7 +189,7 @@ module DSPy
189
189
  # This is a simplified version - could be enhanced with method_source gem
190
190
  code = "Program: #{klass.name}\nSource: #{file}:#{line}"
191
191
  code
192
- rescue => e
192
+ rescue StandardError => e
193
193
  DSPy.logger.warn("Could not extract program source: #{e.message}")
194
194
  nil
195
195
  end
@@ -506,7 +506,7 @@ module DSPy
506
506
  instruction = result.instruction.strip
507
507
 
508
508
  candidates << instruction if instruction.length > 0
509
- rescue => error
509
+ rescue StandardError => error
510
510
  DSPy.logger.warn("Failed to generate instruction candidate #{i + 1}: #{error.message}")
511
511
  end
512
512
  end