activeagents-telemetry-ruby_llm 0.1.0 → 0.2.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 +4 -4
- data/README.md +3 -1
- data/lib/activeagents/telemetry/ruby_llm/version.rb +1 -1
- data/lib/activeagents/telemetry/ruby_llm.rb +73 -8
- 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: cf9f1bb148ecabe70a08facf887f60c55dd08f6683370bcb03cb33d1abad0dd1
|
|
4
|
+
data.tar.gz: 81d06e3973b08667dbb972fe1a013d1be3785b9d9c2b3804a4dfbe5ae67fbaa5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 999b5249ec348dc4b5280aeddfb2c905af13b29948a85aed5b81ddfdfd2f920fd5d0607b0fd79313f5fad732de271e0db2d1ab23b7ce04e575bf93f90c967040
|
|
7
|
+
data.tar.gz: 4ec2de8a1cb63605724e7497632b7463ee3e317c133d3bc05cc8131aa8ea1e9b379fdb0da172bf509809bb2ac193c2164bc3780b8844ed32c4c7225220475fb7
|
data/README.md
CHANGED
|
@@ -48,7 +48,9 @@ enclosing event, 2.x drives a flat `step until complete?` loop whose rounds
|
|
|
48
48
|
are siblings with tool calls between them. Rounds are accumulated and flushed
|
|
49
49
|
on the round that ends the turn, so both produce the same trace.
|
|
50
50
|
|
|
51
|
-
|
|
51
|
+
Prompts, completions, and tool arguments/results are sent only when the
|
|
52
|
+
configuration's `capture_bodies` is enabled (off by default, truncated to
|
|
53
|
+
4,000 characters); error messages are truncated.
|
|
52
54
|
|
|
53
55
|
## Naming the traffic
|
|
54
56
|
|
|
@@ -27,7 +27,9 @@ module ActiveAgents
|
|
|
27
27
|
# Tokens are summed per round from the assistant messages that round added,
|
|
28
28
|
# so a repeated event-level count is never double counted.
|
|
29
29
|
#
|
|
30
|
-
#
|
|
30
|
+
# Prompts, completions, and tool arguments/results are not sent unless the
|
|
31
|
+
# configuration's capture_bodies is enabled (they may contain sensitive
|
|
32
|
+
# data); error messages are truncated.
|
|
31
33
|
module RubyLLM
|
|
32
34
|
AGENT_KEY = :activeagents_telemetry_ruby_llm_agent
|
|
33
35
|
STATE_KEY = :activeagents_telemetry_ruby_llm_state
|
|
@@ -36,6 +38,8 @@ module ActiveAgents
|
|
|
36
38
|
# A turn that never reaches a final round (a halted tool call, or an app
|
|
37
39
|
# driving RubyLLM 2.x's `step` by hand) would otherwise accumulate forever.
|
|
38
40
|
MAX_TURN_SECONDS = 600
|
|
41
|
+
# Captured prompt/completion/tool content is truncated to this many characters.
|
|
42
|
+
CONTENT_LIMIT = 4_000
|
|
39
43
|
|
|
40
44
|
DEFAULT_AGENT = { name: "RubyLLM::Chat", action: "chat" }.freeze
|
|
41
45
|
|
|
@@ -139,11 +143,16 @@ module ActiveAgents
|
|
|
139
143
|
|
|
140
144
|
def build_tool_span(payload, started_at, finished_at)
|
|
141
145
|
error = payload[:exception_object]
|
|
146
|
+
attributes = { "tool.name" => payload[:tool_name].to_s, "tool.call_id" => payload[:tool_call_id].to_s }
|
|
147
|
+
if configuration.capture_bodies?
|
|
148
|
+
attributes["tool.arguments"] = tool_io_json(payload[:tool_arguments])
|
|
149
|
+
attributes["tool.result"] = tool_io_json(payload[:result_content]) unless error
|
|
150
|
+
end
|
|
142
151
|
span = Span.new(
|
|
143
152
|
"tool.#{payload[:tool_name]}",
|
|
144
153
|
type: "tool",
|
|
145
154
|
start_time: started_at,
|
|
146
|
-
attributes:
|
|
155
|
+
attributes: attributes
|
|
147
156
|
)
|
|
148
157
|
span.record_error(error, message_limit: configuration.error_message_limit) if error
|
|
149
158
|
span.finish(at: finished_at)
|
|
@@ -163,14 +172,17 @@ module ActiveAgents
|
|
|
163
172
|
resource_attributes: configuration.resource_attributes
|
|
164
173
|
)
|
|
165
174
|
|
|
175
|
+
root_attributes = {
|
|
176
|
+
"agent.class" => agent[:name],
|
|
177
|
+
"agent.action" => agent[:action],
|
|
178
|
+
"agent.provider" => payload[:provider].to_s,
|
|
179
|
+
"agent.model" => payload[:model].to_s
|
|
180
|
+
}
|
|
181
|
+
root_attributes.merge!(conversation_attributes(payload)) if configuration.capture_bodies?
|
|
182
|
+
|
|
166
183
|
root = trace.span(
|
|
167
184
|
"#{agent[:name]}.#{agent[:action]}", type: "root", start_time: started_at,
|
|
168
|
-
attributes:
|
|
169
|
-
"agent.class" => agent[:name],
|
|
170
|
-
"agent.action" => agent[:action],
|
|
171
|
-
"agent.provider" => payload[:provider].to_s,
|
|
172
|
-
"agent.model" => payload[:model].to_s
|
|
173
|
-
}
|
|
185
|
+
attributes: root_attributes
|
|
174
186
|
)
|
|
175
187
|
|
|
176
188
|
llm = trace.span(
|
|
@@ -215,6 +227,59 @@ module ActiveAgents
|
|
|
215
227
|
nil
|
|
216
228
|
end
|
|
217
229
|
|
|
230
|
+
# The prompt that opened the turn and the answer that closed it — the two
|
|
231
|
+
# ends a trace is otherwise missing. System instructions are included:
|
|
232
|
+
# they are the most common cause of a surprising answer.
|
|
233
|
+
def conversation_attributes(payload)
|
|
234
|
+
input = Array(payload[:input_messages])
|
|
235
|
+
new_messages = Array(payload[:messages_after])[input.size..] || []
|
|
236
|
+
|
|
237
|
+
attributes = {}
|
|
238
|
+
if (prompt = last_message_text(input, "user"))
|
|
239
|
+
attributes["llm.prompt"] = truncate_captured(prompt)
|
|
240
|
+
end
|
|
241
|
+
if (instructions = system_instructions(input))
|
|
242
|
+
attributes["llm.instructions"] = truncate_captured(instructions)
|
|
243
|
+
end
|
|
244
|
+
if (completion = last_message_text(new_messages, "assistant"))
|
|
245
|
+
attributes["llm.completion"] = truncate_captured(completion)
|
|
246
|
+
end
|
|
247
|
+
attributes
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# RubyLLM's `with_instructions` appends by default, so a chat can carry
|
|
251
|
+
# several system messages and the model sees all of them. Join rather
|
|
252
|
+
# than taking the last, or an app that layers a base prompt with a
|
|
253
|
+
# per-request one would report only the fragment.
|
|
254
|
+
def system_instructions(messages)
|
|
255
|
+
texts = messages.select do |candidate|
|
|
256
|
+
candidate.respond_to?(:role) && candidate.role.to_s == "system" &&
|
|
257
|
+
candidate.respond_to?(:content) && !candidate.content.to_s.empty?
|
|
258
|
+
end.map { |message| message.content.to_s }
|
|
259
|
+
|
|
260
|
+
texts.empty? ? nil : texts.join("\n\n")
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def last_message_text(messages, role)
|
|
264
|
+
message = messages.reverse.find do |candidate|
|
|
265
|
+
candidate.respond_to?(:role) && candidate.role.to_s == role &&
|
|
266
|
+
candidate.respond_to?(:content) && !candidate.content.to_s.empty?
|
|
267
|
+
end
|
|
268
|
+
text = message&.content.to_s
|
|
269
|
+
text.empty? ? nil : text
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
def tool_io_json(value)
|
|
273
|
+
json = value.is_a?(String) ? value : JSON.generate(value)
|
|
274
|
+
truncate_captured(json)
|
|
275
|
+
rescue StandardError
|
|
276
|
+
value.inspect[0, CONTENT_LIMIT]
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
def truncate_captured(text)
|
|
280
|
+
text.to_s[0, CONTENT_LIMIT]
|
|
281
|
+
end
|
|
282
|
+
|
|
218
283
|
def token_totals(payload)
|
|
219
284
|
initial_count = Array(payload[:input_messages]).size
|
|
220
285
|
new_messages = Array(payload[:messages_after])[initial_count..] || []
|
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.
|
|
4
|
+
version: 0.2.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-
|
|
11
|
+
date: 2026-08-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activeagents-telemetry
|