dspy 0.3.1 → 0.5.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.
@@ -28,6 +28,10 @@ module DSPy
28
28
  log_lm_request(event)
29
29
  end
30
30
 
31
+ DSPy::Instrumentation.subscribe('dspy.lm.tokens') do |event|
32
+ log_lm_tokens(event)
33
+ end
34
+
31
35
  DSPy::Instrumentation.subscribe('dspy.predict') do |event|
32
36
  log_prediction(event)
33
37
  end
@@ -47,6 +51,27 @@ module DSPy
47
51
  DSPy::Instrumentation.subscribe('dspy.react.tool_call') do |event|
48
52
  log_react_tool_call(event)
49
53
  end
54
+
55
+ # Subscribe to optimization events
56
+ DSPy::Instrumentation.subscribe('dspy.optimization.start') do |event|
57
+ log_optimization_start(event)
58
+ end
59
+
60
+ DSPy::Instrumentation.subscribe('dspy.optimization.complete') do |event|
61
+ log_optimization_complete(event)
62
+ end
63
+
64
+ DSPy::Instrumentation.subscribe('dspy.optimization.trial_start') do |event|
65
+ log_optimization_trial_start(event)
66
+ end
67
+
68
+ DSPy::Instrumentation.subscribe('dspy.optimization.trial_complete') do |event|
69
+ log_optimization_trial_complete(event)
70
+ end
71
+
72
+ DSPy::Instrumentation.subscribe('dspy.optimization.error') do |event|
73
+ log_optimization_error(event)
74
+ end
50
75
  end
51
76
 
52
77
  # Callback methods for different event types
@@ -88,21 +113,44 @@ module DSPy
88
113
  model = payload[:gen_ai_request_model] || payload[:model]
89
114
  duration = payload[:duration_ms]&.round(2)
90
115
  status = payload[:status]
91
- tokens = payload[:tokens_total]
116
+ timestamp = format_timestamp(payload)
92
117
 
93
118
  log_parts = [
94
119
  "event=lm_request",
120
+ timestamp,
95
121
  "provider=#{provider}",
96
122
  "model=#{model}",
97
123
  "status=#{status}",
98
124
  "duration_ms=#{duration}"
99
- ]
100
- log_parts << "tokens=#{tokens}" if tokens
125
+ ].compact
101
126
  log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
102
127
 
103
128
  logger.info(log_parts.join(' '))
104
129
  end
105
130
 
131
+ sig { params(event: T.untyped).void }
132
+ def log_lm_tokens(event)
133
+ payload = event.payload
134
+ provider = payload[:gen_ai_system] || payload[:provider]
135
+ model = payload[:gen_ai_request_model] || payload[:model]
136
+ input_tokens = payload[:input_tokens]
137
+ output_tokens = payload[:output_tokens]
138
+ total_tokens = payload[:total_tokens]
139
+ timestamp = format_timestamp(payload)
140
+
141
+ log_parts = [
142
+ "event=lm_tokens",
143
+ timestamp,
144
+ "provider=#{provider}",
145
+ "model=#{model}"
146
+ ].compact
147
+ log_parts << "input_tokens=#{input_tokens}" if input_tokens
148
+ log_parts << "output_tokens=#{output_tokens}" if output_tokens
149
+ log_parts << "total_tokens=#{total_tokens}" if total_tokens
150
+
151
+ logger.info(log_parts.join(' '))
152
+ end
153
+
106
154
  sig { params(event: T.untyped).void }
107
155
  def log_prediction(event)
108
156
  payload = event.payload
@@ -110,13 +158,15 @@ module DSPy
110
158
  duration = payload[:duration_ms]&.round(2)
111
159
  status = payload[:status]
112
160
  input_size = payload[:input_size]
161
+ timestamp = format_timestamp(payload)
113
162
 
114
163
  log_parts = [
115
164
  "event=prediction",
165
+ timestamp,
116
166
  "signature=#{signature}",
117
167
  "status=#{status}",
118
168
  "duration_ms=#{duration}"
119
- ]
169
+ ].compact
120
170
  log_parts << "input_size=#{input_size}" if input_size
121
171
  log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
122
172
 
@@ -131,13 +181,15 @@ module DSPy
131
181
  status = payload[:status]
132
182
  reasoning_steps = payload[:reasoning_steps]
133
183
  reasoning_length = payload[:reasoning_length]
184
+ timestamp = format_timestamp(payload)
134
185
 
135
186
  log_parts = [
136
187
  "event=chain_of_thought",
188
+ timestamp,
137
189
  "signature=#{signature}",
138
190
  "status=#{status}",
139
191
  "duration_ms=#{duration}"
140
- ]
192
+ ].compact
141
193
  log_parts << "reasoning_steps=#{reasoning_steps}" if reasoning_steps
142
194
  log_parts << "reasoning_length=#{reasoning_length}" if reasoning_length
143
195
  log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
@@ -210,6 +262,129 @@ module DSPy
210
262
 
