Skip to content
telemetry.dev
Esc
↑↓navigate↵open⌘Jpreview
On this page

Span attributes

Send recognized OpenTelemetry attributes for models, tools, agents, users, and content.

The ingest API reads OpenTelemetry span attributes into searchable fields, token totals, input and output content, and cost. This page lists every attribute it recognizes and the order it checks them in.

A minimal model span

These attributes are enough for a model call with token totals and a server-calculated cost:

{
  "gen_ai.operation.name": "chat",
  "gen_ai.provider.name": "openai",
  "gen_ai.request.model": "gpt-4o",
  "gen_ai.usage.input_tokens": 1200,
  "gen_ai.usage.output_tokens": 350
}

Add gen_ai.input.messages and gen_ai.output.messages only if you want content capture. Add gen_ai.usage.cache_read.input_tokens and gen_ai.usage.cache_creation.input_tokens if the provider reports prompt caching.

Every attribute is also kept in the span’s stored attributes, subject to capture settings and redaction. The exception is a content attribute that was moved into the input or output field; see Input and output content.

How aliases are resolved

Many fields have several recognized attributes, listed in priority order on this page. For each field the ingest API takes the first attribute that is present and valid, and skips the rest.

  • A string field skips empty strings.
  • A token field skips values that are not valid token counts; see Token usage.
  • A cost field skips values that are not valid costs; see Client cost.

The first-party SDKs and the standard gen_ai.* names come first in each list. Compatibility names from other frameworks follow.

Operations

gen_ai.operation.name sets the span’s operation. The ingest API stores the value as sent. The operation decides which content attributes are read:

Value Meaning Content read from
chat Chat generation Model attributes
text_completion Text completion Model attributes
generate_content Content generation Model attributes
stream Streaming generation Model attributes
embeddings Embedding Model attributes
rerank Reranking Model attributes
evaluate Evaluation Model attributes, plus the ai.evaluation.* attributes
create_agent Agent creation Model attributes
invoke_agent Agent invocation Model attributes
invoke_workflow Workflow invocation Model attributes
llm Generic model call Model attributes
execute_tool Tool execution Tool attributes
function Plain instrumented function gen_ai.input.messages and gen_ai.output.messages only

Any other value is stored, but no content is extracted for it.

The first-party SDKs always send this attribute: chat for generations, execute_tool for tools, invoke_agent for agents, embeddings for embeddings, and function for plain spans. Set it yourself when you use your own OpenTelemetry exporter.

Derived operations

If gen_ai.operation.name is absent, the ingest API applies these rules in order and uses the first that matches:

Order Rule Operation
1 An attribute starts with gen_ai.tool. execute_tool
2 An attribute is ai.toolCall or starts with ai.toolCall., or the span name is ai.toolCall or starts with ai.toolCall. execute_tool
3 mcp.method.name is tools/call execute_tool
4 mcp.method.name has any other value mcp
5 The span name is ai.eve.turn, or starts with ai.eve.turn and the span has eve.session.id invoke_agent
6 An attribute starts with gen_ai. llm
7 An attribute is db.system or starts with db.system., or the span name contains retriev in any letter case retrieval
8 The span name is ai.streamText or starts with ai.streamText. stream
9 The span name is ai.embed or ai.embedMany, or starts with either followed by . embeddings
10 The span name is ai.generateText or ai.generateObject, or starts with either followed by . llm
11 The span kind is CLIENT and an attribute starts with http. http
12 No rule matches No operation

Rule 6 is broad: any gen_ai.* attribute, even a propagated gen_ai.conversation.id, makes a span an llm call. This is why the first-party SDKs send function explicitly for plain spans.

A few integrations have their own rules:

  • OpenClaw openclaw.model.call spans become llm, and openclaw.model.usage accounting spans get no operation.
  • Claude Code claude_code.llm_request spans become llm.
  • A Claude Platform or Claude Code span without gen_ai.operation.name that would be derived as llm but has no token usage gets no operation. These spans always carry gen_ai.provider.name, so rule 6 would otherwise count sessions, hooks, and similar spans as model calls.

