Class: ActiveAgent::TelemetryTrace
- Inherits:
-
ActiveRecord::Base
- Object
- ActiveRecord::Base
- ActiveAgent::TelemetryTrace
- Defined in:
- lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb
Overview
Stores telemetry traces from ActiveAgent clients.
Each trace represents a complete generation lifecycle, including prompt preparation, LLM calls, tool invocations, and error handling.
This model supports two modes:
- Local mode: No account association (single-tenant, self-hosted)
- Multi-tenant mode: With account association (for activeagents.ai platform)
Constant Summary collapse
- STATUS_OK =
Status values for traces
"OK"- STATUS_ERROR =
"ERROR"- STATUS_UNSET =
"UNSET"
Class Method Summary collapse
-
.create_from_payload(trace, sdk_info = {}, account: nil) ⇒ TelemetryTrace
Creates a TelemetryTrace from an ingested trace payload.
-
.span_token_sum(span) ⇒ Object
private
Sums a span's token counts (used to decide which spans carry the authoritative token data during ingestion).
Instance Method Summary collapse
-
#display_name ⇒ String
Returns display name for the trace.
-
#error? ⇒ Boolean
Returns whether this trace had an error.
-
#formatted_duration ⇒ String
Returns formatted duration.
-
#formatted_tokens ⇒ String
Returns formatted token count.
-
#llm_spans ⇒ Array<Hash>
Returns all LLM spans in this trace.
-
#model ⇒ String?
Returns the model used (from LLM spans).
-
#provider ⇒ String?
Returns the provider used (from LLM spans).
-
#root_span ⇒ Hash?
Returns the root span of this trace.
-
#tool_spans ⇒ Array<Hash>
Returns all tool call spans in this trace.
-
#total_tokens ⇒ Integer
Returns total token count.
Class Method Details
.create_from_payload(trace, sdk_info = {}, account: nil) ⇒ TelemetryTrace
Creates a TelemetryTrace from an ingested trace payload.
Extracts relevant data from the trace payload and stores it in a normalized format for querying and analysis.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 53 def self.create_from_payload(trace, sdk_info = {}, account: nil) spans = trace["spans"] || [] root_span = spans.find { |s| s["parent_span_id"].nil? } || spans.first || {} total_duration = root_span["duration_ms"] # Instrumentation mirrors LLM token usage onto the root span for # display, so summing every span double-counts. When child spans # carry token data, they are the source of truth; the root span only # counts for single-span traces. counted_spans = spans.reject { |s| s["parent_span_id"].nil? } counted_spans = spans if counted_spans.none? { |s| span_token_sum(s).positive? } total_input = 0 total_output = 0 total_thinking = 0 counted_spans.each do |span| tokens = span["tokens"] || {} total_input += (tokens["input"] || 0) total_output += (tokens["output"] || 0) total_thinking += (tokens["thinking"] || 0) end # Extract agent info from root span attributes attributes = root_span["attributes"] || {} agent_class = attributes["agent.class"] agent_action = attributes["agent.action"] # Find any error message error_span = spans.find { |s| s["status"] == STATUS_ERROR } = error_span&.dig("attributes", "error.message") attrs = { trace_id: trace["trace_id"], service_name: trace["service_name"], environment: trace["environment"], timestamp: Time.parse(trace["timestamp"]), spans: spans, resource_attributes: trace["resource_attributes"], sdk_info: sdk_info, total_duration_ms: total_duration, total_input_tokens: total_input, total_output_tokens: total_output, total_thinking_tokens: total_thinking, status: root_span["status"] || STATUS_UNSET, agent_class: agent_class, agent_action: agent_action, error_message: } # Add account if in multi-tenant mode attrs[:account] = account if ActiveAgent::Dashboard.multi_tenant? && account create!(attrs) end |
.span_token_sum(span) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Sums a span's token counts (used to decide which spans carry the authoritative token data during ingestion).
114 115 116 117 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 114 def self.span_token_sum(span) tokens = span["tokens"] || {} tokens.fetch("input", 0).to_i + tokens.fetch("output", 0).to_i + tokens.fetch("thinking", 0).to_i end |
Instance Method Details
#display_name ⇒ String
Returns display name for the trace.
157 158 159 160 161 162 163 164 165 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 157 def display_name if agent_class && agent_action "#{agent_class}.#{agent_action}" elsif agent_class agent_class else trace_id&.first(8) end end |
#error? ⇒ Boolean
Returns whether this trace had an error.
150 151 152 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 150 def error? status == STATUS_ERROR end |
#formatted_duration ⇒ String
Returns formatted duration.
170 171 172 173 174 175 176 177 178 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 170 def formatted_duration return "—" unless total_duration_ms if total_duration_ms >= 1000 "#{(total_duration_ms / 1000.0).round(2)}s" else "#{total_duration_ms.round(0)}ms" end end |
#formatted_tokens ⇒ String
Returns formatted token count.
183 184 185 186 187 188 189 190 191 192 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 183 def formatted_tokens count = total_tokens return "0" if count.zero? if count >= 1000 "#{(count / 1000.0).round(1)}K" else count.to_s end end |
#llm_spans ⇒ Array<Hash>
Returns all LLM spans in this trace.
129 130 131 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 129 def llm_spans spans&.select { |s| s["type"] == "llm" } || [] end |
#model ⇒ String?
Returns the model used (from LLM spans).
207 208 209 210 211 212 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 207 def model llm_span = llm_spans.first return nil unless llm_span llm_span.dig("attributes", "llm.model") end |
#provider ⇒ String?
Returns the provider used (from LLM spans).
197 198 199 200 201 202 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 197 def provider llm_span = llm_spans.first return nil unless llm_span llm_span.dig("attributes", "llm.provider") end |
#root_span ⇒ Hash?
Returns the root span of this trace.
122 123 124 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 122 def root_span spans&.find { |s| s["parent_span_id"].nil? } end |
#tool_spans ⇒ Array<Hash>
Returns all tool call spans in this trace.
136 137 138 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 136 def tool_spans spans&.select { |s| s["type"] == "tool" } || [] end |
#total_tokens ⇒ Integer
Returns total token count.
143 144 145 |
# File 'lib/active_agent/dashboard/app/models/active_agent/telemetry_trace.rb', line 143 def total_tokens (total_input_tokens || 0) + (total_output_tokens || 0) + (total_thinking_tokens || 0) end |