dspy 0.28.0 → 0.28.2
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 +4 -4
- data/lib/dspy/callbacks.rb +222 -0
- data/lib/dspy/chain_of_thought.rb +2 -1
- data/lib/dspy/lm/adapters/gemini/schema_converter.rb +25 -16
- data/lib/dspy/lm/json_strategy.rb +0 -5
- data/lib/dspy/lm.rb +38 -9
- data/lib/dspy/mixins/type_coercion.rb +7 -7
- data/lib/dspy/module.rb +33 -0
- data/lib/dspy/predict.rb +7 -0
- data/lib/dspy/prompt.rb +90 -20
- data/lib/dspy/propose/dataset_summary_generator.rb +177 -0
- data/lib/dspy/propose/grounded_proposer.rb +208 -61
- data/lib/dspy/structured_outputs_prompt.rb +53 -0
- data/lib/dspy/teleprompt/bootstrap_strategy.rb +26 -0
- data/lib/dspy/teleprompt/mipro_v2.rb +81 -56
- data/lib/dspy/teleprompt/simple_optimizer.rb +40 -34
- data/lib/dspy/teleprompt/utils.rb +343 -41
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +1 -0
- metadata +20 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1cc0ac1e2e1dc27f6255b11ca70ff0ad0eb37b5ae50ff38b97ae7d35b7b69b8
|
4
|
+
data.tar.gz: 2399987757b4f037080632e646714e785328fbe3c1fd39138fe750336cdd7710
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea48762186d3de89a005e8eac27f57ca90b294c4ab096ec1c7985d06396216d719ca3a2144406d1f43af472a560670e502329a785d33b8923b5c9c4c0f83dfd1
|
7
|
+
data.tar.gz: 98fee1468f2692a0f7cc87622722f945dbc344fb299e71935ef592bc1d59e68e48e1390208981b6263998627dacb38dc325727cc191f81e1db79fff57c1537a4
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
|
5
|
+
module DSPy
|
6
|
+
# Provides Rails-style callback hooks for DSPy modules
|
7
|
+
#
|
8
|
+
# @example Define callbacks in base class
|
9
|
+
# class DSPy::Module
|
10
|
+
# include DSPy::Callbacks
|
11
|
+
#
|
12
|
+
# create_before_callback :forward
|
13
|
+
# create_after_callback :forward
|
14
|
+
# create_around_callback :forward
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# @example Use callbacks in subclasses
|
18
|
+
# class MyAgent < DSPy::Module
|
19
|
+
# before :setup_context
|
20
|
+
# after :log_metrics
|
21
|
+
# around :manage_memory
|
22
|
+
#
|
23
|
+
# private
|
24
|
+
#
|
25
|
+
# def setup_context
|
26
|
+
# @start_time = Time.now
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# def log_metrics
|
30
|
+
# puts "Duration: #{Time.now - @start_time}"
|
31
|
+
# end
|
32
|
+
#
|
33
|
+
# def manage_memory
|
34
|
+
# load_context
|
35
|
+
# yield
|
36
|
+
# save_context
|
37
|
+
# end
|
38
|
+
# end
|
39
|
+
module Callbacks
|
40
|
+
def self.included(base)
|
41
|
+
base.extend(ClassMethods)
|
42
|
+
end
|
43
|
+
|
44
|
+
module ClassMethods
|
45
|
+
# Creates a before callback hook for the specified method
|
46
|
+
#
|
47
|
+
# @param method_name [Symbol] the method to add callback support to
|
48
|
+
def create_before_callback(method_name)
|
49
|
+
mark_method_has_callbacks(method_name)
|
50
|
+
ensure_callback_method_defined(:before, method_name)
|
51
|
+
wrap_method_with_callbacks(method_name)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Creates an after callback hook for the specified method
|
55
|
+
#
|
56
|
+
# @param method_name [Symbol] the method to add callback support to
|
57
|
+
def create_after_callback(method_name)
|
58
|
+
mark_method_has_callbacks(method_name)
|
59
|
+
ensure_callback_method_defined(:after, method_name)
|
60
|
+
wrap_method_with_callbacks(method_name)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Creates an around callback hook for the specified method
|
64
|
+
#
|
65
|
+
# @param method_name [Symbol] the method to add callback support to
|
66
|
+
def create_around_callback(method_name)
|
67
|
+
mark_method_has_callbacks(method_name)
|
68
|
+
ensure_callback_method_defined(:around, method_name)
|
69
|
+
wrap_method_with_callbacks(method_name)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# Ensures the callback registration method exists
|
75
|
+
def ensure_callback_method_defined(type, target_method_name)
|
76
|
+
return if singleton_class.method_defined?(type)
|
77
|
+
|
78
|
+
define_singleton_method(type) do |callback_method|
|
79
|
+
register_callback(type, target_method_name, callback_method)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Registers a callback for execution
|
84
|
+
def register_callback(type, method_name, callback_method)
|
85
|
+
own_callbacks_for(method_name)[type] ||= []
|
86
|
+
own_callbacks_for(method_name)[type] << callback_method
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns own callbacks (not including parent)
|
90
|
+
def own_callbacks_for(method_name)
|
91
|
+
@callbacks ||= {}
|
92
|
+
@callbacks[method_name] ||= {}
|
93
|
+
end
|
94
|
+
|
95
|
+
# Marks that a method has callback support (even if no callbacks registered yet)
|
96
|
+
def mark_method_has_callbacks(method_name)
|
97
|
+
own_callbacks_for(method_name)
|
98
|
+
end
|
99
|
+
|
100
|
+
# Returns the callback registry for a method
|
101
|
+
# Includes callbacks from parent classes
|
102
|
+
def callbacks_for(method_name)
|
103
|
+
own_callbacks = own_callbacks_for(method_name)
|
104
|
+
|
105
|
+
# Merge parent callbacks if this is a subclass
|
106
|
+
if superclass.respond_to?(:callbacks_for, true)
|
107
|
+
parent_callbacks = superclass.send(:callbacks_for, method_name)
|
108
|
+
|
109
|
+
# Merge each callback type, with own callbacks coming after parent callbacks
|
110
|
+
merged_callbacks = {}
|
111
|
+
[:before, :after, :around].each do |type|
|
112
|
+
parent_list = parent_callbacks[type] || []
|
113
|
+
own_list = own_callbacks[type] || []
|
114
|
+
merged_callbacks[type] = parent_list + own_list if parent_list.any? || own_list.any?
|
115
|
+
end
|
116
|
+
|
117
|
+
merged_callbacks
|
118
|
+
else
|
119
|
+
own_callbacks
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Wraps a method with callback execution logic
|
124
|
+
def wrap_method_with_callbacks(method_name)
|
125
|
+
return if method_wrapped?(method_name)
|
126
|
+
|
127
|
+
# Defer wrapping if method doesn't exist yet
|
128
|
+
return unless method_defined?(method_name)
|
129
|
+
|
130
|
+
# Mark as wrapped BEFORE define_method to prevent infinite recursion
|
131
|
+
mark_method_wrapped(method_name)
|
132
|
+
|
133
|
+
original_method = instance_method(method_name)
|
134
|
+
|
135
|
+
define_method(method_name) do |*args, **kwargs, &block|
|
136
|
+
# Execute before callbacks
|
137
|
+
run_callbacks(:before, method_name)
|
138
|
+
|
139
|
+
# Execute around callbacks or original method
|
140
|
+
result = if self.class.send(:has_around_callbacks?, method_name)
|
141
|
+
execute_with_around_callbacks(method_name, original_method, *args, **kwargs, &block)
|
142
|
+
else
|
143
|
+
original_method.bind(self).call(*args, **kwargs, &block)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Execute after callbacks
|
147
|
+
run_callbacks(:after, method_name)
|
148
|
+
|
149
|
+
result
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Checks if method has around callbacks
|
154
|
+
def has_around_callbacks?(method_name)
|
155
|
+
callbacks_for(method_name)[:around]&.any?
|
156
|
+
end
|
157
|
+
|
158
|
+
# Hook into method_added to wrap methods when they're defined
|
159
|
+
def method_added(method_name)
|
160
|
+
super
|
161
|
+
|
162
|
+
# Check if this method or any parent has callback support (even if no callbacks registered yet)
|
163
|
+
has_callback_support = method_has_callback_support?(method_name)
|
164
|
+
|
165
|
+
return unless has_callback_support
|
166
|
+
return if method_wrapped?(method_name)
|
167
|
+
|
168
|
+
wrap_method_with_callbacks(method_name)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Checks if a method has callback support in this class or parents
|
172
|
+
def method_has_callback_support?(method_name)
|
173
|
+
# Check own callbacks registry
|
174
|
+
return true if @callbacks&.key?(method_name)
|
175
|
+
|
176
|
+
# Check parent class
|
177
|
+
if superclass.respond_to?(:method_has_callback_support?, true)
|
178
|
+
superclass.send(:method_has_callback_support?, method_name)
|
179
|
+
else
|
180
|
+
false
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Marks a method as wrapped
|
185
|
+
def mark_method_wrapped(method_name)
|
186
|
+
@wrapped_methods ||= []
|
187
|
+
@wrapped_methods << method_name
|
188
|
+
end
|
189
|
+
|
190
|
+
# Checks if method is already wrapped
|
191
|
+
def method_wrapped?(method_name)
|
192
|
+
@wrapped_methods&.include?(method_name)
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
private
|
197
|
+
|
198
|
+
# Executes callbacks of a specific type
|
199
|
+
def run_callbacks(type, method_name)
|
200
|
+
callbacks = self.class.send(:callbacks_for, method_name)[type]
|
201
|
+
return unless callbacks
|
202
|
+
|
203
|
+
callbacks.each do |callback_method|
|
204
|
+
send(callback_method)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Executes method with around callbacks
|
209
|
+
def execute_with_around_callbacks(method_name, original_method, *args, **kwargs, &block)
|
210
|
+
callbacks = self.class.send(:callbacks_for, method_name)[:around]
|
211
|
+
|
212
|
+
# Build callback chain from innermost (original method) to outermost
|
213
|
+
chain = callbacks.reverse.inject(
|
214
|
+
-> { original_method.bind(self).call(*args, **kwargs, &block) }
|
215
|
+
) do |inner, callback_method|
|
216
|
+
-> { send(callback_method) { inner.call } }
|
217
|
+
end
|
218
|
+
|
219
|
+
chain.call
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -46,7 +46,8 @@ module DSPy
|
|
46
46
|
input_schema: @signature_class.input_json_schema,
|
47
47
|
output_schema: @signature_class.output_json_schema,
|
48
48
|
few_shot_examples: new_prompt.few_shot_examples,
|
49
|
-
signature_class_name: @signature_class.name
|
49
|
+
signature_class_name: @signature_class.name,
|
50
|
+
schema_format: new_prompt.schema_format
|
50
51
|
)
|
51
52
|
|
52
53
|
instance.instance_variable_set(:@prompt, enhanced_prompt)
|
@@ -11,29 +11,32 @@ module DSPy
|
|
11
11
|
extend T::Sig
|
12
12
|
|
13
13
|
# Models that support structured outputs (JSON + Schema)
|
14
|
-
# Based on official Google documentation
|
14
|
+
# Based on official Google documentation: https://ai.google.dev/gemini-api/docs/models/gemini
|
15
|
+
# Last updated: Oct 2025
|
16
|
+
# Note: Gemini 1.5 series deprecated Oct 2025
|
15
17
|
STRUCTURED_OUTPUT_MODELS = T.let([
|
16
|
-
# Gemini 1.5 series
|
17
|
-
"gemini-1.5-pro",
|
18
|
-
"gemini-1.5-pro-preview-0514",
|
19
|
-
"gemini-1.5-pro-preview-0409",
|
20
|
-
"gemini-1.5-flash", # ✅ Now supports structured outputs
|
21
|
-
"gemini-1.5-flash-8b",
|
22
18
|
# Gemini 2.0 series
|
23
19
|
"gemini-2.0-flash",
|
24
|
-
"gemini-2.0-flash-
|
25
|
-
# Gemini 2.5 series
|
20
|
+
"gemini-2.0-flash-lite",
|
21
|
+
# Gemini 2.5 series (current)
|
26
22
|
"gemini-2.5-pro",
|
27
|
-
"gemini-2.5-flash",
|
28
|
-
"gemini-2.5-flash-lite"
|
23
|
+
"gemini-2.5-flash",
|
24
|
+
"gemini-2.5-flash-lite",
|
25
|
+
"gemini-2.5-flash-image"
|
29
26
|
].freeze, T::Array[String])
|
30
27
|
|
31
|
-
# Models that do not support structured outputs
|
28
|
+
# Models that do not support structured outputs or are deprecated
|
32
29
|
UNSUPPORTED_MODELS = T.let([
|
33
|
-
# Legacy Gemini 1.0 series
|
34
|
-
"gemini-pro",
|
30
|
+
# Legacy Gemini 1.0 series
|
31
|
+
"gemini-pro",
|
35
32
|
"gemini-1.0-pro-002",
|
36
|
-
"gemini-1.0-pro"
|
33
|
+
"gemini-1.0-pro",
|
34
|
+
# Deprecated Gemini 1.5 series (removed Oct 2025)
|
35
|
+
"gemini-1.5-pro",
|
36
|
+
"gemini-1.5-pro-preview-0514",
|
37
|
+
"gemini-1.5-pro-preview-0409",
|
38
|
+
"gemini-1.5-flash",
|
39
|
+
"gemini-1.5-flash-8b"
|
37
40
|
].freeze, T::Array[String])
|
38
41
|
|
39
42
|
sig { params(signature_class: T.class_of(DSPy::Signature)).returns(T::Hash[Symbol, T.untyped]) }
|
@@ -111,7 +114,13 @@ module DSPy
|
|
111
114
|
case property_schema[:type]
|
112
115
|
when "string"
|
113
116
|
result = { type: "string" }
|
114
|
-
|
117
|
+
# Gemini responseJsonSchema doesn't support const, so convert to single-value enum
|
118
|
+
# See: https://ai.google.dev/api/generate-content#FIELDS.response_json_schema
|
119
|
+
if property_schema[:const]
|
120
|
+
result[:enum] = [property_schema[:const]]
|
121
|
+
elsif property_schema[:enum]
|
122
|
+
result[:enum] = property_schema[:enum]
|
123
|
+
end
|
115
124
|
result
|
116
125
|
when "integer"
|
117
126
|
{ type: "integer" }
|
@@ -102,11 +102,6 @@ module DSPy
|
|
102
102
|
type: "tool",
|
103
103
|
name: "json_output"
|
104
104
|
}
|
105
|
-
|
106
|
-
# Update last user message
|
107
|
-
if messages.any? && messages.last[:role] == "user"
|
108
|
-
messages.last[:content] += "\n\nPlease use the json_output tool to provide your response."
|
109
|
-
end
|
110
105
|
end
|
111
106
|
|
112
107
|
# Gemini preparation
|
data/lib/dspy/lm.rb
CHANGED
@@ -26,19 +26,21 @@ require_relative 'lm/json_strategy'
|
|
26
26
|
# Load message builder and message types
|
27
27
|
require_relative 'lm/message'
|
28
28
|
require_relative 'lm/message_builder'
|
29
|
+
require_relative 'structured_outputs_prompt'
|
29
30
|
|
30
31
|
module DSPy
|
31
32
|
class LM
|
32
33
|
extend T::Sig
|
33
|
-
attr_reader :model_id, :api_key, :model, :provider, :adapter
|
34
|
+
attr_reader :model_id, :api_key, :model, :provider, :adapter, :schema_format
|
34
35
|
|
35
|
-
def initialize(model_id, api_key: nil, **options)
|
36
|
+
def initialize(model_id, api_key: nil, schema_format: :json, **options)
|
36
37
|
@model_id = model_id
|
37
38
|
@api_key = api_key
|
38
|
-
|
39
|
+
@schema_format = schema_format
|
40
|
+
|
39
41
|
# Parse provider and model from model_id
|
40
42
|
@provider, @model = parse_model_id(model_id)
|
41
|
-
|
43
|
+
|
42
44
|
# Create appropriate adapter with options
|
43
45
|
@adapter = AdapterFactory.create(model_id, api_key: api_key, **options)
|
44
46
|
end
|
@@ -176,26 +178,53 @@ module DSPy
|
|
176
178
|
|
177
179
|
def build_messages(inference_module, input_values)
|
178
180
|
messages = []
|
179
|
-
|
181
|
+
|
182
|
+
# Determine if structured outputs will be used and wrap prompt if so
|
183
|
+
base_prompt = inference_module.prompt
|
184
|
+
prompt = if will_use_structured_outputs?(inference_module.signature_class)
|
185
|
+
StructuredOutputsPrompt.new(**base_prompt.to_h)
|
186
|
+
else
|
187
|
+
base_prompt
|
188
|
+
end
|
189
|
+
|
180
190
|
# Add system message
|
181
|
-
system_prompt =
|
191
|
+
system_prompt = prompt.render_system_prompt
|
182
192
|
if system_prompt
|
183
193
|
messages << Message.new(
|
184
194
|
role: Message::Role::System,
|
185
195
|
content: system_prompt
|
186
196
|
)
|
187
197
|
end
|
188
|
-
|
198
|
+
|
189
199
|
# Add user message
|
190
|
-
user_prompt =
|
200
|
+
user_prompt = prompt.render_user_prompt(input_values)
|
191
201
|
messages << Message.new(
|
192
202
|
role: Message::Role::User,
|
193
203
|
content: user_prompt
|
194
204
|
)
|
195
|
-
|
205
|
+
|
196
206
|
messages
|
197
207
|
end
|
198
208
|
|
209
|
+
def will_use_structured_outputs?(signature_class)
|
210
|
+
return false unless signature_class
|
211
|
+
|
212
|
+
adapter_class_name = adapter.class.name
|
213
|
+
|
214
|
+
if adapter_class_name.include?('OpenAIAdapter') || adapter_class_name.include?('OllamaAdapter')
|
215
|
+
adapter.instance_variable_get(:@structured_outputs_enabled) &&
|
216
|
+
DSPy::LM::Adapters::OpenAI::SchemaConverter.supports_structured_outputs?(adapter.model)
|
217
|
+
elsif adapter_class_name.include?('GeminiAdapter')
|
218
|
+
adapter.instance_variable_get(:@structured_outputs_enabled) &&
|
219
|
+
DSPy::LM::Adapters::Gemini::SchemaConverter.supports_structured_outputs?(adapter.model)
|
220
|
+
elsif adapter_class_name.include?('AnthropicAdapter')
|
221
|
+
structured_outputs_enabled = adapter.instance_variable_get(:@structured_outputs_enabled)
|
222
|
+
structured_outputs_enabled.nil? ? true : structured_outputs_enabled
|
223
|
+
else
|
224
|
+
false
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
199
228
|
def parse_response(response, input_values, signature_class)
|
200
229
|
# Try to parse the response as JSON
|
201
230
|
content = response.content
|
@@ -208,26 +208,26 @@ module DSPy
|
|
208
208
|
sig { params(value: T.untyped, union_type: T.untyped).returns(T.untyped) }
|
209
209
|
def coerce_union_value(value, union_type)
|
210
210
|
return value unless value.is_a?(Hash)
|
211
|
-
|
211
|
+
|
212
212
|
# Check for _type discriminator field
|
213
213
|
type_name = value[:_type] || value["_type"]
|
214
214
|
return value unless type_name
|
215
|
-
|
215
|
+
|
216
216
|
# Find matching struct type in the union
|
217
217
|
union_type.types.each do |type|
|
218
218
|
next if type == T::Utils.coerce(NilClass)
|
219
|
-
|
219
|
+
|
220
220
|
if type.is_a?(T::Types::Simple) && type.raw_type < T::Struct
|
221
221
|
struct_name = type.raw_type.name.split('::').last
|
222
222
|
if struct_name == type_name
|
223
223
|
# Convert string keys to symbols and remove _type
|
224
224
|
symbolized_hash = value.transform_keys(&:to_sym)
|
225
225
|
symbolized_hash.delete(:_type)
|
226
|
-
|
226
|
+
|
227
227
|
# Coerce struct field values based on their types
|
228
228
|
struct_class = type.raw_type
|
229
229
|
struct_props = struct_class.props
|
230
|
-
|
230
|
+
|
231
231
|
# ONLY include fields that exist in the struct
|
232
232
|
coerced_hash = {}
|
233
233
|
struct_props.each_key do |key|
|
@@ -236,13 +236,13 @@ module DSPy
|
|
236
236
|
coerced_hash[key] = coerce_value_to_type(symbolized_hash[key], prop_type)
|
237
237
|
end
|
238
238
|
end
|
239
|
-
|
239
|
+
|
240
240
|
# Create the struct instance with coerced values
|
241
241
|
return struct_class.new(**coerced_hash)
|
242
242
|
end
|
243
243
|
end
|
244
244
|
end
|
245
|
-
|
245
|
+
|
246
246
|
# If no matching type found, return original value
|
247
247
|
value
|
248
248
|
rescue ArgumentError => e
|
data/lib/dspy/module.rb
CHANGED
@@ -3,16 +3,23 @@
|
|
3
3
|
require 'sorbet-runtime'
|
4
4
|
require 'dry-configurable'
|
5
5
|
require_relative 'context'
|
6
|
+
require_relative 'callbacks'
|
6
7
|
|
7
8
|
module DSPy
|
8
9
|
class Module
|
9
10
|
extend T::Sig
|
10
11
|
extend T::Generic
|
11
12
|
include Dry::Configurable
|
13
|
+
include DSPy::Callbacks
|
12
14
|
|
13
15
|
# Per-instance LM configuration
|
14
16
|
setting :lm, default: nil
|
15
17
|
|
18
|
+
# Define callback hooks for forward method
|
19
|
+
create_before_callback :forward
|
20
|
+
create_after_callback :forward
|
21
|
+
create_around_callback :forward
|
22
|
+
|
16
23
|
# The main forward method that users will call is generic and type parameterized
|
17
24
|
sig do
|
18
25
|
type_parameters(:I, :O)
|
@@ -72,5 +79,31 @@ module DSPy
|
|
72
79
|
def lm
|
73
80
|
config.lm || DSPy.current_lm
|
74
81
|
end
|
82
|
+
|
83
|
+
# Save the module state to a JSON file
|
84
|
+
# Lightweight serialization for intermediate optimization trials
|
85
|
+
#
|
86
|
+
# @param path [String] Path to save the module state (JSON format)
|
87
|
+
sig { params(path: String).void }
|
88
|
+
def save(path)
|
89
|
+
require 'json'
|
90
|
+
require 'fileutils'
|
91
|
+
|
92
|
+
# Ensure parent directory exists
|
93
|
+
dir = File.dirname(path)
|
94
|
+
FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
|
95
|
+
|
96
|
+
# Serialize module to JSON
|
97
|
+
File.write(path, JSON.pretty_generate(to_h))
|
98
|
+
end
|
99
|
+
|
100
|
+
# Default serialization method - subclasses can override
|
101
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
102
|
+
def to_h
|
103
|
+
{
|
104
|
+
class_name: self.class.name,
|
105
|
+
state: {}
|
106
|
+
}
|
107
|
+
end
|
75
108
|
end
|
76
109
|
end
|
data/lib/dspy/predict.rb
CHANGED
@@ -53,11 +53,18 @@ module DSPy
|
|
53
53
|
sig { returns(Prompt) }
|
54
54
|
attr_reader :prompt
|
55
55
|
|
56
|
+
# Mutable demos attribute for MIPROv2 compatibility
|
57
|
+
sig { returns(T.nilable(T::Array[FewShotExample])) }
|
58
|
+
attr_accessor :demos
|
59
|
+
|
56
60
|
sig { params(signature_class: T.class_of(Signature)).void }
|
57
61
|
def initialize(signature_class)
|
58
62
|
super()
|
59
63
|
@signature_class = signature_class
|
64
|
+
|
65
|
+
# Prompt will read schema_format from config automatically
|
60
66
|
@prompt = Prompt.from_signature(signature_class)
|
67
|
+
@demos = nil
|
61
68
|
end
|
62
69
|
|
63
70
|
# Reconstruct program from serialized hash
|