activeagents-telemetry-ruby_llm 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf9f1bb148ecabe70a08facf887f60c55dd08f6683370bcb03cb33d1abad0dd1
4
- data.tar.gz: 81d06e3973b08667dbb972fe1a013d1be3785b9d9c2b3804a4dfbe5ae67fbaa5
3
+ metadata.gz: 5abf7d85a5deec9d008170f9c9a3e50d619024b7bad8c1f01eea3eda3d3befc6
4
+ data.tar.gz: ea10cb9f3cb45700fe8ad0642c72eb3c6e01576c1e4353d274998ead5f3f7243
5
5
  SHA512:
6
- metadata.gz: 999b5249ec348dc4b5280aeddfb2c905af13b29948a85aed5b81ddfdfd2f920fd5d0607b0fd79313f5fad732de271e0db2d1ab23b7ce04e575bf93f90c967040
7
- data.tar.gz: 4ec2de8a1cb63605724e7497632b7463ee3e317c133d3bc05cc8131aa8ea1e9b379fdb0da172bf509809bb2ac193c2164bc3780b8844ed32c4c7225220475fb7
6
+ metadata.gz: 67e5b7d06a3de8510dead200b6ec9a4da44172d24b46c3ea02646cfd4fad1fc3c5acb960798e17a5957f61d8dcd4bd7efc392ae08f8e46ef19b084014e357073
7
+ data.tar.gz: 16dde44b30870de36415b0958b85eabff49caf639238baf140107e6b2a986effe21bf0eea529c78b5810d90816098cac8c35f09800304c3c5cddd3c7ab6b3c22
data/README.md CHANGED
@@ -92,3 +92,44 @@ an explicit `flush!`.
92
92
  ```bash
93
93
  bundle exec rake test
94
94
  ```
95
+
96
+ ## Correlating evaluation traces
97
+
98
+ Use a separate identity for judge calls and attach stable evaluation identifiers
99
+ without changing the application's default agent resolver:
100
+
101
+ ```ruby
102
+ trace_ids = []
103
+ ActiveAgents::Telemetry::RubyLLM.with_agent(
104
+ "EvaluationJudge", action: "score",
105
+ attributes: { "eval.run_id" => run_id, "eval.result_id" => result_id },
106
+ on_trace: ->(trace) { trace_ids << trace.trace_id },
107
+ synchronous: true
108
+ ) do
109
+ judge_chat.ask(prompt)
110
+ end
111
+ ```
112
+
113
+ The callback receives each trace the reporter accepted for delivery, so an
114
+ evaluation result can retain the trace ID of that delivery attempt. Acceptance is
115
+ not ingestion: the trace passed the enabled, configured and sampling checks, but
116
+ a delivery that then fails is logged by the reporter rather than announced here,
117
+ and an asynchronous delivery can outlive a process that exits right away. A trace
118
+ dropped by `sample_rate` or by a disabled configuration is never announced.
119
+ `synchronous: true` delivers in the calling thread for this scope only, through
120
+ the same sampling and configuration checks as ordinary delivery; it does not
121
+ mutate the shared async configuration. Normal reporter error logging still
122
+ applies: synchronous delivery does not turn telemetry failures into application
123
+ exceptions. A turn keeps the scope it started under, so a turn left open by a
124
+ pending tool call and closed later by `flush!` still reports with this agent,
125
+ attributes and callback, and a turn that started outside any scope never adopts
126
+ one. Context
127
+ is restored after the block, including when it raises, and nested scopes use
128
+ their own attributes. A callback error is logged by exception class without
129
+ dropping the trace. Attribute redaction and content-capture settings continue to
130
+ apply.
131
+
132
+ Report publication and trace ingestion are separate operations. Persist each run
133
+ and result ID in the evaluation report and attach the same IDs to its response and
134
+ judge trace attributes. The application chooses whether to publish full report
135
+ content; this API does not enable body capture or upload reports automatically.
@@ -3,7 +3,7 @@
3
3
  module ActiveAgents
4
4
  module Telemetry
5
5
  module RubyLLM
6
- VERSION = "0.2.0"
6
+ VERSION = "0.3.0"
7
7
  end
8
8
  end
9
9
  end
@@ -43,7 +43,7 @@ module ActiveAgents
43
43
 
44
44
  DEFAULT_AGENT = { name: "RubyLLM::Chat", action: "chat" }.freeze
45
45
 
46
- State = Struct.new(:depth, :started_at, :tool_spans, :rounds, :tokens, :chat_key)
46
+ State = Struct.new(:depth, :started_at, :tool_spans, :rounds, :tokens, :chat_key, :agent)
47
47
 
48
48
  class << self
