dspy 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +69 -412
- data/lib/dspy/chain_of_thought.rb +57 -0
- data/lib/dspy/evaluate.rb +554 -0
- data/lib/dspy/example.rb +203 -0
- data/lib/dspy/few_shot_example.rb +81 -0
- data/lib/dspy/instrumentation.rb +103 -1
- data/lib/dspy/lm/adapter_factory.rb +6 -8
- data/lib/dspy/lm.rb +5 -7
- data/lib/dspy/predict.rb +32 -34
- data/lib/dspy/prompt.rb +222 -0
- data/lib/dspy/propose/grounded_proposer.rb +560 -0
- data/lib/dspy/registry/registry_manager.rb +504 -0
- data/lib/dspy/registry/signature_registry.rb +725 -0
- data/lib/dspy/storage/program_storage.rb +442 -0
- data/lib/dspy/storage/storage_manager.rb +331 -0
- data/lib/dspy/subscribers/langfuse_subscriber.rb +669 -0
- data/lib/dspy/subscribers/logger_subscriber.rb +195 -57
- data/lib/dspy/subscribers/newrelic_subscriber.rb +686 -0
- data/lib/dspy/subscribers/otel_subscriber.rb +538 -0
- data/lib/dspy/teleprompt/data_handler.rb +107 -0
- data/lib/dspy/teleprompt/mipro_v2.rb +790 -0
- data/lib/dspy/teleprompt/simple_optimizer.rb +497 -0
- data/lib/dspy/teleprompt/teleprompter.rb +336 -0
- data/lib/dspy/teleprompt/utils.rb +380 -0
- data/lib/dspy/version.rb +5 -0
- data/lib/dspy.rb +19 -17
- metadata +29 -12
- data/lib/dspy/lm/adapters/ruby_llm_adapter.rb +0 -81
@@ -7,14 +7,20 @@ module DSPy
|
|
7
7
|
class LoggerSubscriber
|
8
8
|
extend T::Sig
|
9
9
|
|
10
|
-
sig { params(logger: T.nilable(Logger)).void }
|
10
|
+
sig { params(logger: T.nilable(T.any(Logger, Dry::Logger::Dispatcher))).void }
|
11
11
|
def initialize(logger: nil)
|
12
|
-
@
|
12
|
+
@explicit_logger = T.let(logger, T.nilable(T.any(Logger, Dry::Logger::Dispatcher)))
|
13
13
|
setup_event_subscriptions
|
14
14
|
end
|
15
15
|
|
16
16
|
private
|
17
17
|
|
18
|
+
# Always use the current configured logger or the explicit one
|
19
|
+
sig { returns(T.any(Logger, Dry::Logger::Dispatcher)) }
|
20
|
+
def logger
|
21
|
+
@explicit_logger || DSPy.config.logger
|
22
|
+
end
|
23
|
+
|
18
24
|
sig { void }
|
19
25
|
def setup_event_subscriptions
|
20
26
|
# Subscribe to DSPy instrumentation events
|
@@ -41,6 +47,27 @@ module DSPy
|
|
41
47
|
DSPy::Instrumentation.subscribe('dspy.react.tool_call') do |event|
|
42
48
|
log_react_tool_call(event)
|
43
49
|
end
|
50
|
+
|
51
|
+
# Subscribe to optimization events
|
52
|
+
DSPy::Instrumentation.subscribe('dspy.optimization.start') do |event|
|
53
|
+
log_optimization_start(event)
|
54
|
+
end
|
55
|
+
|
56
|
+
DSPy::Instrumentation.subscribe('dspy.optimization.complete') do |event|
|
57
|
+
log_optimization_complete(event)
|
58
|
+
end
|
59
|
+
|
60
|
+
DSPy::Instrumentation.subscribe('dspy.optimization.trial_start') do |event|
|
61
|
+
log_optimization_trial_start(event)
|
62
|
+
end
|
63
|
+
|
64
|
+
DSPy::Instrumentation.subscribe('dspy.optimization.trial_complete') do |event|
|
65
|
+
log_optimization_trial_complete(event)
|
66
|
+
end
|
67
|
+
|
68
|
+
DSPy::Instrumentation.subscribe('dspy.optimization.error') do |event|
|
69
|
+
log_optimization_error(event)
|
70
|
+
end
|
44
71
|
end
|
45
72
|
|
46
73
|
# Callback methods for different event types
|
@@ -82,18 +109,19 @@ module DSPy
|
|
82
109
|
model = payload[:gen_ai_request_model] || payload[:model]
|
83
110
|
duration = payload[:duration_ms]&.round(2)
|
84
111
|
status = payload[:status]
|
85
|
-
tokens =
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
112
|
+
tokens = payload[:tokens_total]
|
113
|
+
|
114
|
+
log_parts = [
|
115
|
+
"event=lm_request",
|
116
|
+
"provider=#{provider}",
|
117
|
+
"model=#{model}",
|
118
|
+
"status=#{status}",
|
119
|
+
"duration_ms=#{duration}"
|
120
|
+
]
|
121
|
+
log_parts << "tokens=#{tokens}" if tokens
|
122
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
123
|
+
|
124
|
+
logger.info(log_parts.join(' '))
|
97
125
|
end
|
98
126
|
|
99
127
|
sig { params(event: T.untyped).void }
|
@@ -104,13 +132,16 @@ module DSPy
|
|
104
132
|
status = payload[:status]
|
105
133
|
input_size = payload[:input_size]
|
106
134
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
135
|
+
log_parts = [
|
136
|
+
"event=prediction",
|
137
|
+
"signature=#{signature}",
|
138
|
+
"status=#{status}",
|
139
|
+
"duration_ms=#{duration}"
|
140
|
+
]
|
141
|
+
log_parts << "input_size=#{input_size}" if input_size
|
142
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
143
|
+
|
144
|
+
logger.info(log_parts.join(' '))
|
114
145
|
end
|
115
146
|
|
116
147
|
sig { params(event: T.untyped).void }
|
@@ -122,14 +153,17 @@ module DSPy
|
|
122
153
|
reasoning_steps = payload[:reasoning_steps]
|
123
154
|
reasoning_length = payload[:reasoning_length]
|
124
155
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
156
|
+
log_parts = [
|
157
|
+
"event=chain_of_thought",
|
158
|
+
"signature=#{signature}",
|
159
|
+
"status=#{status}",
|
160
|
+
"duration_ms=#{duration}"
|
161
|
+
]
|
162
|
+
log_parts << "reasoning_steps=#{reasoning_steps}" if reasoning_steps
|
163
|
+
log_parts << "reasoning_length=#{reasoning_length}" if reasoning_length
|
164
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
165
|
+
|
166
|
+
logger.info(log_parts.join(' '))
|
133
167
|
end
|
134
168
|
|
135
169
|
sig { params(event: T.untyped).void }
|
@@ -142,20 +176,18 @@ module DSPy
|
|
142
176
|
tools_used = payload[:tools_used]
|
143
177
|
final_answer = payload[:final_answer]
|
144
178
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@logger.error(" Error: #{payload[:error_message]}")
|
158
|
-
end
|
179
|
+
log_parts = [
|
180
|
+
"event=react",
|
181
|
+
"signature=#{signature}",
|
182
|
+
"status=#{status}",
|
183
|
+
"duration_ms=#{duration}"
|
184
|
+
]
|
185
|
+
log_parts << "iterations=#{iteration_count}" if iteration_count
|
186
|
+
log_parts << "tools_used=\"#{tools_used.join(',')}\"" if tools_used&.any?
|
187
|
+
log_parts << "final_answer=\"#{final_answer&.truncate(100)}\"" if final_answer
|
188
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
189
|
+
|
190
|
+
logger.info(log_parts.join(' '))
|
159
191
|
end
|
160
192
|
|
161
193
|
sig { params(event: T.untyped).void }
|
@@ -167,14 +199,17 @@ module DSPy
|
|
167
199
|
duration = payload[:duration_ms]&.round(2)
|
168
200
|
status = payload[:status]
|
169
201
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
202
|
+
log_parts = [
|
203
|
+
"event=react_iteration",
|
204
|
+
"iteration=#{iteration}",
|
205
|
+
"status=#{status}",
|
206
|
+
"duration_ms=#{duration}"
|
207
|
+
]
|
208
|
+
log_parts << "thought=\"#{thought&.truncate(100)}\"" if thought
|
209
|
+
log_parts << "action=\"#{action}\"" if action
|
210
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
211
|
+
|
212
|
+
logger.info(log_parts.join(' '))
|
178
213
|
end
|
179
214
|
|
180
215
|
sig { params(event: T.untyped).void }
|
@@ -185,12 +220,115 @@ module DSPy
|
|
185
220
|
duration = payload[:duration_ms]&.round(2)
|
186
221
|
status = payload[:status]
|
187
222
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
223
|
+
log_parts = [
|
224
|
+
"event=tool_call",
|
225
|
+
"tool=#{tool_name}",
|
226
|
+
"iteration=#{iteration}",
|
227
|
+
"status=#{status}",
|
228
|
+
"duration_ms=#{duration}"
|
229
|
+
]
|
230
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
231
|
+
|
232
|
+
logger.info(log_parts.join(' '))
|
233
|
+
end
|
234
|
+
|
235
|
+
# Optimization event logging methods
|
236
|
+
sig { params(event: T.untyped).void }
|
237
|
+
def log_optimization_start(event)
|
238
|
+
payload = event.payload
|
239
|
+
optimization_id = payload[:optimization_id]
|
240
|
+
optimizer = payload[:optimizer]
|
241
|
+
trainset_size = payload[:trainset_size]
|
242
|
+
valset_size = payload[:valset_size]
|
243
|
+
|
244
|
+
log_parts = [
|
245
|
+
"event=optimization_start",
|
246
|
+
"optimization_id=#{optimization_id}",
|
247
|
+
"optimizer=#{optimizer}",
|
248
|
+
"trainset_size=#{trainset_size}"
|
249
|
+
]
|
250
|
+
log_parts << "valset_size=#{valset_size}" if valset_size
|
251
|
+
|
252
|
+
logger.info(log_parts.join(' '))
|
253
|
+
end
|
254
|
+
|
255
|
+
sig { params(event: T.untyped).void }
|
256
|
+
def log_optimization_complete(event)
|
257
|
+
payload = event.payload
|
258
|
+
optimization_id = payload[:optimization_id]
|
259
|
+
optimizer = payload[:optimizer]
|
260
|
+
duration = payload[:duration_ms]&.round(2)
|
261
|
+
best_score = payload[:best_score]
|
262
|
+
trials_count = payload[:trials_count]
|
263
|
+
|
264
|
+
log_parts = [
|
265
|
+
"event=optimization_complete",
|
266
|
+
"optimization_id=#{optimization_id}",
|
267
|
+
"optimizer=#{optimizer}",
|
268
|
+
"duration_ms=#{duration}"
|
269
|
+
]
|
270
|
+
log_parts << "best_score=#{best_score}" if best_score
|
271
|
+
log_parts << "trials_count=#{trials_count}" if trials_count
|
272
|
+
|
273
|
+
logger.info(log_parts.join(' '))
|
274
|
+
end
|
275
|
+
|
276
|
+
sig { params(event: T.untyped).void }
|
277
|
+
def log_optimization_trial_start(event)
|
278
|
+
payload = event.payload
|
279
|
+
optimization_id = payload[:optimization_id]
|
280
|
+
trial_number = payload[:trial_number]
|
281
|
+
instruction = payload[:instruction]
|
282
|
+
|
283
|
+
log_parts = [
|
284
|
+
"event=optimization_trial_start",
|
285
|
+
"optimization_id=#{optimization_id}",
|
286
|
+
"trial_number=#{trial_number}"
|
287
|
+
]
|
288
|
+
log_parts << "instruction=\"#{instruction&.slice(0, 100)}\"" if instruction
|
289
|
+
|
290
|
+
logger.info(log_parts.join(' '))
|
291
|
+
end
|
292
|
+
|
293
|
+
sig { params(event: T.untyped).void }
|
294
|
+
def log_optimization_trial_complete(event)
|
295
|
+
payload = event.payload
|
296
|
+
optimization_id = payload[:optimization_id]
|
297
|
+
trial_number = payload[:trial_number]
|
298
|
+
duration = payload[:duration_ms]&.round(2)
|
299
|
+
score = payload[:score]
|
300
|
+
status = payload[:status]
|
301
|
+
|
302
|
+
log_parts = [
|
303
|
+
"event=optimization_trial_complete",
|
304
|
+
"optimization_id=#{optimization_id}",
|
305
|
+
"trial_number=#{trial_number}",
|
306
|
+
"status=#{status}",
|
307
|
+
"duration_ms=#{duration}"
|
308
|
+
]
|
309
|
+
log_parts << "score=#{score}" if score
|
310
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
311
|
+
|
312
|
+
logger.info(log_parts.join(' '))
|
313
|
+
end
|
314
|
+
|
315
|
+
sig { params(event: T.untyped).void }
|
316
|
+
def log_optimization_error(event)
|
317
|
+
payload = event.payload
|
318
|
+
optimization_id = payload[:optimization_id]
|
319
|
+
optimizer = payload[:optimizer]
|
320
|
+
error_message = payload[:error_message]
|
321
|
+
error_type = payload[:error_type]
|
322
|
+
|
323
|
+
log_parts = [
|
324
|
+
"event=optimization_error",
|
325
|
+
"optimization_id=#{optimization_id}",
|
326
|
+
"optimizer=#{optimizer}",
|
327
|
+
"error_type=#{error_type}"
|
328
|
+
]
|
329
|
+
log_parts << "error=\"#{error_message}\"" if error_message
|
330
|
+
|
331
|
+
logger.info(log_parts.join(' '))
|
194
332
|
end
|
195
333
|
end
|
196
334
|
end
|