dspy 0.15.2 → 0.15.4
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 +5 -3
- data/lib/dspy/instrumentation/event_payload_factory.rb +282 -0
- data/lib/dspy/instrumentation/event_payloads.rb +476 -0
- data/lib/dspy/instrumentation.rb +36 -4
- data/lib/dspy/lm/adapters/anthropic_adapter.rb +11 -6
- data/lib/dspy/lm/adapters/openai_adapter.rb +13 -8
- data/lib/dspy/lm/message.rb +99 -0
- data/lib/dspy/lm/message_builder.rb +26 -3
- data/lib/dspy/lm/response.rb +143 -14
- data/lib/dspy/lm/strategies/anthropic_tool_use_strategy.rb +2 -2
- data/lib/dspy/lm.rb +95 -19
- data/lib/dspy/mixins/type_coercion.rb +64 -2
- data/lib/dspy/subscribers/otel_subscriber.rb +3 -4
- data/lib/dspy/version.rb +1 -1
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e7154d8c22730a4216467c15a71d3adf4e2826e49211dfde7fd20e20344e4d4b
|
4
|
+
data.tar.gz: 4e3450f2b5d9e8acf2a5b583a3180bec0608b4c948a9b860bcd8bd387dc0bb7a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 36f07d86290d95e91ac8842807739d9edd566e3b9694413060181266611ea6fa1de941a509feb4518dfd3cf97f922689f8f15df7f6d3182cae0bef1b94b785cb
|
7
|
+
data.tar.gz: ad2a480af294da127d6fc6b54c56f66e9c59829e86c4dab70c6427d5b9dc6a7a9e26381f35448e9a8b514326169784ba160a92ba488fc06a9e47fcdacd2a2814
|
data/README.md
CHANGED
@@ -184,13 +184,15 @@ DSPy.rb has rapidly evolved from experimental to production-ready:
|
|
184
184
|
|
185
185
|
## Roadmap - Battle-Testing Toward v1.0
|
186
186
|
|
187
|
-
DSPy.rb is currently at **v0.
|
187
|
+
DSPy.rb is currently at **v0.15.2** and approaching stability. I'm focusing on real-world usage and refinement through the 0.16+ series before committing to a stable v1.0 API.
|
188
188
|
|
189
189
|
**Current Focus Areas:**
|
190
|
-
-
|
190
|
+
- ✅ **Ollama Support** - Local model integration (completed in v0.15.0)
|
191
|
+
- ✅ **Agentic Memory** - Persistent agent state management with Memory module
|
192
|
+
- 🚧 **Google Gemini Support** - Integration with Gemini models (#52)
|
191
193
|
- 🚧 **Context Engineering** - Advanced prompt optimization techniques
|
192
194
|
- 🚧 **MCP Support** - Model Context Protocol integration
|
193
|
-
- 🚧 **
|
195
|
+
- 🚧 **Additional Optimizer Support** - Expanding teleprompt capabilities
|
194
196
|
- 🚧 **Performance Optimization** - Based on production usage patterns
|
195
197
|
|
196
198
|
**v1.0 Philosophy:**
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sorbet-runtime'
|
4
|
+
require_relative 'event_payloads'
|
5
|
+
|
6
|
+
module DSPy
|
7
|
+
module Instrumentation
|
8
|
+
# Factory for creating typed event payloads from hash data
|
9
|
+
module EventPayloadFactory
|
10
|
+
extend T::Sig
|
11
|
+
extend self
|
12
|
+
|
13
|
+
# Create appropriate event struct based on event name
|
14
|
+
sig { params(event_name: String, payload: T::Hash[Symbol, T.untyped]).returns(T.untyped) }
|
15
|
+
def create_event(event_name, payload)
|
16
|
+
case event_name
|
17
|
+
when 'dspy.lm.request'
|
18
|
+
create_lm_request_event(payload)
|
19
|
+
when 'dspy.lm.tokens'
|
20
|
+
create_lm_tokens_event(payload)
|
21
|
+
when 'dspy.lm.response.parsed'
|
22
|
+
create_lm_response_parsed_event(payload)
|
23
|
+
when 'dspy.predict'
|
24
|
+
create_predict_event(payload)
|
25
|
+
when 'dspy.predict.validation_error'
|
26
|
+
create_predict_validation_error_event(payload)
|
27
|
+
when 'dspy.chain_of_thought'
|
28
|
+
create_chain_of_thought_event(payload)
|
29
|
+
when 'dspy.chain_of_thought.reasoning_complete'
|
30
|
+
create_chain_of_thought_reasoning_complete_event(payload)
|
31
|
+
when 'dspy.react.iteration'
|
32
|
+
create_react_iteration_event(payload)
|
33
|
+
when 'dspy.react.tool_call'
|
34
|
+
create_react_tool_call_event(payload)
|
35
|
+
when 'dspy.react.iteration_complete'
|
36
|
+
create_react_iteration_complete_event(payload)
|
37
|
+
when 'dspy.react.max_iterations'
|
38
|
+
create_react_max_iterations_event(payload)
|
39
|
+
when 'dspy.codeact.iteration'
|
40
|
+
create_codeact_iteration_event(payload)
|
41
|
+
when 'dspy.codeact.code_execution'
|
42
|
+
create_codeact_code_execution_event(payload)
|
43
|
+
else
|
44
|
+
# Return original payload for unhandled events
|
45
|
+
payload
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# LM Request Event
|
52
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMRequestEvent) }
|
53
|
+
def create_lm_request_event(payload)
|
54
|
+
# Extract timestamp, handling both timestamp and timestamp_ns keys
|
55
|
+
timestamp = extract_timestamp(payload)
|
56
|
+
|
57
|
+
LMRequestEvent.new(
|
58
|
+
timestamp: timestamp,
|
59
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
60
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
61
|
+
status: payload[:status] || 'success',
|
62
|
+
gen_ai_operation_name: payload[:gen_ai_operation_name] || 'unknown',
|
63
|
+
gen_ai_system: payload[:gen_ai_system] || 'unknown',
|
64
|
+
gen_ai_request_model: payload[:gen_ai_request_model] || 'unknown',
|
65
|
+
signature_class: payload[:signature_class],
|
66
|
+
provider: payload[:provider] || 'unknown',
|
67
|
+
adapter_class: payload[:adapter_class] || 'unknown',
|
68
|
+
input_size: payload[:input_size] || 0,
|
69
|
+
error_type: payload[:error_type],
|
70
|
+
error_message: payload[:error_message]
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Helper to extract timestamp from various formats
|
75
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(String) }
|
76
|
+
def extract_timestamp(payload)
|
77
|
+
if payload[:timestamp]
|
78
|
+
payload[:timestamp]
|
79
|
+
elsif payload[:timestamp_ns]
|
80
|
+
# Convert nanoseconds to ISO8601 for storage in struct
|
81
|
+
Time.at(payload[:timestamp_ns] / 1_000_000_000.0).iso8601
|
82
|
+
else
|
83
|
+
Time.now.iso8601
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# LM Tokens Event
|
88
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMTokensEvent) }
|
89
|
+
def create_lm_tokens_event(payload)
|
90
|
+
LMTokensEvent.new(
|
91
|
+
timestamp: extract_timestamp(payload),
|
92
|
+
status: payload[:status] || 'success',
|
93
|
+
input_tokens: payload[:input_tokens] || 0,
|
94
|
+
output_tokens: payload[:output_tokens] || 0,
|
95
|
+
total_tokens: payload[:total_tokens] || 0,
|
96
|
+
gen_ai_system: payload[:gen_ai_system] || 'unknown',
|
97
|
+
gen_ai_request_model: payload[:gen_ai_request_model] || 'unknown',
|
98
|
+
signature_class: payload[:signature_class]
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
# LM Response Parsed Event
|
103
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(LMResponseParsedEvent) }
|
104
|
+
def create_lm_response_parsed_event(payload)
|
105
|
+
LMResponseParsedEvent.new(
|
106
|
+
timestamp: extract_timestamp(payload),
|
107
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
108
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
109
|
+
status: payload[:status] || 'success',
|
110
|
+
signature_class: payload[:signature_class] || 'unknown',
|
111
|
+
provider: payload[:provider] || 'unknown',
|
112
|
+
success: payload[:success] || false,
|
113
|
+
response_length: payload[:response_length] || 0,
|
114
|
+
parse_type: payload[:parse_type],
|
115
|
+
error_type: payload[:error_type],
|
116
|
+
error_message: payload[:error_message]
|
117
|
+
)
|
118
|
+
end
|
119
|
+
|
120
|
+
# Predict Event
|
121
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(PredictEvent) }
|
122
|
+
def create_predict_event(payload)
|
123
|
+
PredictEvent.new(
|
124
|
+
timestamp: extract_timestamp(payload),
|
125
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
126
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
127
|
+
status: payload[:status] || 'success',
|
128
|
+
signature_class: payload[:signature_class] || 'unknown',
|
129
|
+
module_name: payload[:module_name] || 'unknown',
|
130
|
+
model: payload[:model] || 'unknown',
|
131
|
+
provider: payload[:provider] || 'unknown',
|
132
|
+
input_fields: payload[:input_fields] || [],
|
133
|
+
input_size: payload[:input_size],
|
134
|
+
output_size: payload[:output_size],
|
135
|
+
error_type: payload[:error_type],
|
136
|
+
error_message: payload[:error_message]
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
# Predict Validation Error Event
|
141
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(PredictValidationErrorEvent) }
|
142
|
+
def create_predict_validation_error_event(payload)
|
143
|
+
PredictValidationErrorEvent.new(
|
144
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
145
|
+
status: payload[:status] || 'error',
|
146
|
+
signature_class: payload[:signature_class] || 'unknown',
|
147
|
+
module_name: payload[:module_name] || 'unknown',
|
148
|
+
field_name: payload[:field_name] || 'unknown',
|
149
|
+
error_message: payload[:error_message] || 'unknown error',
|
150
|
+
retry_count: payload[:retry_count] || 0
|
151
|
+
)
|
152
|
+
end
|
153
|
+
|
154
|
+
# Chain of Thought Event
|
155
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ChainOfThoughtEvent) }
|
156
|
+
def create_chain_of_thought_event(payload)
|
157
|
+
ChainOfThoughtEvent.new(
|
158
|
+
timestamp: extract_timestamp(payload),
|
159
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
160
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
161
|
+
status: payload[:status] || 'success',
|
162
|
+
signature_class: payload[:signature_class] || 'unknown',
|
163
|
+
module_name: payload[:module_name] || 'unknown',
|
164
|
+
model: payload[:model] || 'unknown',
|
165
|
+
provider: payload[:provider] || 'unknown',
|
166
|
+
reasoning_length: payload[:reasoning_length],
|
167
|
+
answer_length: payload[:answer_length],
|
168
|
+
error_type: payload[:error_type],
|
169
|
+
error_message: payload[:error_message]
|
170
|
+
)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Chain of Thought Reasoning Complete Event
|
174
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ChainOfThoughtReasoningCompleteEvent) }
|
175
|
+
def create_chain_of_thought_reasoning_complete_event(payload)
|
176
|
+
ChainOfThoughtReasoningCompleteEvent.new(
|
177
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
178
|
+
status: payload[:status] || 'success',
|
179
|
+
signature_class: payload[:signature_class] || 'unknown',
|
180
|
+
module_name: payload[:module_name] || 'unknown',
|
181
|
+
reasoning_length: payload[:reasoning_length] || 0,
|
182
|
+
answer_present: payload[:answer_present] || false
|
183
|
+
)
|
184
|
+
end
|
185
|
+
|
186
|
+
# ReAct Iteration Event
|
187
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactIterationEvent) }
|
188
|
+
def create_react_iteration_event(payload)
|
189
|
+
ReactIterationEvent.new(
|
190
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
191
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
192
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
193
|
+
status: payload[:status] || 'success',
|
194
|
+
iteration: payload[:iteration] || 0,
|
195
|
+
max_iterations: payload[:max_iterations] || 5,
|
196
|
+
history_length: payload[:history_length] || 0,
|
197
|
+
tools_used_so_far: payload[:tools_used_so_far] || [],
|
198
|
+
error_type: payload[:error_type],
|
199
|
+
error_message: payload[:error_message]
|
200
|
+
)
|
201
|
+
end
|
202
|
+
|
203
|
+
# ReAct Tool Call Event
|
204
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactToolCallEvent) }
|
205
|
+
def create_react_tool_call_event(payload)
|
206
|
+
ReactToolCallEvent.new(
|
207
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
208
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
209
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
210
|
+
status: payload[:status] || 'success',
|
211
|
+
iteration: payload[:iteration] || 0,
|
212
|
+
tool_name: payload[:tool_name] || 'unknown',
|
213
|
+
tool_input: payload[:tool_input],
|
214
|
+
error_type: payload[:error_type],
|
215
|
+
error_message: payload[:error_message]
|
216
|
+
)
|
217
|
+
end
|
218
|
+
|
219
|
+
# ReAct Iteration Complete Event
|
220
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactIterationCompleteEvent) }
|
221
|
+
def create_react_iteration_complete_event(payload)
|
222
|
+
ReactIterationCompleteEvent.new(
|
223
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
224
|
+
status: payload[:status] || 'success',
|
225
|
+
iteration: payload[:iteration] || 0,
|
226
|
+
thought: payload[:thought] || '',
|
227
|
+
action: payload[:action] || '',
|
228
|
+
action_input: payload[:action_input],
|
229
|
+
observation: payload[:observation] || '',
|
230
|
+
tools_used: payload[:tools_used] || []
|
231
|
+
)
|
232
|
+
end
|
233
|
+
|
234
|
+
# ReAct Max Iterations Event
|
235
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(ReactMaxIterationsEvent) }
|
236
|
+
def create_react_max_iterations_event(payload)
|
237
|
+
ReactMaxIterationsEvent.new(
|
238
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
239
|
+
status: payload[:status] || 'warning',
|
240
|
+
iteration_count: payload[:iteration_count] || 0,
|
241
|
+
max_iterations: payload[:max_iterations] || 5,
|
242
|
+
tools_used: payload[:tools_used] || [],
|
243
|
+
final_history_length: payload[:final_history_length] || 0
|
244
|
+
)
|
245
|
+
end
|
246
|
+
|
247
|
+
# CodeAct Iteration Event
|
248
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(CodeActIterationEvent) }
|
249
|
+
def create_codeact_iteration_event(payload)
|
250
|
+
CodeActIterationEvent.new(
|
251
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
252
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
253
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
254
|
+
status: payload[:status] || 'success',
|
255
|
+
iteration: payload[:iteration] || 0,
|
256
|
+
max_iterations: payload[:max_iterations] || 5,
|
257
|
+
history_length: payload[:history_length] || 0,
|
258
|
+
code_blocks_executed: payload[:code_blocks_executed] || 0,
|
259
|
+
error_type: payload[:error_type],
|
260
|
+
error_message: payload[:error_message]
|
261
|
+
)
|
262
|
+
end
|
263
|
+
|
264
|
+
# CodeAct Code Execution Event
|
265
|
+
sig { params(payload: T::Hash[Symbol, T.untyped]).returns(CodeActCodeExecutionEvent) }
|
266
|
+
def create_codeact_code_execution_event(payload)
|
267
|
+
CodeActCodeExecutionEvent.new(
|
268
|
+
timestamp: payload[:timestamp] || Time.now.iso8601,
|
269
|
+
duration_ms: payload[:duration_ms] || 0.0,
|
270
|
+
cpu_time_ms: payload[:cpu_time_ms] || 0.0,
|
271
|
+
status: payload[:status] || 'success',
|
272
|
+
iteration: payload[:iteration] || 0,
|
273
|
+
code_type: payload[:code_type] || 'unknown',
|
274
|
+
code_length: payload[:code_length] || 0,
|
275
|
+
execution_success: payload[:execution_success] || false,
|
276
|
+
error_type: payload[:error_type],
|
277
|
+
error_message: payload[:error_message]
|
278
|
+
)
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|