dspy 0.3.0 → 0.3.1
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 +0 -30
- data/lib/dspy/instrumentation.rb +13 -0
- data/lib/dspy/subscribers/logger_subscriber.rb +75 -57
- data/lib/dspy.rb +3 -17
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 64e8b7011ea06273772d2ef8a985d61aa1ee30d5d6fb3c559dc22ed81e345b16
|
4
|
+
data.tar.gz: a16fab394ee1db1bcaddc0baaa3636590a2ca00c1ce60eb7dc1a00355750009f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ce4ab780cce89c2c3680e6c5703e853bbd901ac206993af1cd0660cc25e9796ef1dd5adb488d575bbc97ea75a71b563e82eb4cc521b5c84291ff9b1e106216e1
|
7
|
+
data.tar.gz: be313a08f282eb7a08638879298742baf5ec26bcb48b946ac068892c2ad542003d99df575ecc85bb5220c637368f02adf042164831cf5d48fd7139bb3f5424a7
|
data/README.md
CHANGED
@@ -380,16 +380,6 @@ DSPy.rb includes built-in instrumentation that captures detailed events and
|
|
380
380
|
performance metrics from your LLM operations. Perfect for monitoring your
|
381
381
|
applications and integrating with observability tools.
|
382
382
|
|
383
|
-
### Quick Setup
|
384
|
-
|
385
|
-
Enable instrumentation to start capturing events:
|
386
|
-
|
387
|
-
```ruby
|
388
|
-
DSPy::Instrumentation.configure do |config|
|
389
|
-
config.enabled = true
|
390
|
-
end
|
391
|
-
```
|
392
|
-
|
393
383
|
### Available Events
|
394
384
|
|
395
385
|
Subscribe to these events to monitor different aspects of your LLM operations:
|
@@ -449,26 +439,6 @@ giving you precise cost tracking:
|
|
449
439
|
}
|
450
440
|
```
|
451
441
|
|
452
|
-
### Configuration Options
|
453
|
-
|
454
|
-
```ruby
|
455
|
-
DSPy::Instrumentation.configure do |config|
|
456
|
-
config.enabled = true
|
457
|
-
config.log_to_stdout = false
|
458
|
-
config.log_file = 'log/dspy.log'
|
459
|
-
config.log_level = :info
|
460
|
-
|
461
|
-
# Custom payload enrichment
|
462
|
-
config.custom_options = lambda do |event|
|
463
|
-
{
|
464
|
-
timestamp: Time.current.iso8601,
|
465
|
-
hostname: Socket.gethostname,
|
466
|
-
request_id: Thread.current[:request_id]
|
467
|
-
}
|
468
|
-
end
|
469
|
-
end
|
470
|
-
```
|
471
|
-
|
472
442
|
### Integration with Monitoring Tools
|
473
443
|
|
474
444
|
Subscribe to events for custom processing:
|
data/lib/dspy/instrumentation.rb
CHANGED
@@ -7,6 +7,13 @@ module DSPy
|
|
7
7
|
# Core instrumentation module using dry-monitor for event emission
|
8
8
|
# Provides extension points for logging, Langfuse, New Relic, and custom monitoring
|
9
9
|
module Instrumentation
|
10
|
+
# Get the current logger subscriber instance (lazy initialization)
|
11
|
+
def self.logger_subscriber
|
12
|
+
@logger_subscriber ||= begin
|
13
|
+
require_relative 'subscribers/logger_subscriber'
|
14
|
+
DSPy::Subscribers::LoggerSubscriber.new
|
15
|
+
end
|
16
|
+
end
|
10
17
|
|
11
18
|
def self.notifications
|
12
19
|
@notifications ||= Dry::Monitor::Notifications.new(:dspy).tap do |n|
|
@@ -94,7 +101,13 @@ module DSPy
|
|
94
101
|
end
|
95
102
|
|
96
103
|
def self.emit_event(event_name, payload)
|
104
|
+
# Ensure logger subscriber is initialized
|
105
|
+
logger_subscriber
|
97
106
|
notifications.instrument(event_name, payload)
|
98
107
|
end
|
108
|
+
|
109
|
+
def self.setup_subscribers
|
110
|
+
# Lazy initialization - will be created when first accessed
|
111
|
+
end
|
99
112
|
end
|
100
113
|
end
|
@@ -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
|
@@ -82,18 +88,19 @@ module DSPy
|
|
82
88
|
model = payload[:gen_ai_request_model] || payload[:model]
|
83
89
|
duration = payload[:duration_ms]&.round(2)
|
84
90
|
status = payload[:status]
|
85
|
-
tokens =
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
91
|
+
tokens = payload[:tokens_total]
|
92
|
+
|
93
|
+
log_parts = [
|
94
|
+
"event=lm_request",
|
95
|
+
"provider=#{provider}",
|
96
|
+
"model=#{model}",
|
97
|
+
"status=#{status}",
|
98
|
+
"duration_ms=#{duration}"
|
99
|
+
]
|
100
|
+
log_parts << "tokens=#{tokens}" if tokens
|
101
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
102
|
+
|
103
|
+
logger.info(log_parts.join(' '))
|
97
104
|
end
|
98
105
|
|
99
106
|
sig { params(event: T.untyped).void }
|
@@ -104,13 +111,16 @@ module DSPy
|
|
104
111
|
status = payload[:status]
|
105
112
|
input_size = payload[:input_size]
|
106
113
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
+
log_parts = [
|
115
|
+
"event=prediction",
|
116
|
+
"signature=#{signature}",
|
117
|
+
"status=#{status}",
|
118
|
+
"duration_ms=#{duration}"
|
119
|
+
]
|
120
|
+
log_parts << "input_size=#{input_size}" if input_size
|
121
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
122
|
+
|
123
|
+
logger.info(log_parts.join(' '))
|
114
124
|
end
|
115
125
|
|
116
126
|
sig { params(event: T.untyped).void }
|
@@ -122,14 +132,17 @@ module DSPy
|
|
122
132
|
reasoning_steps = payload[:reasoning_steps]
|
123
133
|
reasoning_length = payload[:reasoning_length]
|
124
134
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
135
|
+
log_parts = [
|
136
|
+
"event=chain_of_thought",
|
137
|
+
"signature=#{signature}",
|
138
|
+
"status=#{status}",
|
139
|
+
"duration_ms=#{duration}"
|
140
|
+
]
|
141
|
+
log_parts << "reasoning_steps=#{reasoning_steps}" if reasoning_steps
|
142
|
+
log_parts << "reasoning_length=#{reasoning_length}" if reasoning_length
|
143
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
144
|
+
|
145
|
+
logger.info(log_parts.join(' '))
|
133
146
|
end
|
134
147
|
|
135
148
|
sig { params(event: T.untyped).void }
|
@@ -142,20 +155,18 @@ module DSPy
|
|
142
155
|
tools_used = payload[:tools_used]
|
143
156
|
final_answer = payload[:final_answer]
|
144
157
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
@logger.error(" Error: #{payload[:error_message]}")
|
158
|
-
end
|
158
|
+
log_parts = [
|
159
|
+
"event=react",
|
160
|
+
"signature=#{signature}",
|
161
|
+
"status=#{status}",
|
162
|
+
"duration_ms=#{duration}"
|
163
|
+
]
|
164
|
+
log_parts << "iterations=#{iteration_count}" if iteration_count
|
165
|
+
log_parts << "tools_used=\"#{tools_used.join(',')}\"" if tools_used&.any?
|
166
|
+
log_parts << "final_answer=\"#{final_answer&.truncate(100)}\"" if final_answer
|
167
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
168
|
+
|
169
|
+
logger.info(log_parts.join(' '))
|
159
170
|
end
|
160
171
|
|
161
172
|
sig { params(event: T.untyped).void }
|
@@ -167,14 +178,17 @@ module DSPy
|
|
167
178
|
duration = payload[:duration_ms]&.round(2)
|
168
179
|
status = payload[:status]
|
169
180
|
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
181
|
+
log_parts = [
|
182
|
+
"event=react_iteration",
|
183
|
+
"iteration=#{iteration}",
|
184
|
+
"status=#{status}",
|
185
|
+
"duration_ms=#{duration}"
|
186
|
+
]
|
187
|
+
log_parts << "thought=\"#{thought&.truncate(100)}\"" if thought
|
188
|
+
log_parts << "action=\"#{action}\"" if action
|
189
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
190
|
+
|
191
|
+
logger.info(log_parts.join(' '))
|
178
192
|
end
|
179
193
|
|
180
194
|
sig { params(event: T.untyped).void }
|
@@ -185,12 +199,16 @@ module DSPy
|
|
185
199
|
duration = payload[:duration_ms]&.round(2)
|
186
200
|
status = payload[:status]
|
187
201
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
202
|
+
log_parts = [
|
203
|
+
"event=tool_call",
|
204
|
+
"tool=#{tool_name}",
|
205
|
+
"iteration=#{iteration}",
|
206
|
+
"status=#{status}",
|
207
|
+
"duration_ms=#{duration}"
|
208
|
+
]
|
209
|
+
log_parts << "error=\"#{payload[:error_message]}\"" if status == 'error' && payload[:error_message]
|
210
|
+
|
211
|
+
logger.info(log_parts.join(' '))
|
194
212
|
end
|
195
213
|
end
|
196
214
|
end
|
data/lib/dspy.rb
CHANGED
@@ -11,23 +11,6 @@ module DSPy
|
|
11
11
|
def self.logger
|
12
12
|
config.logger
|
13
13
|
end
|
14
|
-
|
15
|
-
# Convenient instrumentation configuration
|
16
|
-
def self.configure_instrumentation(&block)
|
17
|
-
require_relative 'dspy/instrumentation'
|
18
|
-
require_relative 'dspy/instrumentation/dry_monitor_bridge'
|
19
|
-
|
20
|
-
Instrumentation.configure(&block)
|
21
|
-
|
22
|
-
# Setup dry-monitor bridge for HTTP instrumentation
|
23
|
-
if Instrumentation.config.enabled
|
24
|
-
Instrumentation::DryMonitorBridge.setup!
|
25
|
-
|
26
|
-
# Auto-enable logger subscriber for event-based logging
|
27
|
-
require_relative 'dspy/subscribers/logger_subscriber'
|
28
|
-
@logger_subscriber ||= Subscribers::LoggerSubscriber.new
|
29
|
-
end
|
30
|
-
end
|
31
14
|
end
|
32
15
|
|
33
16
|
require_relative 'dspy/module'
|
@@ -39,3 +22,6 @@ require_relative 'dspy/chain_of_thought'
|
|
39
22
|
require_relative 'dspy/re_act'
|
40
23
|
require_relative 'dspy/subscribers/logger_subscriber'
|
41
24
|
require_relative 'dspy/tools'
|
25
|
+
require_relative 'dspy/instrumentation'
|
26
|
+
|
27
|
+
# LoggerSubscriber will be lazy-initialized when first accessed
|