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.pricing_source |
string | Where the price came from: table, estimated or unpriced |
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.
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 plusgen_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.
Session Tracking¶
| Attribute | Type | Description |
|---|---|---|
session.id |
string | Session identifier |
user.id |
string | User identifier |
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, which this library enables by default via
OTEL_EXPERIMENTAL_RESOURCE_DETECTORS. host.ip has no upstream detector and
is resolved by the library.
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 (prompt/completion) |
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 consecutive streamed chunks |
GPU Metrics¶
See GPU Metrics Guide for the complete list.
Evaluation Metrics¶
See Evaluation Guide for detector-specific metrics.
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.