Model, provider, and response

Field Attributes, in priority order Notes
Model gen_ai.request.model, gen_ai.response.model, ai.model.id, llm.model_name Used for the cost lookup
Provider gen_ai.provider.name, gen_ai.system, ai.model.provider, llm.system Stored as sent; normalized only for the cost lookup
Response model gen_ai.response.model, ai.response.model
Response ID gen_ai.response.id
Finish reason gen_ai.response.finish_reasons, ai.response.finishReason, gen_ai.response.finish_reason, anthropic.message.stop_reason, anthropic.session.turn.stop_reason A string or an array of strings. From an array, the first nonempty string is stored.
Output type gen_ai.output.type

The first-party SDKs send gen_ai.response.finish_reasons as an array with one value.

Token usage

Send token counts as integers. Each token field uses the first valid attribute in this order:

Token field Attributes, in priority order
Input gen_ai.usage.input_tokens, ai.usage.inputTokens, ai.usage.promptTokens, gen_ai.usage.prompt_tokens, llm.token_count.prompt
Output gen_ai.usage.output_tokens, ai.usage.outputTokens, ai.usage.completionTokens, gen_ai.usage.completion_tokens, llm.token_count.completion
Total (fallback only) gen_ai.usage.total_tokens, ai.usage.tokens
Cache read gen_ai.usage.cache_read.input_tokens, ai.usage.cachedInputTokens, gen_ai.usage.cache_read_input_tokens, gen_ai.usage.cached_input_tokens
Cache creation gen_ai.usage.cache_creation.input_tokens, gen_ai.usage.cache_write.input_tokens, ai.usage.cacheCreationInputTokens, gen_ai.usage.cache_creation_input_tokens
One-hour cache creation anthropic.usage.cache_creation.ephemeral_1h_input_tokens (the part of cache creation written to the one-hour cache)
Reasoning output gen_ai.usage.reasoning.output_tokens, gen_ai.usage.reasoning_tokens, ai.usage.reasoningTokens

Input is the total input, including cache reads and cache creation. Output includes reasoning tokens. The cost formula depends on this.

Valid token counts

A token count is valid when it is a whole number from 0 to 253 − 1 (9,007,199,254,740,991). It can be an integer value, a double with no fractional part, or a string of decimal digits only, such as "1200". Negative, fractional, and larger values, and strings such as "1e3" or "12.0", are skipped, and the next alias is tried.

The ingest API records the diagnostic unrecognized_usage_attribute when a span has a numeric gen_ai.usage.*, ai.usage.*, or llm.token_count.* token attribute it does not recognize and the matching token field stays empty. It does the same for a near-miss cost name, such as gen_ai.usage.cost_usd or td.cost, when no cost is stored. It records inconsistent_usage_counts when cache read plus cache creation is larger than input.

Fallbacks for missing totals

When a span has no input or output attribute from the table, the ingest API fills the field this way:

Missing field Filled from
Input, on an embeddings or rerank span with no output or zero output Total
Input, otherwise Cache read plus cache creation, if either is present; else total, if the span reports no output
Output Reasoning output

When a span reports modality counts for a direction but no aggregate attribute, these fallbacks are not used. Token totals and cost use the sum of the modality counts instead.

Modality token usage

Providers that bill text, image, and audio tokens at different rates report usage per modality. The ingest API recognizes one attribute for each modality and token kind:

Modality Input Output Cache read
Text gen_ai.usage.text.input_tokens gen_ai.usage.text.output_tokens gen_ai.usage.text.cache_read.input_tokens
Image gen_ai.usage.image.input_tokens gen_ai.usage.image.output_tokens gen_ai.usage.image.cache_read.input_tokens
Audio gen_ai.usage.audio.input_tokens gen_ai.usage.audio.output_tokens gen_ai.usage.audio.cache_read.input_tokens

