Skip to content

Semantic Conventions

TraceVerde follows OpenTelemetry semantic conventions for GenAI with additional custom attributes.

Span Attributes

GenAI Core (OTel Standard)

Attribute Type Description
gen_ai.system string Provider name (e.g., "openai", "anthropic")
gen_ai.request.model string Requested model identifier
gen_ai.response.model string Actual model used in response
gen_ai.request.type string Call type ("chat", "embedding", "completion")
gen_ai.usage.input_tokens int Input token count
gen_ai.usage.output_tokens int Output token count
gen_ai.usage.total_tokens int Total token count
gen_ai.usage.cache_read.input_tokens int Prompt-cache hits (Anthropic cache_read_input_tokens, OpenAI prompt_tokens_details.cached_tokens). Included in gen_ai.usage.input_tokens.
gen_ai.usage.cache_write.input_tokens int Prompt-cache writes (Anthropic cache_creation_input_tokens). Current spelling: semantic-conventions-genai#440 renamed cache_creation to cache_write.
gen_ai.usage.cache_creation.input_tokens int Superseded spelling of the row above. Emitted alongside it only under OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai/dup (the default).
gen_ai.usage.reasoning.output_tokens int Reasoning tokens (OpenAI o1/o3-style completion_tokens_details.reasoning_tokens). Included in gen_ai.usage.output_tokens.
server.address string Host of the model endpoint, derived from the SDK client's base URL. Omitted when the SDK exposes no base URL -- absent means "endpoint unknown", never a guessed vendor host.
server.port int Port of the model endpoint. Explicit port when the base URL carries one, otherwise the scheme default.
gen_ai.usage.{text,image,audio}.input_tokens int Per-modality input tokens (semantic-conventions-genai#440). A subset of gen_ai.usage.input_tokens -- do not sum alongside it. Absent when the provider reports no breakdown.
gen_ai.usage.{text,image,audio}.output_tokens int Per-modality output tokens; subset of gen_ai.usage.output_tokens.
gen_ai.usage.{text,image,audio}.cache_read.input_tokens int Per-modality prompt-cache hits.
gen_ai.response.finish_reasons string[] Finish reasons as an array (registry spelling).
gen_ai.response.finish_reason string Superseded singular form, emitted alongside the array under gen_ai/dup.
gen_ai.embeddings.dimension.count int Requested embedding dimensions (registry spelling; supersedes gen_ai.request.dimensions).
gen_ai.request.encoding_formats string[] Requested encoding formats as an array (registry spelling; supersedes the singular gen_ai.request.encoding_format).
gen_ai.request.seed int Sampling seed, when the caller passed one.
gen_ai.request.stream bool Whether the caller requested streaming.
gen_ai.request.top_k int Top-k sampling parameter, when passed.
gen_ai.request.choice.count int Number of completions requested (n, or Google's candidate_count).
gen_ai.output.type string json or text, derived from the caller's response_format.

| gen_ai.agent.token_budget | int | Cumulative token cap configured for the invocation (semantic-conventions-genai#425). | | gen_ai.agent.token_budget.consumed | int | Tokens actually consumed across the invocation's inference calls. | | gen_ai.agent.iteration_budget | int | Configured iteration / turn / recursion limit. | | gen_ai.agent.iteration_budget.consumed | int | Inference calls completed during the invocation. | | gen_ai.latency.time_in_queue | float | Scheduler queue wait, seconds (self-hosted engines). | | gen_ai.latency.time_to_first_token | float | TTFT, seconds. | | gen_ai.latency.e2e | float | End-to-end request latency, seconds. | | gen_ai.latency.time_in_model_prefill | float | Prefill phase duration, seconds. | | gen_ai.latency.time_in_model_decode | float | Decode phase duration, seconds. | | gen_ai.latency.time_in_model_inference | float | Total on-engine inference duration, seconds. | | gen_ai.request.id | string | Engine-assigned request id (vLLM / SGLang). |

Engine latency keys are the de-facto vocabulary, not ours

gen_ai.latency.* is emitted verbatim as vLLM and SGLang define it, and as semantic-conventions-genai#408 proposes standardising. Running vLLM's own OTLP tracing alongside this library therefore yields one vocabulary rather than two to join. These keys are not yet in the registry.

Agent budget consumption and threads

Consumption is accumulated in a context-local frame, so it survives head sampling that drops the child inference spans. A call made on a thread that does not carry the context (a bare threading.Thread) is not counted -- consistent with tracing itself, which would also lose the parent/child span relationship for such a call.

Request parameters are recorded only when the caller passes them

An absent gen_ai.request.* attribute means the application did not set that parameter. The library does not materialise provider defaults, which would report a top_k or seed the application never chose.

| gen_ai.usage.token_count_estimated | bool | true when prompt/completion token counts came from a fallback estimate (e.g. multimodal Ollama responses lacking prompt_eval_count, or HuggingFace vision/audio pipelines that don't surface usage). Absent on spans whose tokens come from the provider response. | | gen_ai.usage.image_count | int | Number of input images counted by the instrumentor for multimodal calls (vision pipelines). | | gen_ai.usage.audio_seconds | float | Total input audio duration in seconds for ASR / audio pipelines. | | gen_ai.usage.audio_duration_seconds | float | Canonical total input audio duration in seconds for speech-to-text and audio pipelines. | | gen_ai.operation.name | string | Operation being performed (chat, embeddings) |

Cost lives under gen_ai.usage.cost.*

Earlier revisions of this page listed gen_ai.cost.amount as the cost attribute. Cost is reported under gen_ai.usage.cost.total and its breakdown. Only the Hyperbolic instrumentor also emits gen_ai.cost.amount, for backwards compatibility with dashboards built against it; do not query it as a general cost attribute, because no other provider sets it.

Embeddings

Emitted on embedding spans (openai.embeddings), which are the retrieval half of a RAG trace.

Attribute Type Description
gen_ai.request.type string embedding - the value cost lookup dispatches on
gen_ai.request.input_count int Number of texts in the request; a batch of chunks counts each one, pre-tokenised input counts as one
gen_ai.request.dimensions int Requested vector dimension, when the caller asked for one
gen_ai.request.encoding_format string Requested encoding format, when set
gen_ai.response.embedding_count int Number of vectors returned
gen_ai.response.vector_size int Dimension of the returned vectors
embedding.model_name string Embedding model; set only when content capture is enabled
embedding.text string The embedded text, truncated to GENAI_CONTENT_MAX_LENGTH; set only when content capture is enabled
embedding.vector string JSON-encoded vector; off unless capture_embedding_vectors is set, since vectors dominate span size

Token usage and cost are recorded exactly as for chat, priced against the embeddings table rather than the chat one.

Retrieval quality

The rag.* attributes are additive to retrieval.* and db.vector.*. They describe embedding provenance, score quality, corpus version, and downstream context policy. top_k and result count remain db.vector.top_k and retrieval.document_count; see the retrieval quality guide.

gen_ai.rag.context is application-owned input for evaluation processors. The library reads it when present but does not emit it automatically.

Degradation events

Capability downgrades are recorded as a gen_ai.degraded span event rather than an error. The event attributes are gen_ai.degraded.component, gen_ai.degraded.from, gen_ai.degraded.to, gen_ai.degraded.reason, and gen_ai.degraded.recoverable. The span status remains OK when the request successfully completes through the degraded path.

Cost Attributes

Attribute Type Description
gen_ai.usage.cost.total float Total cost in USD
gen_ai.usage.cost.prompt float Prompt token cost
gen_ai.usage.cost.completion float Completion token cost
gen_ai.usage.cost.decision float Decision-model cost, on decision spans only. Equal to cost.total there, so a reader can keep decision spend apart without reading the span kind
gen_ai.usage.cost.pricing_source string Where the price came from: table, estimated or unpriced (decision spans: table or unpriced, never estimated)
gen_ai.request.model.deprecated bool Set only when the provider has announced the model's retirement
gen_ai.request.model.deprecation_note string Retirement date and migration target

Read pricing_source before summing cost

A gen_ai.usage.cost.total of 0.0 is ambiguous on its own — it means either "this call really was free" or "no price could be found for this model". Always read pricing_source alongside it:

Value Meaning
table Matched an entry in the shipped pricing file. Billable.
estimated No entry; price inferred from the parameter count in the model name via the local-model size tier. Indicative only — do not treat as a billable figure.
unpriced No price could be determined. cost.total will be 0.0 and must not be counted as free spend.

A dashboard that sums cost.total without filtering on pricing_source will silently under-report spend for every unpriced model.

Decision Spans

Decision-model calls are OpenInference DECISION spans (openinference.span.kind = DECISION). The full attribute table, and which attributes need content capture, is in Decision models.

Attribute Type Recorded
decision.system string always: openai, typesafe or custom
decision.provider string recognised host, or GENAI_DECISION_PROVIDER
decision.model_name, decision.request.model_name, decision.response.model_name string always / when they differ
decision.token_count.input, decision.token_count.output int when reported
genai.decision.protocol string always: openai_decisions, systemone, granite_adapter, manual
genai.decision.question_count, genai.decision.refusal_count int always
genai.decision.question.types string[] always
genai.decision.input.modalities string[] always: text, image, audio
genai.decision.input.audio_seconds double WAV audio inputs
genai.decision.option_count int one-question calls
genai.decision.question.names string[] content capture on
genai.decision.min_confidence double content capture on
genai.decision.choice string content capture on, one-question calls
genai.decision.confidence, .probability, .score, .margin double content capture on, one-question calls
evaluation.skipped_reason string decision_span when the response detectors had no text to read

Streaming Latency

Emitted only on streamed calls. On a non-streamed call these attributes are absent, not zero.

Attribute Type Description
gen_ai.server.time_to_first_token float Seconds from request start to the first streamed chunk (TTFT)
gen_ai.server.time_per_output_token float Seconds per output token after the first (TPOT), (duration - ttft) / max(output_tokens - 1, 1)
gen_ai.server.ttft float Superseded spelling of TTFT, still emitted for consumers already reading it
gen_ai.streaming.token_count int Number of chunks yielded by the stream (chunks, not tokens)
gen_ai.streaming.tpot_unavailable_reason string Why TPOT was omitted; currently only output_token_count_unavailable

An absent TTFT is not a zero

Span duration cannot substitute for TTFT: a fast-starting long answer and a slow-starting short one can share a duration. But a fabricated 0 is worse than nothing — it is indistinguishable from an instant first token and drags down every average it enters.

So these attributes are omitted whenever they were not genuinely measured:

  • Non-streamed calls carry neither attribute.
  • TPOT needs a real output-token count. Some providers only send usage in the final chunk when asked. With OpenAI, for example, pass stream_options={"include_usage": True}; without it the span gets TTFT plus gen_ai.streaming.tpot_unavailable_reason, and no TPOT. The chunk count is not used as a stand-in — chunks are not tokens.
  • Text-to-speech streams report TTFT (time to first audio byte) and never TPOT, having no output tokens to divide by.

Report a missing attribute as "not measured" rather than substituting zero.

Agent Graph

Added to every span that carries gen_ai.agent.name, so OpenInference backends (Arize AX, Phoenix) can draw the agent graph. Keys a span already carries are kept. Disable with GENAI_ENABLE_AGENT_GRAPH_ATTRIBUTES=false.

Attribute Type Description
agent.name string Agent name, copied from gen_ai.agent.name
graph.node.id string Graph node; the agent name, so repeat runs share one node
graph.node.name string Node label (OpenInference spelling)
graph.node.display_name string Node label (Arize AX spelling)
graph.node.parent_id string Node of the nearest enclosing agent; unset on a root agent
openinference.span.kind string AGENT

LangGraph node spans (langgraph.node.<name>) carry:

Attribute Type Description
langgraph.node string Node name
langgraph.step int Superstep the node ran in
langgraph.triggers string[] Channels that triggered the node
langgraph.node.interrupted bool Set when the node paused with interrupt()
graph.node.id string Node name (agent graph attributes enabled)
metadata string JSON {"langgraph_node", "langgraph_step"}, the OpenInference key Arize reads for LangGraph
openinference.span.kind string CHAIN (also on the graph run span, whose graph.node.id is the graph name)

Session Tracking

Attribute Type Description
session.id string Session identifier; groups a conversation's traces into one session
gen_ai.conversation.id string Same value as session.id (OTel GenAI spelling)
user.id string User identifier

Every span gets these from the enclosing genai_otel.session() block, or else from its parent span, so a session resolved on a framework span reaches every span beneath it. A span that already carries a key keeps it. Disable with GENAI_ENABLE_SESSION_PROPAGATION=false.

Between services the ids can travel as W3C baggage under the same keys, when the caller sets GENAI_SESSION_BAGGAGE_KEYS and the callee GENAI_SESSION_BAGGAGE_ACCEPT.

Resource Attributes

Every resource attribute is a name from the OpenTelemetry registry. They identify where a span came from, which is what makes it possible to separate traffic per host and per instance when several copies of an application run at once.

Attribute Description
service.name Service name
service.instance.id Instance identifier. Taken from OTEL_SERVICE_INSTANCE_ID when set, otherwise generated - see Host and Instance Identity
deployment.environment.name Environment name
telemetry.distro.name "genai-otel-instrument"
telemetry.distro.version Package version
host.name Hostname of the machine
host.arch CPU architecture
host.ip Non-loopback IP addresses of the host (array)
os.type / os.version Operating system
process.pid / process.parent_pid Process identifiers
process.command Program that was started
process.command_line Full startup command line
process.command_args Startup arguments (array)
process.executable.name / process.executable.path Interpreter location
process.owner User the process runs as (requires psutil)
process.runtime.name / .version / .description Python runtime

The host.*, os.* and process.* groups come from the OpenTelemetry SDK's own detectors. This library enables host and os by default via OTEL_EXPERIMENTAL_RESOURCE_DETECTORS; process is opt-in (add it to that variable). host.ip has no upstream detector and is resolved by the library only when GENAI_OTEL_RESOURCE_HOST_IP=true.

Credential values in process.command_args are redacted

A value following a flag whose name looks like a credential (--password, --api-key, --token, --client-secret, ...) is replaced with ***REDACTED***, in both the --flag value and --flag=value forms, as are name=value pairs and scheme://user:password@host URLs. This has to happen in the SDK: process.command_args is an array, so a flag and its value become separate elements and no downstream name=value rule can reconnect them. Redaction is pattern-based and cannot catch a credential passed under an unrecognised flag name - see Host and Instance Identity for how to drop the process detector entirely.

Superseded spellings, still emitted alongside the current names for the same reason the GenAI token attributes are dual-emitted, and removed at 2.0:

Superseded Current
environment deployment.environment.name
telemetry.auto.name telemetry.distro.name
telemetry.auto.version telemetry.distro.version

Metrics

GenAI Metrics

Metric Type Unit Description
gen_ai.requests Counter requests Request count by provider/model
gen_ai.client.token.usage Counter tokens Token usage, labelled token_type (prompt/completion). No longer defined by the conventions; see Token usage metrics
gen_ai.client.operation.duration Histogram seconds Request latency
gen_ai.cost Counter USD Estimated costs
gen_ai.errors Counter errors Error count by type
gen_ai.server.time_to_first_token Histogram seconds TTFT, recorded on streamed calls only
gen_ai.server.time_per_output_token Histogram seconds TPOT, recorded when the output-token count is known
gen_ai.server.ttft Histogram seconds Superseded spelling of TTFT
gen_ai.server.tbt Histogram seconds Time between streamed chunks: one sample per stream, its mean gap. With GENAI_METRICS_PROFILE=full, one sample per gap

Token Usage Metrics

semantic-conventions-genai#374 replaced the gen_ai.client.token.usage histogram and its gen_ai.token.type attribute with these instruments. They are recorded for every inference call whose provider reported usage.

Metric Type Unit Records
gen_ai.client.inference.usage.input_tokens Counter {token} Input tokens, cached ones included
gen_ai.client.inference.usage.output_tokens Counter {token} Output tokens, reasoning ones included
gen_ai.client.inference.usage.cache_read.input_tokens Counter {token} Input tokens served from the provider's cache. Part of input_tokens
gen_ai.client.inference.usage.cache_write.input_tokens Counter {token} Input tokens written to the provider's cache. Part of input_tokens
gen_ai.client.inference.usage.reasoning.output_tokens Counter {token} Output tokens spent on reasoning. Part of output_tokens
gen_ai.client.inference.operation.input_tokens Histogram {token} One call's input tokens
gen_ai.client.inference.operation.output_tokens Histogram {token} One call's output tokens

Attributes, each only when the span has it: gen_ai.operation.name, gen_ai.provider.name (read from gen_ai.system when only that is set), gen_ai.request.model, gen_ai.response.model, server.address, server.port. The counters also carry gen_ai.token.modality; the histograms do not, because percentiles taken per modality do not add up to a call.

  • Modality. Where the provider breaks usage down (text, image, audio), each part is counted under its modality and whatever the breakdown leaves over is counted under unknown, so the modalities of one counter always add up to its total. Usage with no breakdown is all unknown.
  • Zero and missing counts record nothing on the counters, as semantic-conventions-genai#602 proposes: a call with no cache activity adds no cache_read point. The histograms record one value per call whenever usage was reported, zero included.
  • Agent runs. When a framework span (an agent, crew, graph or workflow run) is the one that accounts a run's tokens, they are added to the counters but not to the histograms: a run's total is not the size of any one model call.
  • Cached input is counted as input. Anthropic reports input_tokens without its cache reads and writes; the input counter adds them back so that the cache counters stay subsets of it. The gen_ai.usage.input_tokens span attribute is unchanged.
  • Not recorded: embeddings calls (the conventions are to give them their own instrument), token counts this library estimated because the provider returned none (they stay on the span, marked gen_ai.usage.token_count_estimated, and on gen_ai.client.token.usage), and failed calls.

gen_ai.client.token.usage is still recorded alongside these with its existing token_type label, so dashboards built on it keep working. It is removed at 2.0.

GPU Metrics

See GPU Metrics Guide for the complete list.

Evaluation Metrics

See Evaluation Guide for detector-specific metrics.

Evaluation Runtime Attributes

Written on a span by the evaluation runtime, whichever detectors are enabled.

Attribute Type Description
evaluation.completed boolean The detectors ran for this span
evaluation.skipped string Why they did not: overloaded (the queue was full) or shutdown (the process was exiting)
evaluation.truncated boolean Detectors read only part of the prompt or response
evaluation.prompt.length int Characters in the prompt, when it was cut
evaluation.prompt.analyzed_length int Characters of it the detectors read
evaluation.response.length int Characters in the response, when it was cut
evaluation.response.analyzed_length int Characters of it the detectors read
evaluation.pii.prompt.pattern_analyzed_length int Characters the PII patterns covered
evaluation.pii.prompt.model_analyzed_length int Characters the PII entity model read
evaluation.<detector>.prompt.analyzed_length int Characters read by prompt_injection, restricted_topics or bias when it has a block policy: the whole prompt

Multimodal Content-Part Attributes (v1.0.0)

Emitted only when GENAI_OTEL_MEDIA_CAPTURE_MODE is set to reference_only or full. Default behaviour (off) emits nothing additional.

Attribute Type Notes
gen_ai.prompt.{n}.role string user, system, assistant, tool
gen_ai.prompt.{n}.content.{m}.type enum text | image | audio | video | document
gen_ai.prompt.{n}.content.{m}.text string text parts only
gen_ai.prompt.{n}.content.{m}.media_uri string URI returned by the configured store
gen_ai.prompt.{n}.content.{m}.media_mime_type string e.g. image/png
gen_ai.prompt.{n}.content.{m}.media_byte_size int size of the captured payload
gen_ai.prompt.{n}.content.{m}.media_source enum inline_offloaded | external_url | reference_only
gen_ai.completion.{n}.* — mirror namespace for generated content
gen_ai.media.stripped_reason enum size_exceeded, modality_not_allowed, redactor_error, upload_error

Not yet part of upstream OTel GenAI semconv. See open-telemetry/semantic-conventions#3672 for the upstream contribution.