Skip to content

Data Model

Every integration is a mapping from a framework's own telemetry onto the model below. This page is the contract: what Console stores, what each field does to the UI and to cost accounting, and what a new integration — or a language we do not ship an SDK for yet — has to produce.

For the raw endpoint shapes see the Tracing API reference.

Session → event → section

session   one agent run  ──────────────────────  Tracing → Sessions
  ├── event   one step of that run  ────────────  a row in the run timeline
  │     └── section   one renderable block  ────  the expanded row body
  └── event …

Threads sit above sessions: several sessions sharing a threadId are one conversation in Tracing → Threads. That is how a chat agent's turns, or a LangGraph run that pauses for human input and resumes, are reassembled — each resume is a fresh run with a fresh trace id, and threadId is the only thing tying them together.

Session

FieldTypeWhat it does
sessionIdstringIdentity. Posting the same id again updates the session rather than creating another.
threadIdstringConversation key — drives the Threads view. Set it whenever your framework has a conversation concept.
traceId32 hexW3C trace id, so a run can be correlated with another OTel backend.
rootSpanId16 hexParent of every top-level event.
agent{name, version, model, provider}name is what the Agents screen and cost reports group by. version is what makes a before/after comparison possible after a prompt change.
statussuccess | error | in_progress
startedAt / endedAtISO 8601
durationMsnumber
summarytotalstotalInputTokens, totalOutputTokens, totalCachedInputTokens, totalDurationMs, eventCounts
totalReasoningTokensnumberRunning total of the session's event reasoningTokens. Already counted within totalOutputTokens — see Tokens — never add it again in cost math.
truncatedEventsnumberCount of events whose finishReason signalled a token/length cutoff (length, max_tokens, …) rather than the model stopping on its own terms.
configobjectFree-form run configuration, shown on the session header.
errorsarrayA non-empty list marks the session failed.

Session totals only grow

A session's totals are monotonic server-side. An SDK that reports one run in several legs — a summarization pass, a tool retry, a resume after an interrupt — sends several end calls, each describing only its own leg. Console takes the larger of what it already has and what the leg reports, so a later leg can never shrink a run's totals, and a retried end is idempotent.

Event types

Console aggregates per type, so use these rather than inventing names:

typeUse for
ai_callA model call. The only type that should carry model and token counts.
tool_callA tool or function invocation. Set toolName.
retrievalRAG or vector search.
embeddingAn embedding call.
summarizationHistory compaction.
guardrailA policy or safety check.
spanAnything else — a graph node, a chain step, a block of your own work.

Event

