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.
- checksums.yaml +4 -4
- data/README.md +67 -385
- data/lib/dspy/chain_of_thought.rb +123 -86
- 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/token_tracker.rb +6 -6
- data/lib/dspy/instrumentation.rb +199 -18
- data/lib/dspy/lm/adapter_factory.rb +6 -8
- data/lib/dspy/lm.rb +79 -35
- data/lib/dspy/mixins/instrumentation_helpers.rb +133 -0
- data/lib/dspy/mixins/struct_builder.rb +133 -0
- data/lib/dspy/mixins/type_coercion.rb +67 -0
- data/lib/dspy/predict.rb +83 -128
- data/lib/dspy/prompt.rb +222 -0
- data/lib/dspy/propose/grounded_proposer.rb +560 -0
- data/lib/dspy/re_act.rb +242 -173
- 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 +180 -5
- 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 +105 -0
- metadata +32 -12
- data/lib/dspy/lm/adapters/ruby_llm_adapter.rb +0 -81
@@ -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
|
-
|
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
|