dspy 0.16.0 → 0.18.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.
@@ -1,480 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module DSPy
4
- module Subscribers
5
- # Logger subscriber that provides detailed logging based on instrumentation events
6
- # Subscribes to DSPy events and logs relevant information for debugging and monitoring
7
- class LoggerSubscriber
8
- extend T::Sig
9
-
10
- sig { params(logger: T.nilable(T.any(Logger, Dry::Logger::Dispatcher))).void }
11
- def initialize(logger: nil)
12
- @explicit_logger = T.let(logger, T.nilable(T.any(Logger, Dry::Logger::Dispatcher)))
13
- setup_event_subscriptions
14
- end
15
-
16
- private
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
-
24
- sig { void }
25
- def setup_event_subscriptions
26
- # Subscribe to DSPy instrumentation events
27
- DSPy::Instrumentation.subscribe('dspy.lm.request') do |event|
28
- log_lm_request(event)
29
- end
30
-
31
- DSPy::Instrumentation.subscribe('dspy.lm.tokens') do |event|
32
- log_lm_tokens(event)
33
- end
34
-
35
- DSPy::Instrumentation.subscribe('dspy.predict') do |event|
36
- log_prediction(event)
37
- end
38
-
39
- DSPy::Instrumentation.subscribe('dspy.chain_of_thought') do |event|
40
- log_chain_of_thought(event)
41
- end
42
-
43
- DSPy::Instrumentation.subscribe('dspy.react') do |event|
44
- log_react(event)
45
- end
46
-
47
- DSPy::Instrumentation.subscribe('dspy.react.iteration_complete') do |event|
48
- log_react_iteration_complete(event)
49
- end
50
-
51
- DSPy::Instrumentation.subscribe('dspy.react.tool_call') do |event|
52
- log_react_tool_call(event)
53
- end
54
-
55
- DSPy::Instrumentation.subscribe('dspy.codeact') do |event|
56
- log_codeact(event)
57
- end
58
-
59
- DSPy::Instrumentation.subscribe('dspy.codeact.iteration_complete') do |event|
60
- log_codeact_iteration_complete(event)
61
- end
62
-
63
- DSPy::Instrumentation.subscribe('dspy.codeact.code_execution') do |event|
64
- log_codeact_code_execution(event)
65
- end
66
-
67
- # Subscribe to optimization events
68
- DSPy::Instrumentation.subscribe('dspy.optimization.start') do |event|
69
- log_optimization_start(event)
70
- end
71
-
72
- DSPy::Instrumentation.subscribe('dspy.optimization.complete') do |event|
73
- log_optimization_complete(event)
74
- end
75
-
76
- DSPy::Instrumentation.subscribe('dspy.optimization.trial_start') do |event|
77
- log_optimization_trial_start(event)
78
- end
79
-
80
- DSPy::Instrumentation.subscribe('dspy.optimization.trial_complete') do |event|
81
- log_optimization_trial_complete(event)
82
- end
83
-
84
- DSPy::Instrumentation.subscribe('dspy.optimization.error') do |event|
85
- log_optimization_error(event)
86
- end
87
- end
88
-
89
- # Callback methods for different event types
90
- sig { params(event: T.untyped).void }
91
- def on_lm_request(event)
92
- log_lm_request(event)
93
- end
94
-
95
- sig { params(event: T.untyped).void }
96
- def on_predict(event)
97
- log_prediction(event)
98
- end
99
-
100
- sig { params(event: T.untyped).void }
101
- def on_chain_of_thought(event)
102
- log_chain_of_thought(event)
103
- end
104
-
105
- sig { params(event: T.untyped).void }
106
- def on_react(event)
107
- log_react(event)
108
- end
109
-
110
- sig { params(event: T.untyped).void }
111
- def on_react_iteration_complete(event)
112
- log_react_iteration_complete(event)
113
- end
114
-
115
- sig { params(event: T.untyped).void }
116
- def on_react_tool_call(event)
117
- log_react_tool_call(event)
118
- end
119
-
120
- # Event logging methods
121
- sig { params(event: T.untyped).void }
122
- def log_lm_request(event)
123
- payload = event.payload
124
- provider = payload[:provider]
125
- model = payload[:gen_ai_request_model] || payload[:model]
126
- duration = payload[:duration_ms]&.round(2)
127
- status = payload[:status]
128
- timestamp = format_timestamp(payload)
129
-
130
- log_parts = [
131
- "event=lm_request",
132
- timestamp,
133
- "provider=#{provider}",
134
- "model=#{model}",
135
- "status=#{status}",
136
- "duration_ms=#{duration}"
137
- ].compact
138
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
139
-
140
- logger.info(log_parts.join(' '))
141
- end
142
-
143
- sig { params(event: T.untyped).void }
144
- def log_lm_tokens(event)
145
- payload = event.payload
146
- provider = payload[:gen_ai_system] || payload[:provider]
147
- model = payload[:gen_ai_request_model] || payload[:model]
148
- input_tokens = payload[:input_tokens]
149
- output_tokens = payload[:output_tokens]
150
- total_tokens = payload[:total_tokens]
151
- timestamp = format_timestamp(payload)
152
-
153
- log_parts = [
154
- "event=lm_tokens",
155
- timestamp,
156
- "provider=#{provider}",
157
- "model=#{model}"
158
- ].compact
159
- log_parts << "input_tokens=#{input_tokens}" if input_tokens
160
- log_parts << "output_tokens=#{output_tokens}" if output_tokens
161
- log_parts << "total_tokens=#{total_tokens}" if total_tokens
162
-
163
- logger.info(log_parts.join(' '))
164
- end
165
-
166
- sig { params(event: T.untyped).void }
167
- def log_prediction(event)
168
- payload = event.payload
169
- signature = payload[:signature_class]
170
- duration = payload[:duration_ms]&.round(2)
171
- status = payload[:status]
172
- input_size = payload[:input_size]
173
- timestamp = format_timestamp(payload)
174
-
175
- log_parts = [
176
- "event=prediction",
177
- timestamp,
178
- "signature=#{signature}",
179
- "status=#{status}",
180
- "duration_ms=#{duration}"
181
- ].compact
182
- log_parts << "input_size=#{input_size}" if input_size
183
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
184
-
185
- logger.info(log_parts.join(' '))
186
- end
187
-
188
- sig { params(event: T.untyped).void }
189
- def log_chain_of_thought(event)
190
- payload = event.payload
191
- signature = payload[:signature_class]
192
- duration = payload[:duration_ms]&.round(2)
193
- status = payload[:status]
194
- reasoning_steps = payload[:reasoning_steps]
195
- reasoning_length = payload[:reasoning_length]
196
- timestamp = format_timestamp(payload)
197
-
198
- log_parts = [
199
- "event=chain_of_thought",
200
- timestamp,
201
- "signature=#{signature}",
202
- "status=#{status}",
203
- "duration_ms=#{duration}"
204
- ].compact
205
- log_parts << "reasoning_steps=#{reasoning_steps}" if reasoning_steps
206
- log_parts << "reasoning_length=#{reasoning_length}" if reasoning_length
207
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
208
-
209
- logger.info(log_parts.join(' '))
210
- end
211
-
212
- sig { params(event: T.untyped).void }
213
- def log_react(event)
214
- payload = event.payload
215
- signature = payload[:signature_class]
216
- duration = payload[:duration_ms]&.round(2)
217
- status = payload[:status]
218
- iteration_count = payload[:iteration_count]
219
- tools_used = payload[:tools_used]
220
- final_answer = payload[:final_answer]
221
-
222
- log_parts = [
223
- "event=react",
224
- "signature=#{signature}",
225
- "status=#{status}",
226
- "duration_ms=#{duration}"
227
- ]
228
- log_parts << "iterations=#{iteration_count}" if iteration_count
229
- log_parts << "tools_used=\"#{tools_used.join(',')}\"" if tools_used&.any?
230
- log_parts << "final_answer=\"#{final_answer&.truncate(100)}\"" if final_answer
231
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
232
-
233
- logger.info(log_parts.join(' '))
234
- end
235
-
236
- sig { params(event: T.untyped).void }
237
- def log_react_iteration_complete(event)
238
- payload = event.payload
239
- iteration = payload[:iteration]
240
- thought = payload[:thought]
241
- action = payload[:action]
242
- duration = payload[:duration_ms]&.round(2)
243
- status = payload[:status]
244
-
245
- log_parts = [
246
- "event=react_iteration",
247
- "iteration=#{iteration}",
248
- "status=#{status}",
249
- "duration_ms=#{duration}"
250
- ]
251
- log_parts << "thought=\"#{thought && thought.length > 100 ? thought[0..97] + '...' : thought}\"" if thought
252
- log_parts << "action=\"#{action}\"" if action
253
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
254
-
255
- logger.info(log_parts.join(' '))
256
- end
257
-
258
- sig { params(event: T.untyped).void }
259
- def log_react_tool_call(event)
260
- payload = event.payload
261
- iteration = payload[:iteration]
262
- tool_name = payload[:tool_name]
263
- duration = payload[:duration_ms]&.round(2)
264
- status = payload[:status]
265
-
266
- log_parts = [
267
- "event=tool_call",
268
- "tool=#{tool_name}",
269
- "iteration=#{iteration}",
270
- "status=#{status}",
271
- "duration_ms=#{duration}"
272
- ]
273
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
274
-
275
- logger.info(log_parts.join(' '))
276
- end
277
-
278
- sig { params(event: T.untyped).void }
279
- def log_codeact(event)
280
- payload = event.payload
281
- signature = payload[:signature_class]
282
- duration = payload[:duration_ms]&.round(2)
283
- status = payload[:status]
284
- iteration_count = payload[:iteration_count]
285
- code_executions = payload[:code_executions]
286
- final_answer = payload[:final_answer]
287
- timestamp = format_timestamp(payload)
288
-
289
- log_parts = [
290
- "event=codeact",
291
- timestamp,
292
- "signature=#{signature}",
293
- "status=#{status}",
294
- "duration_ms=#{duration}"
295
- ].compact
296
- log_parts << "iterations=#{iteration_count}" if iteration_count
297
- log_parts << "code_executions=#{code_executions}" if code_executions
298
- log_parts << "final_answer=\"#{final_answer&.truncate(100)}\"" if final_answer
299
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
300
-
301
- logger.info(log_parts.join(' '))
302
- end
303
-
304
- sig { params(event: T.untyped).void }
305
- def log_codeact_iteration_complete(event)
306
- payload = event.payload
307
- iteration = payload[:iteration]
308
- thought = payload[:thought]
309
- ruby_code = payload[:ruby_code]
310
- observation = payload[:observation]
311
- duration = payload[:duration_ms]&.round(2)
312
- status = payload[:status]
313
- timestamp = format_timestamp(payload)
314
-
315
- log_parts = [
316
- "event=codeact_iteration",
317
- timestamp,
318
- "iteration=#{iteration}",
319
- "status=#{status}",
320
- "duration_ms=#{duration}"
321
- ].compact
322
- log_parts << "thought=\"#{thought && thought.length > 100 ? thought[0..97] + '...' : thought}\"" if thought
323
- log_parts << "code=\"#{ruby_code && ruby_code.length > 100 ? ruby_code[0..97] + '...' : ruby_code}\"" if ruby_code
324
- log_parts << "observation=\"#{observation && observation.length > 100 ? observation[0..97] + '...' : observation}\"" if observation
325
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
326
-
327
- logger.info(log_parts.join(' '))
328
- end
329
-
330
- sig { params(event: T.untyped).void }
331
- def log_codeact_code_execution(event)
332
- payload = event.payload
333
- iteration = payload[:iteration]
334
- ruby_code = payload[:ruby_code]
335
- execution_result = payload[:execution_result]
336
- execution_error = payload[:execution_error]
337
- duration = payload[:duration_ms]&.round(2)
338
- status = payload[:status]
339
- timestamp = format_timestamp(payload)
340
-
341
- log_parts = [
342
- "event=code_execution",
343
- timestamp,
344
- "iteration=#{iteration}",
345
- "status=#{status}",
346
- "duration_ms=#{duration}"
347
- ].compact
348
- log_parts << "code=\"#{ruby_code && ruby_code.length > 50 ? ruby_code[0..47] + '...' : ruby_code}\"" if ruby_code
349
- log_parts << "result=\"#{execution_result && execution_result.length > 100 ? execution_result[0..97] + '...' : execution_result}\"" if execution_result
350
- log_parts << "execution_error=\"#{execution_error}\"" if execution_error
351
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
352
-
353
- logger.info(log_parts.join(' '))
354
- end
355
-
356
- # Optimization event logging methods
357
- sig { params(event: T.untyped).void }
358
- def log_optimization_start(event)
359
- payload = event.payload
360
- optimization_id = payload[:optimization_id]
361
- optimizer = payload[:optimizer]
362
- trainset_size = payload[:trainset_size]
363
- valset_size = payload[:valset_size]
364
-
365
- log_parts = [
366
- "event=optimization_start",
367
- "optimization_id=#{optimization_id}",
368
- "optimizer=#{optimizer}",
369
- "trainset_size=#{trainset_size}"
370
- ]
371
- log_parts << "valset_size=#{valset_size}" if valset_size
372
-
373
- logger.info(log_parts.join(' '))
374
- end
375
-
376
- sig { params(event: T.untyped).void }
377
- def log_optimization_complete(event)
378
- payload = event.payload
379
- optimization_id = payload[:optimization_id]
380
- optimizer = payload[:optimizer]
381
- duration = payload[:duration_ms]&.round(2)
382
- best_score = payload[:best_score]
383
- trials_count = payload[:trials_count]
384
-
385
- log_parts = [
386
- "event=optimization_complete",
387
- "optimization_id=#{optimization_id}",
388
- "optimizer=#{optimizer}",
389
- "duration_ms=#{duration}"
390
- ]
391
- log_parts << "best_score=#{best_score}" if best_score
392
- log_parts << "trials_count=#{trials_count}" if trials_count
393
-
394
- logger.info(log_parts.join(' '))
395
- end
396
-
397
- sig { params(event: T.untyped).void }
398
- def log_optimization_trial_start(event)
399
- payload = event.payload
400
- optimization_id = payload[:optimization_id]
401
- trial_number = payload[:trial_number]
402
- instruction = payload[:instruction]
403
-
404
- log_parts = [
405
- "event=optimization_trial_start",
406
- "optimization_id=#{optimization_id}",
407
- "trial_number=#{trial_number}"
408
- ]
409
- log_parts << "instruction=\"#{instruction&.slice(0, 100)}\"" if instruction
410
-
411
- logger.info(log_parts.join(' '))
412
- end
413
-
414
- sig { params(event: T.untyped).void }
415
- def log_optimization_trial_complete(event)
416
- payload = event.payload
417
- optimization_id = payload[:optimization_id]
418
- trial_number = payload[:trial_number]
419
- duration = payload[:duration_ms]&.round(2)
420
- score = payload[:score]
421
- status = payload[:status]
422
-
423
- log_parts = [
424
- "event=optimization_trial_complete",
425
- "optimization_id=#{optimization_id}",
426
- "trial_number=#{trial_number}",
427
- "status=#{status}",
428
- "duration_ms=#{duration}"
429
- ]
430
- log_parts << "score=#{score}" if score
431
- log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
432
-
433
- logger.info(log_parts.join(' '))
434
- end
435
-
436
- sig { params(event: T.untyped).void }
437
- def log_optimization_error(event)
438
- payload = event.payload
439
- optimization_id = payload[:optimization_id]
440
- optimizer = payload[:optimizer]
441
- error_message = payload[:error_message]
442
- error_type = payload[:error_type]
443
-
444
- log_parts = [
445
- "event=optimization_error",
446
- "optimization_id=#{optimization_id}",
447
- "optimizer=#{optimizer}",
448
- "error_type=#{error_type}"
449
- ]
450
- log_parts << "error=\"#{error_message}\"" if error_message
451
-
452
- logger.info(log_parts.join(' '))
453
- end
454
-
455
- # Format timestamp based on configured format
456
- sig { params(payload: T::Hash[Symbol, T.untyped]).returns(T.nilable(String)) }
457
- def format_timestamp(payload)
458
- case DSPy.config.instrumentation.timestamp_format
459
- when DSPy::TimestampFormat::ISO8601
460
- if timestamp = payload[:timestamp]
461
- "timestamp=#{timestamp}"
462
- end
463
- when DSPy::TimestampFormat::RFC3339_NANO
464
- if timestamp = payload[:timestamp]
465
- "timestamp=#{timestamp}"
466
- end
467
- when DSPy::TimestampFormat::UNIX_NANO
468
- if timestamp_ns = payload[:timestamp_ns]
469
- "timestamp_ns=#{timestamp_ns}"
470
- end
471
- else
472
- # Fallback to timestamp if available
473
- if timestamp = payload[:timestamp]
474
- "timestamp=#{timestamp}"
475
- end
476
- end
477
- end
478
- end
479
- end
480
- end