dspy 0.30.0 → 0.30.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 +57 -13
- data/lib/dspy/callbacks.rb +21 -2
- data/lib/dspy/context.rb +52 -1
- data/lib/dspy/module.rb +213 -17
- data/lib/dspy/version.rb +1 -1
- data/lib/dspy.rb +6 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 0bdf656ce5715a455d3fb36427878ae507d8d4bd9a7f092a6b9752196f9ebc4b
|
|
4
|
+
data.tar.gz: cd21540e0eea82c1567c085b9ef2fd5c2d9742cd1ff08f855a16f4109893ebb2
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 992c191cbc6bbdd1bc770a83d4a1151869c4f57a20d7236a19b0add9c50329ec2dea901fe440488b2194fe96951d729c4509d33a9df8fb6c2dc7154c570e9c00
|
|
7
|
+
data.tar.gz: 82e67a674db952801adf40407d34daf0e2808f4c1b4d99da60f63c33fd2033b7f5a2deaefe63ea91bf4bcc7129e7580772877df3ca3b69bf9894d2454a5bdf38
|
data/README.md
CHANGED
|
@@ -47,23 +47,67 @@ and
|
|
|
47
47
|
bundle install
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
-
###
|
|
50
|
+
### Your First Reliable Predictor
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
|
|
54
|
+
# Configure DSPy globablly to use your fave LLM - you can override this on an instance levle.
|
|
55
|
+
DSPy.configure do |c|
|
|
56
|
+
c.lm = DSPy::LM.new('openai/gpt-4o-mini',
|
|
57
|
+
api_key: ENV['OPENAI_API_KEY'],
|
|
58
|
+
structured_outputs: true) # Enable OpenAI's native JSON mode
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Define a signature for sentiment classification - instead of writing a full prompt!
|
|
62
|
+
class Classify < DSPy::Signature
|
|
63
|
+
description "Classify sentiment of a given sentence." # sets the goal of the underlying prompt
|
|
64
|
+
|
|
65
|
+
class Sentiment < T::Enum
|
|
66
|
+
enums do
|
|
67
|
+
Positive = new('positive')
|
|
68
|
+
Negative = new('negative')
|
|
69
|
+
Neutral = new('neutral')
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Structured Inputs: makes sure you are sending only valid prompt inputs to your model
|
|
74
|
+
input do
|
|
75
|
+
const :sentence, String, description: 'The sentence to analyze'
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Structured Outputs: your predictor will validate the output of the model too.
|
|
79
|
+
output do
|
|
80
|
+
const :sentiment, Sentiment, description: 'The sentiment of the sentence'
|
|
81
|
+
const :confidence, Float, description: 'A number between 0.0 and 1.0'
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Wire it to the simplest prompting technique - a Predictn.
|
|
86
|
+
classify = DSPy::Predict.new(Classify)
|
|
87
|
+
# it may raise an error if you mess the inputs or your LLM messes the outputs.
|
|
88
|
+
result = classify.call(sentence: "This book was super fun to read!")
|
|
89
|
+
|
|
90
|
+
puts result.sentiment # => #<Sentiment::Positive>
|
|
91
|
+
puts result.confidence # => 0.85
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Sibling Gems
|
|
51
95
|
|
|
52
|
-
DSPy.rb ships multiple gems from this monorepo so you only
|
|
96
|
+
DSPy.rb ships multiple gems from this monorepo so you can opt into features with heavier dependency trees (e.g., datasets pull in Polars/Arrow, MIPROv2 requires `numo-*` BLAS bindings) only when you need them. Add these alongside `dspy`:
|
|
53
97
|
|
|
54
98
|
| Gem | Description | Status |
|
|
55
99
|
| --- | --- | --- |
|
|
56
|
-
| `dspy-schema` | Exposes `DSPy::TypeSystem::SorbetJsonSchema` for downstream reuse. | **Stable** (v1.0.0) |
|
|
57
|
-
| `dspy-code_act` | Think-Code-Observe agents that synthesize and execute Ruby safely. |
|
|
58
|
-
| `dspy-datasets` | Dataset helpers plus Parquet/Polars tooling for richer evaluation corpora. |
|
|
59
|
-
| `dspy-evals` | High-throughput evaluation harness with metrics, callbacks, and regression fixtures. |
|
|
60
|
-
| `dspy-miprov2` | Bayesian optimization + Gaussian Process backend for the MIPROv2 teleprompter. |
|
|
61
|
-
| `dspy-gepa` | `DSPy::Teleprompt::GEPA`, reflection loops, experiment tracking, telemetry adapters.
|
|
62
|
-
| `gepa` | GEPA optimizer core (Pareto engine, telemetry, reflective proposer). |
|
|
63
|
-
| `dspy-o11y` | Core observability APIs: `DSPy::Observability`, async span processor, observation types. | **Stable** (v1.0.0) |
|
|
64
|
-
| `dspy-o11y-langfuse` | Auto-configures DSPy observability to stream spans to Langfuse via OTLP. | **Stable** (v1.0.0) |
|
|
65
|
-
|
|
66
|
-
Set the matching `DSPY_WITH_*` environment variables (see `Gemfile`) to include or exclude each sibling gem when running Bundler locally (for example `DSPY_WITH_GEPA=1` or `DSPY_WITH_O11Y_LANGFUSE=1`). Refer to `
|
|
100
|
+
| `dspy-schema` | Exposes `DSPy::TypeSystem::SorbetJsonSchema` for downstream reuse. (Still required by the core `dspy` gem; extraction lets other projects depend on it directly.) | **Stable** (v1.0.0) |
|
|
101
|
+
| `dspy-code_act` | Think-Code-Observe agents that synthesize and execute Ruby safely. (Add the gem or set `DSPY_WITH_CODE_ACT=1` before requiring `dspy/code_act`.) | **Stable** (v1.0.0) |
|
|
102
|
+
| `dspy-datasets` | Dataset helpers plus Parquet/Polars tooling for richer evaluation corpora. (Toggle via `DSPY_WITH_DATASETS`.) | **Stable** (v1.0.0) |
|
|
103
|
+
| `dspy-evals` | High-throughput evaluation harness with metrics, callbacks, and regression fixtures. (Toggle via `DSPY_WITH_EVALS`.) | **Stable** (v1.0.0) |
|
|
104
|
+
| `dspy-miprov2` | Bayesian optimization + Gaussian Process backend for the MIPROv2 teleprompter. (Install or export `DSPY_WITH_MIPROV2=1` before requiring the teleprompter.) | **Stable** (v1.0.0) |
|
|
105
|
+
| `dspy-gepa` | `DSPy::Teleprompt::GEPA`, reflection loops, experiment tracking, telemetry adapters. (Install or set `DSPY_WITH_GEPA=1`.) | **Stable** (v1.0.0) |
|
|
106
|
+
| `gepa` | GEPA optimizer core (Pareto engine, telemetry, reflective proposer). | **Stable** (v1.0.0) |
|
|
107
|
+
| `dspy-o11y` | Core observability APIs: `DSPy::Observability`, async span processor, observation types. (Install or set `DSPY_WITH_O11Y=1`.) | **Stable** (v1.0.0) |
|
|
108
|
+
| `dspy-o11y-langfuse` | Auto-configures DSPy observability to stream spans to Langfuse via OTLP. (Install or set `DSPY_WITH_O11Y_LANGFUSE=1`.) | **Stable** (v1.0.0) |
|
|
109
|
+
|
|
110
|
+
Set the matching `DSPY_WITH_*` environment variables (see `Gemfile`) to include or exclude each sibling gem when running Bundler locally (for example `DSPY_WITH_GEPA=1` or `DSPY_WITH_O11Y_LANGFUSE=1`). Refer to `adr/013-dependency-tree.md` for the full dependency map and roadmap.
|
|
67
111
|
### Your First Reliable Predictor
|
|
68
112
|
|
|
69
113
|
```ruby
|
data/lib/dspy/callbacks.rb
CHANGED
|
@@ -259,15 +259,34 @@ module DSPy
|
|
|
259
259
|
# Executes method with around callbacks
|
|
260
260
|
def execute_with_around_callbacks(method_name, original_method, *args, **kwargs, &block)
|
|
261
261
|
callbacks = self.class.send(:callbacks_for, method_name)[:around]
|
|
262
|
+
args_copy = args.dup
|
|
263
|
+
kwargs_copy = kwargs.dup
|
|
262
264
|
|
|
263
265
|
# Build callback chain from innermost (original method) to outermost
|
|
264
266
|
chain = callbacks.reverse.inject(
|
|
265
267
|
-> { original_method.bind(self).call(*args, **kwargs, &block) }
|
|
266
268
|
) do |inner, callback|
|
|
267
269
|
if callback.is_a?(Proc)
|
|
268
|
-
->
|
|
270
|
+
-> do
|
|
271
|
+
next_proc = -> { inner.call }
|
|
272
|
+
proc_arity = callback.arity
|
|
273
|
+
expects_extra = proc_arity.abs > 1
|
|
274
|
+
|
|
275
|
+
if expects_extra
|
|
276
|
+
instance_exec(next_proc, args_copy, kwargs_copy, &callback)
|
|
277
|
+
else
|
|
278
|
+
instance_exec(next_proc, &callback)
|
|
279
|
+
end
|
|
280
|
+
end
|
|
269
281
|
else
|
|
270
|
-
->
|
|
282
|
+
-> do
|
|
283
|
+
method_obj = method(callback)
|
|
284
|
+
if method_obj.arity.zero?
|
|
285
|
+
send(callback) { inner.call }
|
|
286
|
+
else
|
|
287
|
+
send(callback, args_copy, kwargs_copy) { inner.call }
|
|
288
|
+
end
|
|
289
|
+
end
|
|
271
290
|
end
|
|
272
291
|
end
|
|
273
292
|
|
data/lib/dspy/context.rb
CHANGED
|
@@ -33,7 +33,8 @@ module DSPy
|
|
|
33
33
|
context = {
|
|
34
34
|
trace_id: SecureRandom.uuid,
|
|
35
35
|
span_stack: [],
|
|
36
|
-
otel_span_stack: []
|
|
36
|
+
otel_span_stack: [],
|
|
37
|
+
module_stack: []
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
# Set in both Thread and Fiber storage
|
|
@@ -158,6 +159,48 @@ module DSPy
|
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
end
|
|
162
|
+
|
|
163
|
+
def with_module(module_instance, label: nil)
|
|
164
|
+
stack = module_stack
|
|
165
|
+
entry = build_module_entry(module_instance, label)
|
|
166
|
+
stack.push(entry)
|
|
167
|
+
yield
|
|
168
|
+
ensure
|
|
169
|
+
if stack.last.equal?(entry)
|
|
170
|
+
stack.pop
|
|
171
|
+
else
|
|
172
|
+
stack.delete(entry)
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def module_stack
|
|
177
|
+
current[:module_stack] ||= []
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def module_context_attributes
|
|
181
|
+
stack = module_stack
|
|
182
|
+
return {} if stack.empty?
|
|
183
|
+
|
|
184
|
+
path = stack.map do |entry|
|
|
185
|
+
{
|
|
186
|
+
id: entry[:id],
|
|
187
|
+
class: entry[:class],
|
|
188
|
+
label: entry[:label]
|
|
189
|
+
}
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
ancestry_token = path.map { |node| node[:id] }.join('>')
|
|
193
|
+
|
|
194
|
+
{
|
|
195
|
+
module_path: path,
|
|
196
|
+
module_root: path.first,
|
|
197
|
+
module_leaf: path.last,
|
|
198
|
+
module_scope: {
|
|
199
|
+
ancestry_token: ancestry_token,
|
|
200
|
+
depth: path.length
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
end
|
|
161
204
|
|
|
162
205
|
def clear!
|
|
163
206
|
# Clear both the thread-specific key and the legacy key
|
|
@@ -218,6 +261,14 @@ module DSPy
|
|
|
218
261
|
end
|
|
219
262
|
end
|
|
220
263
|
end
|
|
264
|
+
|
|
265
|
+
def build_module_entry(module_instance, explicit_label)
|
|
266
|
+
{
|
|
267
|
+
id: (module_instance.respond_to?(:module_scope_id) ? module_instance.module_scope_id : SecureRandom.uuid),
|
|
268
|
+
class: module_instance.class.name,
|
|
269
|
+
label: explicit_label || (module_instance.respond_to?(:module_scope_label) ? module_instance.module_scope_label : nil)
|
|
270
|
+
}
|
|
271
|
+
end
|
|
221
272
|
end
|
|
222
273
|
end
|
|
223
274
|
end
|
data/lib/dspy/module.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require 'sorbet-runtime'
|
|
4
4
|
require 'dry-configurable'
|
|
5
|
+
require 'securerandom'
|
|
5
6
|
require_relative 'context'
|
|
6
7
|
require_relative 'callbacks'
|
|
7
8
|
|
|
@@ -12,10 +13,84 @@ module DSPy
|
|
|
12
13
|
include Dry::Configurable
|
|
13
14
|
include DSPy::Callbacks
|
|
14
15
|
|
|
16
|
+
class SubcriptionScope < T::Enum
|
|
17
|
+
enums do
|
|
18
|
+
Descendants = new('descendants')
|
|
19
|
+
SelfOnly = new('self')
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
DEFAULT_MODULE_SUBSCRIPTION_SCOPE = SubcriptionScope::Descendants
|
|
24
|
+
|
|
25
|
+
module ForwardOverrideHooks
|
|
26
|
+
def method_added(method_name)
|
|
27
|
+
super
|
|
28
|
+
|
|
29
|
+
return unless method_name == :forward
|
|
30
|
+
return if self == DSPy::Module
|
|
31
|
+
return if @_wrapping_forward
|
|
32
|
+
|
|
33
|
+
@_wrapping_forward = true
|
|
34
|
+
|
|
35
|
+
original = instance_method(:forward)
|
|
36
|
+
define_method(:forward) do |*args, **kwargs, &block|
|
|
37
|
+
instrument_forward_call(args, kwargs) do
|
|
38
|
+
original.bind(self).call(*args, **kwargs, &block)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
ensure
|
|
42
|
+
@_wrapping_forward = false
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
class << self
|
|
47
|
+
def inherited(subclass)
|
|
48
|
+
super
|
|
49
|
+
specs_copy = module_subscription_specs.map(&:dup)
|
|
50
|
+
subclass.instance_variable_set(:@module_subscription_specs, specs_copy)
|
|
51
|
+
subclass.extend(ForwardOverrideHooks)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def subscribe(pattern, handler = nil, scope: DEFAULT_MODULE_SUBSCRIPTION_SCOPE, &block)
|
|
55
|
+
scope = normalize_scope(scope)
|
|
56
|
+
raise ArgumentError, 'Provide a handler method or block' if handler.nil? && block.nil?
|
|
57
|
+
|
|
58
|
+
module_subscription_specs << {
|
|
59
|
+
pattern: pattern,
|
|
60
|
+
handler: handler,
|
|
61
|
+
block: block,
|
|
62
|
+
scope: scope
|
|
63
|
+
}
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def module_subscription_specs
|
|
67
|
+
@module_subscription_specs ||= []
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def validate_subscription_scope!(scope)
|
|
73
|
+
T.must(scope)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def normalize_scope(scope)
|
|
77
|
+
return scope if scope.is_a?(SubcriptionScope)
|
|
78
|
+
|
|
79
|
+
case scope
|
|
80
|
+
when :descendants
|
|
81
|
+
SubcriptionScope::Descendants
|
|
82
|
+
when :self
|
|
83
|
+
SubcriptionScope::SelfOnly
|
|
84
|
+
else
|
|
85
|
+
raise ArgumentError, "Unsupported subscription scope: #{scope.inspect}"
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
15
90
|
# Per-instance LM configuration
|
|
16
91
|
setting :lm, default: nil
|
|
17
92
|
|
|
18
|
-
#
|
|
93
|
+
# Enable callback hooks for forward method
|
|
19
94
|
create_before_callback :forward
|
|
20
95
|
create_after_callback :forward
|
|
21
96
|
create_around_callback :forward
|
|
@@ -29,23 +104,8 @@ module DSPy
|
|
|
29
104
|
.returns(T.type_parameter(:O))
|
|
30
105
|
end
|
|
31
106
|
def forward(**input_values)
|
|
32
|
-
|
|
33
|
-
observation_type = DSPy::ObservationType.for_module_class(self.class)
|
|
34
|
-
DSPy::Context.with_span(
|
|
35
|
-
operation: "#{self.class.name}.forward",
|
|
36
|
-
**observation_type.langfuse_attributes,
|
|
37
|
-
'langfuse.observation.input' => input_values.to_json,
|
|
38
|
-
'dspy.module' => self.class.name
|
|
39
|
-
) do |span|
|
|
107
|
+
instrument_forward_call([], input_values) do
|
|
40
108
|
result = forward_untyped(**input_values)
|
|
41
|
-
|
|
42
|
-
# Add output to span
|
|
43
|
-
if span && result
|
|
44
|
-
output_json = result.respond_to?(:to_h) ? result.to_h.to_json : result.to_json rescue result.to_s
|
|
45
|
-
span.set_attribute('langfuse.observation.output', output_json)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
# Cast the result of forward_untyped to the expected output type
|
|
49
109
|
T.cast(result, T.type_parameter(:O))
|
|
50
110
|
end
|
|
51
111
|
end
|
|
@@ -116,5 +176,141 @@ module DSPy
|
|
|
116
176
|
def predictors
|
|
117
177
|
named_predictors.map { |(_, predictor)| predictor }
|
|
118
178
|
end
|
|
179
|
+
|
|
180
|
+
def instrument_forward_call(call_args, call_kwargs)
|
|
181
|
+
ensure_module_subscriptions!
|
|
182
|
+
|
|
183
|
+
DSPy::Context.with_module(self) do
|
|
184
|
+
observation_type = DSPy::ObservationType.for_module_class(self.class)
|
|
185
|
+
span_attributes = observation_type.langfuse_attributes.merge(
|
|
186
|
+
'langfuse.observation.input' => serialize_module_input(call_args, call_kwargs),
|
|
187
|
+
'dspy.module' => self.class.name
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
DSPy::Context.with_span(
|
|
191
|
+
operation: "#{self.class.name}.forward",
|
|
192
|
+
**span_attributes
|
|
193
|
+
) do |span|
|
|
194
|
+
yield.tap do |result|
|
|
195
|
+
if span && result
|
|
196
|
+
span.set_attribute('langfuse.observation.output', serialize_module_output(result))
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def serialize_module_input(call_args, call_kwargs)
|
|
204
|
+
payload = if call_kwargs && !call_kwargs.empty?
|
|
205
|
+
call_kwargs
|
|
206
|
+
elsif call_args && !call_args.empty?
|
|
207
|
+
call_args
|
|
208
|
+
else
|
|
209
|
+
{}
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
payload.to_json
|
|
213
|
+
rescue StandardError
|
|
214
|
+
payload.to_s
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
def serialize_module_output(result)
|
|
218
|
+
if result.respond_to?(:to_h)
|
|
219
|
+
result.to_h.to_json
|
|
220
|
+
else
|
|
221
|
+
result.to_json
|
|
222
|
+
end
|
|
223
|
+
rescue StandardError
|
|
224
|
+
result.to_s
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
private :instrument_forward_call, :serialize_module_input, :serialize_module_output
|
|
228
|
+
|
|
229
|
+
sig { returns(String) }
|
|
230
|
+
def module_scope_id
|
|
231
|
+
@module_scope_id ||= SecureRandom.uuid
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
sig { returns(T.nilable(String)) }
|
|
235
|
+
def module_scope_label
|
|
236
|
+
@module_scope_label
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
sig { params(label: T.nilable(String)).void }
|
|
240
|
+
def module_scope_label=(label)
|
|
241
|
+
@module_scope_label = label
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
sig { returns(T::Array[String]) }
|
|
245
|
+
def registered_module_subscriptions
|
|
246
|
+
Array(@module_subscription_ids).dup
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
sig { void }
|
|
250
|
+
def unsubscribe_module_events
|
|
251
|
+
Array(@module_subscription_ids).each { |id| DSPy.events.unsubscribe(id) }
|
|
252
|
+
@module_subscription_ids = []
|
|
253
|
+
@module_subscriptions_registered = false
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
private
|
|
257
|
+
|
|
258
|
+
def ensure_module_subscriptions!
|
|
259
|
+
return if @module_subscriptions_registered
|
|
260
|
+
|
|
261
|
+
specs = self.class.module_subscription_specs
|
|
262
|
+
if specs.empty?
|
|
263
|
+
@module_subscriptions_registered = true
|
|
264
|
+
return
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
@module_subscription_ids ||= []
|
|
268
|
+
specs.each do |spec|
|
|
269
|
+
callback = build_subscription_callback(spec)
|
|
270
|
+
subscription_id = DSPy.events.subscribe(spec[:pattern], &callback)
|
|
271
|
+
@module_subscription_ids << subscription_id
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
@module_subscriptions_registered = true
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def build_subscription_callback(spec)
|
|
278
|
+
scope = spec[:scope] || DEFAULT_MODULE_SUBSCRIPTION_SCOPE
|
|
279
|
+
handler = spec[:handler]
|
|
280
|
+
block = spec[:block]
|
|
281
|
+
|
|
282
|
+
proc do |event_name, attributes|
|
|
283
|
+
next unless module_event_within_scope?(attributes, scope)
|
|
284
|
+
|
|
285
|
+
if handler
|
|
286
|
+
send(handler, event_name, attributes)
|
|
287
|
+
else
|
|
288
|
+
instance_exec(event_name, attributes, &block)
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def module_event_within_scope?(attributes, scope)
|
|
294
|
+
metadata = extract_module_metadata(attributes)
|
|
295
|
+
return false unless metadata
|
|
296
|
+
|
|
297
|
+
case scope
|
|
298
|
+
when SubcriptionScope::SelfOnly
|
|
299
|
+
metadata[:leaf_id] == module_scope_id
|
|
300
|
+
else
|
|
301
|
+
metadata[:path_ids].include?(module_scope_id)
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def extract_module_metadata(attributes)
|
|
306
|
+
path = attributes[:module_path] || attributes['module_path']
|
|
307
|
+
leaf = attributes[:module_leaf] || attributes['module_leaf']
|
|
308
|
+
return nil unless path.is_a?(Array)
|
|
309
|
+
|
|
310
|
+
{
|
|
311
|
+
path_ids: path.map { |entry| entry[:id] || entry['id'] }.compact,
|
|
312
|
+
leaf_id: leaf&.dig(:id) || leaf&.dig('id')
|
|
313
|
+
}
|
|
314
|
+
end
|
|
119
315
|
end
|
|
120
316
|
end
|
data/lib/dspy/version.rb
CHANGED
data/lib/dspy.rb
CHANGED
|
@@ -75,6 +75,10 @@ module DSPy
|
|
|
75
75
|
create_event_span(event_name, attributes)
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
attributes = attributes.dup
|
|
79
|
+
module_metadata = DSPy::Context.module_context_attributes
|
|
80
|
+
attributes.merge!(module_metadata) unless module_metadata.empty?
|
|
81
|
+
|
|
78
82
|
# Perform the actual logging (original DSPy.log behavior)
|
|
79
83
|
# emit_log(event_name, attributes)
|
|
80
84
|
|
|
@@ -100,6 +104,8 @@ module DSPy
|
|
|
100
104
|
# Merge context automatically (but don't include span_stack)
|
|
101
105
|
context = Context.current.dup
|
|
102
106
|
context.delete(:span_stack)
|
|
107
|
+
context.delete(:otel_span_stack)
|
|
108
|
+
context.delete(:module_stack)
|
|
103
109
|
attributes = context.merge(attributes)
|
|
104
110
|
attributes[:event] = event_name
|
|
105
111
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dspy
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.30.
|
|
4
|
+
version: 0.30.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vicente Reig Rincón de Arellano
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-26 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dry-configurable
|