activeagents-telemetry-ruby_llm 0.3.1 → 0.3.2
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 +60 -4
- data/lib/activeagents/telemetry/ruby_llm/version.rb +1 -1
- data/lib/activeagents/telemetry/ruby_llm.rb +80 -5
- 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: e97d04ad7846320b7c233f247ce4ecad0ea2f20dba9574a73d2c3f93d3a47bfd
|
|
4
|
+
data.tar.gz: 50e301417298577aad66eb5d8423689d2c24742e5cc91be7a5fd55f518695c22
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 759dd7ce43f5c74c4eef4003ec72e2254e618523a132fcd0e7d8c249416fb2c8a4d96170d2984d7a6c352ca95ff4240d1beff259d8d96b410807e02836660199
|
|
7
|
+
data.tar.gz: 2e49c0ec5f861e55fc42304ea30e3f27e5bcefe8043e841d54cb2bcb8bc39c95870793baa87d6128006ada7e5b8e12dc3aeeaa83ad5fd5d4b277c663b64e2d3c
|
data/README.md
CHANGED
|
@@ -124,10 +124,66 @@ exceptions. A turn keeps the scope it started under, so a turn left open by a
|
|
|
124
124
|
pending tool call and closed later by `flush!` still reports with this agent,
|
|
125
125
|
attributes and callback, and a turn that started outside any scope never adopts
|
|
126
126
|
one. Context
|
|
127
|
-
is restored after the block, including when it raises
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
127
|
+
is restored after the block, including when it raises. A callback error is
|
|
128
|
+
logged by exception class without dropping the trace. Attribute redaction and
|
|
129
|
+
content-capture settings continue to apply.
|
|
130
|
+
|
|
131
|
+
### Nested scopes
|
|
132
|
+
|
|
133
|
+
Scopes compose, so an evaluation can wrap code that names its own agent
|
|
134
|
+
without losing what the evaluation attached. When a `with_agent` block opens
|
|
135
|
+
inside another one:
|
|
136
|
+
|
|
137
|
+
| Setting | Nested scope inside a plain scope | Nested scope inside a `pin: true` scope |
|
|
138
|
+
|---------|-----------------------------------|------------------------------------------|
|
|
139
|
+
| `attributes` | Merged: enclosing first, nested keys win on collision | Merged the same way |
|
|
140
|
+
| `on_trace` | Inherited unless the nested call passes its own | The pinned scope's callback stays in force |
|
|
141
|
+
| `synchronous` | Inherited unless the nested call passes its own (`nil`, the default, inherits; an explicit `false` overrides an inherited `true`) | The pinned scope's setting stays in force |
|
|
142
|
+
| `name` / `action` | Replaced by the nested scope | The pinned scope's identity stays in force |
|
|
143
|
+
|
|
144
|
+
A pinned scope owns the turn's identity: while it is active, a nested
|
|
145
|
+
`with_agent` may only add attributes, and it stays pinned however deep the
|
|
146
|
+
nesting goes. The previous scope is restored exactly on exit, including when
|
|
147
|
+
the block raises. The `agent.*` identity keys are always written last, so an
|
|
148
|
+
attribute with one of those names never overrides the reported agent.
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
ActiveAgents::Telemetry::RubyLLM.with_agent(
|
|
152
|
+
"SupportAgent", action: "replay",
|
|
153
|
+
attributes: { "eval.run_id" => run_id, "eval.result_id" => result_id },
|
|
154
|
+
on_trace: ->(trace) { result.trace_id = trace.trace_id },
|
|
155
|
+
synchronous: true, pin: true
|
|
156
|
+
) do
|
|
157
|
+
# The code under test names its own scope around the actual call. The
|
|
158
|
+
# trace reports as SupportAgent.replay with eval.* and sparkle.role, the
|
|
159
|
+
# evaluation's on_trace receives it, and it is delivered before this
|
|
160
|
+
# block returns.
|
|
161
|
+
ActiveAgents::Telemetry::RubyLLM.with_agent("SupportAgent", action: "respond",
|
|
162
|
+
attributes: { "sparkle.role" => "assistant" }) do
|
|
163
|
+
chat.ask(prompt)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Correlating with ActiveAgent evaluations
|
|
169
|
+
|
|
170
|
+
`ActiveAgent::Evals::Correlation` (activeagent >= 1.6.3) takes a `tracer:`
|
|
171
|
+
that it calls as `tracer.call(name, action:, attributes:, on_trace:) { ... }`
|
|
172
|
+
around each replay, and expects `on_trace` to receive something answering
|
|
173
|
+
`#trace_id`. `correlation_tracer` returns exactly that lambda, opening a
|
|
174
|
+
pinned, synchronous `with_agent` scope with the arguments it is given and
|
|
175
|
+
returning the block's value:
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
ActiveAgent::Evals::Correlation.new(
|
|
179
|
+
agent_name: "SupportAgent",
|
|
180
|
+
tracer: ActiveAgents::Telemetry::RubyLLM.correlation_tracer
|
|
181
|
+
)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`correlation_tracer(synchronous: false)` or `correlation_tracer(pin: false)`
|
|
185
|
+
relaxes either default. This gem does not depend on activeagent; the only
|
|
186
|
+
contract shared with it is the lambda's signature.
|
|
131
187
|
|
|
132
188
|
Report publication and trace ingestion are separate operations. Persist each run
|
|
133
189
|
and result ID in the evaluation report and attach the same IDs to its response and
|
|
@@ -105,17 +105,71 @@ module ActiveAgents
|
|
|
105
105
|
# `on_trace` runs only for a trace the reporter accepted, which means
|
|
106
106
|
# it passed the enabled, configured and sampling checks: a delivery
|
|
107
107
|
# that then fails is logged by the reporter, not announced here.
|
|
108
|
-
|
|
108
|
+
#
|
|
109
|
+
# Scopes compose. Inside an enclosing scope, a nested call merges its
|
|
110
|
+
# `attributes` onto the enclosing ones (a nested key wins on
|
|
111
|
+
# collision) and inherits `on_trace` and `synchronous` unless it
|
|
112
|
+
# passes its own; `synchronous: nil`, the default, means "inherit,
|
|
113
|
+
# else false", so an explicit `synchronous: false` still overrides an
|
|
114
|
+
# inherited `true`. Without `pin`, the nested scope replaces `name`
|
|
115
|
+
# and `action`, so a call site that names its own agent keeps doing
|
|
116
|
+
# so under an evaluation's scope. A scope opened with `pin: true` owns
|
|
117
|
+
# the turn's identity: while it is active a nested `with_agent` may
|
|
118
|
+
# only add attributes, and the pinned scope's `name`, `action`,
|
|
119
|
+
# `on_trace` and `synchronous` stay in force whatever the nested call
|
|
120
|
+
# passes. An evaluation replay pins its scope so the code under test,
|
|
121
|
+
# which opens its own scope around the actual `chat.ask`, cannot
|
|
122
|
+
# detach the trace from the evaluation's attributes, callback and
|
|
123
|
+
# synchronous delivery. The previous scope is restored on exit,
|
|
124
|
+
# including when the block raises.
|
|
125
|
+
#
|
|
126
|
+
# @param synchronous [Boolean, nil] nil inherits the enclosing scope's
|
|
127
|
+
# setting, or false at the top level.
|
|
128
|
+
# @param pin [Boolean] whether this scope keeps its identity, callback
|
|
129
|
+
# and delivery mode against nested scopes.
|
|
130
|
+
def with_agent(name, action: "chat", attributes: {}, on_trace: nil, synchronous: nil, pin: false)
|
|
109
131
|
previous = Thread.current[AGENT_KEY]
|
|
110
|
-
Thread.current[AGENT_KEY] =
|
|
111
|
-
name: name, action: action, attributes: attributes
|
|
112
|
-
on_trace: on_trace, synchronous: synchronous
|
|
113
|
-
|
|
132
|
+
Thread.current[AGENT_KEY] = compose_scope(
|
|
133
|
+
previous, name: name, action: action, attributes: attributes,
|
|
134
|
+
on_trace: on_trace, synchronous: synchronous, pin: pin
|
|
135
|
+
)
|
|
114
136
|
yield
|
|
115
137
|
ensure
|
|
116
138
|
Thread.current[AGENT_KEY] = previous
|
|
117
139
|
end
|
|
118
140
|
|
|
141
|
+
# A `tracer:` for `ActiveAgent::Evals::Correlation` (activeagent >=
|
|
142
|
+
# 1.6.3), which wraps each evaluation replay in
|
|
143
|
+
# `tracer.call(name, action:, attributes:, on_trace:) { ... }` and
|
|
144
|
+
# expects `on_trace` to receive something answering `#trace_id`. The
|
|
145
|
+
# returned lambda opens a pinned, synchronous `with_agent` scope with
|
|
146
|
+
# exactly those arguments, so the replayed code's own `with_agent`
|
|
147
|
+
# around `chat.ask` can add attributes but cannot replace the
|
|
148
|
+
# evaluation's identity, drop its `on_trace` or turn delivery
|
|
149
|
+
# asynchronous, and the trace id the callback receives is the one
|
|
150
|
+
# the reporter delivered before the replay returns. The block's value
|
|
151
|
+
# is returned. There is no dependency on activeagent: the contract is
|
|
152
|
+
# only the lambda's signature.
|
|
153
|
+
#
|
|
154
|
+
# ActiveAgent::Evals::Correlation.new(
|
|
155
|
+
# agent_name: "SupportAgent",
|
|
156
|
+
# tracer: ActiveAgents::Telemetry::RubyLLM.correlation_tracer
|
|
157
|
+
# )
|
|
158
|
+
#
|
|
159
|
+
# @param synchronous [Boolean] whether the scope delivers in the
|
|
160
|
+
# calling thread; true keeps the trace id available when the
|
|
161
|
+
# replay returns and before a short-lived process exits.
|
|
162
|
+
# @param pin [Boolean] whether nested scopes may only add attributes.
|
|
163
|
+
# @return [Proc] `->(name, action:, attributes:, on_trace:, &block)`
|
|
164
|
+
def correlation_tracer(synchronous: true, pin: true)
|
|
165
|
+
lambda do |name, action: "chat", attributes: {}, on_trace: nil, &block|
|
|
166
|
+
with_agent(
|
|
167
|
+
name, action: action, attributes: attributes, on_trace: on_trace,
|
|
168
|
+
synchronous: synchronous, pin: pin, &block
|
|
169
|
+
)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
|
|
119
173
|
def state
|
|
120
174
|
Thread.current[STATE_KEY] ||= State.new(0, nil, [], 0, Span::ZERO_TOKENS.dup, nil, nil)
|
|
121
175
|
end
|
|
@@ -182,6 +236,27 @@ module ActiveAgents
|
|
|
182
236
|
|
|
183
237
|
private
|
|
184
238
|
|
|
239
|
+
# The scope a nested `with_agent` installs, given the enclosing one.
|
|
240
|
+
# Attributes always merge with the nested keys winning; the rest
|
|
241
|
+
# depends on whether the enclosing scope is pinned.
|
|
242
|
+
def compose_scope(enclosing, name:, action:, attributes:, on_trace:, synchronous:, pin:)
|
|
243
|
+
own = attributes.to_h.transform_keys(&:to_s)
|
|
244
|
+
if enclosing.nil?
|
|
245
|
+
return { name: name, action: action, attributes: own, on_trace: on_trace,
|
|
246
|
+
synchronous: synchronous ? true : false, pin: pin ? true : false }
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
merged = (enclosing[:attributes] || {}).merge(own)
|
|
250
|
+
return enclosing.merge(attributes: merged) if enclosing[:pin]
|
|
251
|
+
|
|
252
|
+
{
|
|
253
|
+
name: name, action: action, attributes: merged,
|
|
254
|
+
on_trace: on_trace.nil? ? enclosing[:on_trace] : on_trace,
|
|
255
|
+
synchronous: synchronous.nil? ? enclosing[:synchronous] : synchronous,
|
|
256
|
+
pin: pin ? true : false
|
|
257
|
+
}
|
|
258
|
+
end
|
|
259
|
+
|
|
185
260
|
def report_turn(payload, turn)
|
|
186
261
|
agent = turn.agent || resolve_agent(payload) || DEFAULT_AGENT
|
|
187
262
|
started_at = turn.started_at || Time.now
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: activeagents-telemetry-ruby_llm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ActiveAgents
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-10-07 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activeagents-telemetry
|