211
263
  logger.info(log_parts.join(' '))
212
264
  end
265
+
266
+ # Optimization event logging methods
267
+ sig { params(event: T.untyped).void }
268
+ def log_optimization_start(event)
269
+ payload = event.payload
270
+ optimization_id = payload[:optimization_id]
271
+ optimizer = payload[:optimizer]
272
+ trainset_size = payload[:trainset_size]
273
+ valset_size = payload[:valset_size]
274
+
275
+ log_parts = [
276
+ "event=optimization_start",
277
+ "optimization_id=#{optimization_id}",
278
+ "optimizer=#{optimizer}",
279
+ "trainset_size=#{trainset_size}"
280
+ ]
281
+ log_parts << "valset_size=#{valset_size}" if valset_size
282
+
283
+ logger.info(log_parts.join(' '))
284
+ end
285
+
286
+ sig { params(event: T.untyped).void }
287
+ def log_optimization_complete(event)
288
+ payload = event.payload
289
+ optimization_id = payload[:optimization_id]
290
+ optimizer = payload[:optimizer]
291
+ duration = payload[:duration_ms]&.round(2)
292
+ best_score = payload[:best_score]
293
+ trials_count = payload[:trials_count]
294
+
295
+ log_parts = [
296
+ "event=optimization_complete",
297
+ "optimization_id=#{optimization_id}",
298
+ "optimizer=#{optimizer}",
299
+ "duration_ms=#{duration}"
300
+ ]
301
+ log_parts << "best_score=#{best_score}" if best_score
302
+ log_parts << "trials_count=#{trials_count}" if trials_count
303
+
304
+ logger.info(log_parts.join(' '))
305
+ end
306
+
307
+ sig { params(event: T.untyped).void }
308
+ def log_optimization_trial_start(event)
309
+ payload = event.payload
310
+ optimization_id = payload[:optimization_id]
311
+ trial_number = payload[:trial_number]
312
+ instruction = payload[:instruction]
313
+
314
+ log_parts = [
315
+ "event=optimization_trial_start",
316
+ "optimization_id=#{optimization_id}",
317
+ "trial_number=#{trial_number}"
318
+ ]
319
+ log_parts << "instruction=\"#{instruction&.slice(0, 100)}\"" if instruction
320
+
321
+ logger.info(log_parts.join(' '))
322
+ end
323
+
324
+ sig { params(event: T.untyped).void }
325
+ def log_optimization_trial_complete(event)
326
+ payload = event.payload
327
+ optimization_id = payload[:optimization_id]
328
+ trial_number = payload[:trial_number]
329
+ duration = payload[:duration_ms]&.round(2)
330
+ score = payload[:score]
331
+ status = payload[:status]
332
+
333
+ log_parts = [
334
+ "event=optimization_trial_complete",
335
+ "optimization_id=#{optimization_id}",
336
+ "trial_number=#{trial_number}",
337
+ "status=#{status}",
338
+ "duration_ms=#{duration}"
339
+ ]
340
+ log_parts << "score=#{score}" if score
341
+ log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
342
+
343
+ logger.info(log_parts.join(' '))
344
+ end
345
+
346
+ sig { params(event: T.untyped).void }
347
+ def log_optimization_error(event)
348
+ payload = event.payload
349
+ optimization_id = payload[:optimization_id]
350
+ optimizer = payload[:optimizer]
351
+ error_message = payload[:error_message]
352
+ error_type = payload[:error_type]
353
+
354
+ log_parts = [
355
+ "event=optimization_error",
356
+ "optimization_id=#{optimization_id}",
357
+ "optimizer=#{optimizer}",
358
+ "error_type=#{error_type}"
359
+ ]
360
+ log_parts << "error=\"#{error_message}\"" if error_message
361
+
362
+ logger.info(log_parts.join(' '))
363
+ end
364
+
365
+ # Format timestamp based on configured format
366
+ sig { params(payload: T::Hash[Symbol, T.untyped]).returns(T.nilable(String)) }
367
+ def format_timestamp(payload)
368
+ case DSPy.config.instrumentation.timestamp_format
369
+ when DSPy::TimestampFormat::ISO8601
370
+ if timestamp = payload[:timestamp]
371
+ "timestamp=#{timestamp}"
372
+ end
373
+ when DSPy::TimestampFormat::RFC3339_NANO
374
+ if timestamp = payload[:timestamp]
375
+ "timestamp=#{timestamp}"
376
+ end
377
+ when DSPy::TimestampFormat::UNIX_NANO
378
+ if timestamp_ns = payload[:timestamp_ns]
379
+ "timestamp_ns=#{timestamp_ns}"
380
+ end
381
+ else
382
+ # Fallback to timestamp if available
383
+ if timestamp = payload[:timestamp]
384
+ "timestamp=#{timestamp}"
385
+ end
386
+ end
387
+ end
213
388
  end
214
389
  end
215
390
  end