For example, a call with 900 text and 300 image input tokens, where 100 of the text tokens were read from cache, sends:

{
  "gen_ai.usage.input_tokens": 1200,
  "gen_ai.usage.cache_read.input_tokens": 100,
  "gen_ai.usage.text.input_tokens": 900,
  "gen_ai.usage.text.cache_read.input_tokens": 100,
  "gen_ai.usage.image.input_tokens": 300,
  "gen_ai.usage.image.cache_read.input_tokens": 0,
  "gen_ai.usage.output_tokens": 80,
  "gen_ai.usage.text.output_tokens": 80
}

Modality counts are parts of the aggregate counts, not additions to them:

  • Send gen_ai.usage.input_tokens and gen_ai.usage.output_tokens as the totals, and keep each modality sum at or below its aggregate.
  • A modality’s input count includes its own cache reads.
  • When the aggregate cache-read count is above zero, send each modality’s cache-read count, even when it is 0. Without it, that modality’s input is priced at the base input rate; see Modality pricing.

When an aggregate is absent, token totals and cost use the sum of the modality counts that are present.

The first-party SDKs accept modality fields, and several provider integrations set them when the provider reports modality detail. See Cost for how modality counts change the calculation.

Cost attributes

A valid client cost replaces the server calculation. The ingest API checks these attributes in order: gen_ai.usage.cost, ai.usage.cost, td.cost_usd, telemetry.dev.cost_usd. For valid values and the calculation itself, see Cost calculation.

Streaming latency

Field Attributes, in priority order
Time to first chunk gen_ai.client.operation.time_to_first_chunk, gen_ai.response.time_to_first_chunk, gen_ai.response.time_to_first_token, gen_ai.server.time_to_first_token, llm.time_to_first_token, llm.ttft, ai.response.msToFirstChunk, ai.stream.firstChunkMs
Time per output chunk gen_ai.client.operation.time_per_output_chunk, gen_ai.response.time_per_output_chunk, gen_ai.server.time_per_output_token, llm.time_per_output_chunk, llm.time_per_output_token

The unit comes from the attribute name. If the key, in lowercase, contains ms, the value is milliseconds. Otherwise it is seconds and the ingest API multiplies it by 1,000. The result is rounded to the nearest millisecond. For example, gen_ai.response.time_to_first_chunk = 0.25 is stored as 250 ms, and ai.response.msToFirstChunk = 250 is also 250 ms.

Time to first chunk is stored as a span field. Both values are also added to the stored attributes as telemetry.dev.streaming.time_to_first_chunk_ms and telemetry.dev.streaming.time_per_output_chunk_ms.

The first-party SDK property timeToFirstChunkMs (time_to_first_chunk_ms in Python) is sent as gen_ai.response.time_to_first_chunk in seconds.

Errors

A span is stored with status error if its OpenTelemetry status is ERROR or it has an error.type attribute. Set the status, and add error.type for error classification and trace error totals.

An exception or gen_ai.client.operation.exception span event adds exception.type, exception.message, and exception.stacktrace to the span’s error details, and is also stored as an error log record.

Tools and agents

Attribute Stored as
gen_ai.tool.name Tool name field
gen_ai.agent.name Agent name field
gen_ai.tool.call.arguments Tool input content; see Tool operations
gen_ai.tool.call.result Tool output content
gen_ai.tool.call.id Attribute only
gen_ai.tool.description Attribute only
gen_ai.agent.id Attribute only

“Attribute only” means the value is kept in the span’s stored attributes but has no field of its own.

User, session, and metadata