49
49
  # Subscribes to RubyLLM's instrumentation.
@@ -91,17 +91,30 @@ module ActiveAgents
91
91
 
92
92
  attr_writer :reporter
93
93
 
94
- # Attributes traces inside the block to a named agent/action.
95
- def with_agent(name, action: "chat")
94
+ # Attributes traces inside the block to a named agent/action. Correlation
95
+ # attributes and the callback apply to this scope only. Short-lived
96
+ # evaluation commands can deliver synchronously without changing the
97
+ # application's shared reporter configuration.
98
+ #
99
+ # A turn keeps the scope it started under, so a turn left open by a
100
+ # pending tool call and closed later by `flush!` still reports as this
101
+ # agent, and a turn that started outside any scope never adopts one.
102
+ # `on_trace` runs only for a trace the reporter accepted, which means
103
+ # it passed the enabled, configured and sampling checks: a delivery
104
+ # that then fails is logged by the reporter, not announced here.
105
+ def with_agent(name, action: "chat", attributes: {}, on_trace: nil, synchronous: false)
96
106
  previous = Thread.current[AGENT_KEY]
97
- Thread.current[AGENT_KEY] = { name: name, action: action }
107
+ Thread.current[AGENT_KEY] = {
108
+ name: name, action: action, attributes: attributes.to_h.transform_keys(&:to_s),
109
+ on_trace: on_trace, synchronous: synchronous
110
+ }
98
111
  yield
99
112
  ensure
100
113
  Thread.current[AGENT_KEY] = previous
101
114
  end
102
115
 
103
116
  def state
104
- Thread.current[STATE_KEY] ||= State.new(0, nil, [], 0, Span::ZERO_TOKENS.dup, nil)
117
+ Thread.current[STATE_KEY] ||= State.new(0, nil, [], 0, Span::ZERO_TOKENS.dup, nil, nil)
105
118
  end
106
119
 
107
120
  def clear_state
@@ -126,7 +139,13 @@ module ActiveAgents
126
139
  flush! if turn.rounds.positive? && (turn.chat_key != chat_key || turn_expired?(turn))
127
140
  turn = state
128
141
  turn.chat_key = chat_key
129
- turn.started_at ||= Time.now
142
+ if turn.started_at.nil?
143
+ turn.started_at = Time.now
144
+ # Captured once, on the turn's first round, whether or not a scope
145
+ # is active: a turn that started unscoped stays unscoped even when
146
+ # a later scope's chat is what flushes it.
147
+ turn.agent = Thread.current[AGENT_KEY]
148
+ end
130
149
  end
131
150
  turn.depth += 1
132
151
  end
@@ -161,7 +180,7 @@ module ActiveAgents
161
180
  private
162
181
 
163
182
  def report_turn(payload, turn)
164
- agent = Thread.current[AGENT_KEY] || resolve_agent(payload) || DEFAULT_AGENT
183
+ agent = turn.agent || resolve_agent(payload) || DEFAULT_AGENT
165
184
  started_at = turn.started_at || Time.now
166
185
  finished_at = Time.now
167
186
  error = payload[:exception_object]
@@ -172,12 +191,12 @@ module ActiveAgents
172
191
  resource_attributes: configuration.resource_attributes
173
192
  )
174
193
 
175
- root_attributes = {
194
+ root_attributes = (agent[:attributes] || {}).merge(
176
195
  "agent.class" => agent[:name],
177
196
  "agent.action" => agent[:action],
178
197
  "agent.provider" => payload[:provider].to_s,
179
198
  "agent.model" => payload[:model].to_s
180
- }
199
+ )
181
200
  root_attributes.merge!(conversation_attributes(payload)) if configuration.capture_bodies?
182
201
 
183
202
  root = trace.span(
@@ -206,7 +225,14 @@ module ActiveAgents
206
225
  trace.add_span(tool_span)
207
226
  end
208
227
 
209
- reporter.report(trace)
228
+ accepted = agent[:synchronous] ? reporter.report(trace, sync: true) : reporter.report(trace)
229
+ notify_trace(agent[:on_trace], trace) if accepted
230
+ end
231
+
232
+ def notify_trace(callback, trace)
233
+ callback&.call(trace)
234
+ rescue StandardError => e
235
+ warn "[#{SDK_NAME}] on_trace failed: #{e.class}"
210
236
  end
211
237
 
212
238
  def turn_expired?(turn)
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.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ActiveAgents
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-14 00:00:00.000000000 Z
11
+ date: 2026-09-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activeagents-telemetry
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.1'
19
+ version: '0.3'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.1'
26
+ version: '0.3'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: activesupport
29
29
  requirement: !ruby/object:Gem::Requirement