FieldTypeNotes
idstringStable per event; used to de-duplicate on re-ingest.
labelstringThe timeline row's title. Node name, tool name or model name.
spanId / parentSpanId16 hexBuilds the tree. See Identifiers.
sequencenumberTimeline ordering.
timestampISO 8601Start of the step.
durationMsnumber
statussuccess | error
errorstring or objectAttached to a failed step.
modelstringThe provider's model id (gpt-4.1-mini), not a nickname — see Cost.
inputTokens / outputTokens / cachedInputTokens / totalTokensnumberSee Tokens.
reasoningTokensnumberReasoning/thinking tokens the model spent before its answer (e.g. OpenAI's completion_tokens_details.reasoning_tokens). A subset of outputTokens — see Tokens — never billed on top of it.
finishReasonstringNormalized (trim + lowercase) raw provider stop reason — stop, length, tool_calls, etc. Feeds the session's truncatedEvents count.
toolName / toolExecutionIdstringtoolExecutionId correlates the run with the model's tool-call id.
toolDefinitionsarrayThe tool menu offered on this call. See below.
actor{scope, name}scope is agent, model, tool, retriever or user; drives the actor column.
sectionsarrayThe expanded row body.
metadataobjectAnything else worth keeping; rendered as a key/value block.

Sections

kind drives the badge colour in the tracing detail UI. Every other key is rendered generically as a labelled field, so extra keys are safe and are a reasonable place to put framework-specific detail.

kindShapeRendered as
message{role, content}A chat bubble with a role badge
tool_call{tool, content}The arguments, JSON-tree if parseable
tool_result{tool, content}The result
tool_definitions{tools: [{name, description?, parameters?}]}A name badge list plus collapsible schemas
metadata{content}A labelled value block
json
{
  "kind": "message",
  "label": "User message",
  "role": "user",
  "content": "book me a flight to Rome"
}

Tool definitions

toolDefinitions records the menu the model was offered on that call — not the tool set the agent was configured with. The menu changes between turns, and a large one is frequently the biggest single line item in an agent's prompt bill, which is why it is captured per event rather than per session.

json
{
  "type": "ai_call",
  "toolDefinitions": [
    { "name": "search_flights",
      "description": "Search flights by city",
      "parameters": { "type": "object", "properties": { "city": { "type": "string" } } } }
  ]
}

Console normalises this into a tool_definitions section, accepts the OpenAI {type: 'function', function: {…}} envelope and Anthropic's input_schema alias, and caps oversized schemas — entries then keep name and description and drop parameters with "truncated": true. Malformed input is ignored silently rather than failing the ingest.

Not every framework can report this. Where it cannot, the integration page says so instead of inventing a menu.

Tokens

cachedInputTokens is a subset of inputTokens, matching OpenAI's prompt_tokens_details.cached_tokens and LangChain's standardised input_token_details.cache_read. Cost is computed as

(inputTokens − cachedInputTokens) × input rate
+ cachedInputTokens              × cached rate
+ outputTokens                   × output rate

Anthropic reports it the other way round — its input_tokens excludes cache reads — so the Claude integrations add the cache buckets back in:

inputTokens       = input_tokens + cache_read_input_tokens + cache_creation_input_tokens
cachedInputTokens = cache_read_input_tokens

reasoningTokens is also a subset — of outputTokens, not inputTokens — covering the model's internal reasoning/thinking tokens. It is never added on top of outputTokens or totalTokens in cost math; it exists so the reasoning/answer split is visible without double-billing it. The session's totalReasoningTokens is the running sum of the field across its events.

Absent is not zero

When a framework reports no usage — a streaming call without usage opt-in, a cancelled run — the fields are omitted. A zero would silently under-report spend; an absent value shows up as unknown and can be chased.

Cost and double counting

Trace-derived token usage is priced and rolled into spend reporting as service: models, source: tracing, keyed by agent name — separable from gateway-served traffic (source: api) in every report. Pricing resolves model against Model Hub keys, then provider model ids, then your external pricing catalogue, all case-insensitively. An unmatched model still records its tokens at zero cost so the Cost page can surface it as unpriced rather than dropping it.

A trace event describing a model call that Console's own gateway served is already billed at serving time. Mark those with metadata.gateway = true (or a metadata.gatewayRequestId) and the cost pipeline skips them. Direct-to-provider calls — the normal case for these integrations — need no marker.

Identifiers

traceId is 32 lower-hex characters, spanId is 16, per W3C. Framework run ids are usually UUIDs, so the SDK folds them deterministically:

  • an input that is already exactly 16 (or 32) hex characters passes through;
  • anything else is hashed.

Truncation is deliberately not used. LangChain run ids are UUIDv7, whose first 16 hex digits are a millisecond timestamp plus 12 bits of entropy — two runs started in the same millisecond would collide, and colliding span ids silently corrupt the tree. Because the fold is deterministic, a child can compute its parent's span id from a parent run id it never saw as an event of its own.

Delivery

Two wire shapes, chosen by mode:

ModeRequestsUse for
batchone, at the endShort runs, serverless, cron. The endpoint replaces the session's event list, so re-posting is idempotent.
stream/start, one per event, /endLong runs you want to watch live. Events append.
auto (default)eitherBuffers, then switches to streaming once the run passes streamAfterMs (2 s) or streamAfterEvents (25). One request for a quick run, live updates for a slow one.

A session commits to one shape the first time it delivers anything and never switches back — a batch post would otherwise wipe already-streamed events.

Endpoints

All under POST /api/client/v1, with Authorization: Bearer <token>:

PathPurpose
/tracing/sessionsBatch: a whole session in one request
/tracing/sessions/stream/:id/startOpen a streaming session
/tracing/sessions/stream/:id/eventsAppend one event
/tracing/sessions/stream/:id/endClose, with final totals
/tracesOTLP/HTTP JSON ExportTraceServiceRequest
GET /tracing/threadsRead threads back

The body limit is TRACING_MAX_BODY_SIZE_MB (10 MB by default), which is why the SDK caps section content and strips base64 data URLs before sending.

OpenTelemetry attribute mapping

Traces arriving at /traces from third-party instrumentation are normalised from whichever convention they use. Console reads all of them, and merges when a span carries two:

Internal fieldOpenInference (Arize)OTel GenAI / OpenLLMetry ≥ 0.55OpenLLMetry ≤ 0.54
event typeopeninference.span.kindgen_ai.operation.nametraceloop.span.kind
modelllm.model_namegen_ai.response.modelgen_ai.request.modelgen_ai.request.model
inputTokensllm.token_count.promptgen_ai.usage.input_tokensgen_ai.usage.prompt_tokens
outputTokensllm.token_count.completiongen_ai.usage.output_tokensgen_ai.usage.completion_tokens
cachedInputTokensllm.token_count.prompt_details.cache_readgen_ai.usage.cache_read.input_tokensgen_ai.usage.cache_read_input_tokens
message sectionsllm.input_messages.N.message.*, llm.output_messages.N.message.*gen_ai.system_instructions, gen_ai.input.messages, gen_ai.output.messagesgen_ai.prompt.N.*, gen_ai.completion.N.*
toolDefinitionsllm.tools.N.tool.json_schemagen_ai.tool.definitionsllm.request.functions.N.*
toolNametool.namegen_ai.tool.nametraceloop.entity.name
threadIdsession.idgen_ai.conversation.idtraceloop.association.properties.session_id
tool input / resultinput.value / output.valuemessage parts, gen_ai.tool.call.resulttraceloop.entity.input / .output

Two details worth knowing when reading traces from these sources:

  • The system prompt moved. OpenLLMetry ≥ 0.55 strips it out of gen_ai.input.messages into gen_ai.system_instructions. Console reads both; a backend that reads only the former loses the largest part of the prompt.
  • __REDACTED__ is a sentinel, not content. Instrumentation replaces redacted values with that literal, and Console does not render it as a message.

Community edition is AGPL-3.0. Commercial licensing and support are available separately.