Field Attributes, in priority order
User ID user.id, ai.telemetry.metadata.userId, ai.telemetry.metadata.user_id, ai.settings.context.user.id, ai.settings.context.userId, ai.settings.context.user_id, td.user_id, td.metadata.userId, td.metadata.user_id
Session ID anthropic.session.id, gen_ai.conversation.id, session.id, ai.telemetry.metadata.sessionId, ai.telemetry.metadata.session_id, eve.session.id, ai.settings.context.eve.session.id, ai.settings.context.sessionId, ai.settings.context.session_id, td.session_id, td.metadata.sessionId, td.metadata.session_id
Metadata Every attribute that starts with td.metadata., ai.telemetry.metadata., or ai.settings.context., with the prefix removed

For user and session IDs, the ingest API checks the whole list on the span first, then on the resource. A span’s td.user_id therefore wins over a resource’s user.id. If redaction removes the value it finds, the span contributes no ID, and another span in the trace can still supply one.

Metadata is read from both the resource and the span; a span key overrides a resource key with the same name. These keys are not added to metadata because they are user or session IDs:

  • the suffixes userId, user_id, sessionId, and session_id under any metadata prefix
  • user.id and eve.session.id under ai.settings.context.

The first-party SDKs send user.id and gen_ai.conversation.id on every span and log in a propagated scope, and send per-span metadata as td.metadata.<key>.

Input and output content

The span’s operation selects which attributes become its input and output content. For string values marked “parsed” below, the ingest API parses the string as JSON when it starts with { or [ after leading whitespace. If parsing fails, it stores the raw string and records the diagnostic extraction_failed.

Model operations

For the model operations in the operations table:

Direction Attributes, in priority order
Input gen_ai.input.messages (parsed), gen_ai.prompt.N.*, gen_ai.prompt, ai.prompt.messages (parsed), ai.prompt (parsed), langchain.inputs (parsed), traceloop.entity.input (parsed), llm.prompts (parsed)
Output gen_ai.output.messages (parsed), gen_ai.completion.N.*, gen_ai.completion, ai.response.text, ai.response.object (parsed), ai.response.toolCalls (parsed), langchain.outputs (parsed), traceloop.entity.output (parsed), llm.completions (parsed), anthropic.session.outcome_evaluation.explanation, then response metadata
  • gen_ai.prompt.N.* and gen_ai.completion.N.* are indexed attributes, such as gen_ai.prompt.0.role and gen_ai.prompt.0.content. The ingest API rebuilds them into a list of messages ordered by N.
  • gen_ai.system_instructions (parsed) is added as a system message before the input.
  • If no output attribute is present, the output is one assistant message built from gen_ai.response.id, gen_ai.response.finish_reasons, and gen_ai.output.type, when any of them is set.
  • For an evaluate span, ai.evaluation.state and ai.evaluation.questions are read as input after gen_ai.input.messages, and ai.evaluation.answers as output after gen_ai.output.messages.

Tool operations

For execute_tool spans:

Direction Attributes, in priority order
Input gen_ai.tool.call.arguments (parsed), ai.toolCall.args (parsed)
Output gen_ai.tool.call.result (parsed), anthropic.tool_result.content (parsed), ai.toolCall.result (parsed)

Function operations

For function spans, only gen_ai.input.messages and gen_ai.output.messages (both parsed) become content.

What is stored

Content passes through the project’s capture settings and redaction before it is stored. When an attribute’s content is stored in the input or output field, the ingest API removes that attribute from the stored attributes so that the content is not stored twice. If capture is off or redaction removes all of the content, the attribute is not removed this way. The stored attributes then go through the same capture and redaction rules, which can remove or redact it. For details, see Privacy.

Request parameters

The first-party SDKs send these request attributes. The ingest API keeps them in the stored attributes; they have no separate fields.

Attribute Value type
gen_ai.request.temperature Number
gen_ai.request.top_p Number
gen_ai.request.top_k Number
gen_ai.request.max_tokens Number
gen_ai.request.stop_sequences String array
gen_ai.request.seed Number
gen_ai.request.frequency_penalty Number
gen_ai.request.presence_penalty Number

Framework detection

Each span gets a framework from its instrumentation scope name and attributes. The ingest API compares the scope name in lowercase, except for the Claude scopes, and uses the first rule that matches:

Order Rule Framework
1 Scope com.anthropic.claude-platform, com.anthropic.sdk, or a scope that starts with com.anthropic.sdk. claude
2 Scope ai or @telemetry-dev/ai-sdk, or a scope that starts with @ai-sdk vercel-ai-sdk
3 Scope @telemetry-dev/tanstack-ai or @tanstack/ai, or a scope that starts with @tanstack/ai/ tanstack-ai
4 Any span attribute starts with ai. vercel-ai-sdk
5 The scope name contains langchain langchain
6 No rule matches otel

A trace takes the first framework other than otel that the ingest API sees among its spans, or otel if there is none.

The first-party core SDKs use exact scope names:

SDK Instrumentation scope Framework
TypeScript @telemetry-dev/sdk otel
Python telemetry_dev otel

Compatibility attributes by framework

The tables above already include these attributes in priority order. This section groups them by the framework that sends them, so you can check what the ingest API reads from a given instrumentation.

Vercel AI SDK

Purpose Attributes
Input tokens ai.usage.inputTokens, ai.usage.promptTokens
Output tokens ai.usage.outputTokens, ai.usage.completionTokens
Total tokens ai.usage.tokens
Cache-read tokens ai.usage.cachedInputTokens
Cache-creation tokens ai.usage.cacheCreationInputTokens
Reasoning tokens ai.usage.reasoningTokens
Cost ai.usage.cost
Model ai.model.id
Response model ai.response.model
Provider ai.model.provider
Finish reason ai.response.finishReason
Time to first chunk, in ms ai.response.msToFirstChunk, ai.stream.firstChunkMs
Input ai.prompt.messages, ai.prompt
Output ai.response.text, ai.response.object, ai.response.toolCalls
Tool input and output ai.toolCall.args, ai.toolCall.result
User, session, and metadata ai.telemetry.metadata.* and ai.settings.context.*; see User, session, and metadata

OpenInference and Traceloop llm.*

Purpose Attributes
Input tokens llm.token_count.prompt
Output tokens llm.token_count.completion
Model llm.model_name
Provider llm.system
Time to first token, in seconds llm.time_to_first_token, llm.ttft
Time per output chunk, in seconds llm.time_per_output_chunk, llm.time_per_output_token
Input llm.prompts
Output llm.completions

LangChain and Traceloop entities

Purpose Attributes
Input langchain.inputs, traceloop.entity.input
Output langchain.outputs, traceloop.entity.output

Claude Platform and Claude Code

Purpose Attributes
Session ID anthropic.session.id
Finish reason anthropic.message.stop_reason, anthropic.session.turn.stop_reason
One-hour cache creation tokens anthropic.usage.cache_creation.ephemeral_1h_input_tokens
Compaction tokens on SDK request spans anthropic.usage.compaction.input_tokens, anthropic.usage.compaction.output_tokens
Tool output anthropic.tool_result.content
Evaluation output anthropic.session.outcome_evaluation.explanation

Claude Platform telemetry can repeat one request’s usage on several spans. The ingest API counts each request’s tokens and cost once, and prefers the Claude Platform API’s request span over the client SDK’s span for the same request. For setup, see Claude Platform.

Claude Code claude_code.llm_request spans report usage in their own keys. The standard attributes above win when present:

Claude Code key Read as
input_tokens Uncached input. The ingest API adds cache read and cache creation to get total input.
output_tokens Output
cache_read_tokens Cache read
cache_creation_tokens Cache creation
ttft_ms Time to first chunk, in ms
model Model, when no model attribute is present
speed fast turns on fast-mode pricing

These spans get provider anthropic when no provider attribute is present. For setup, see Claude Code.

Last updated on October 18, 2026

Was this page helpful?