DEV Community

azena.ai
azena.ai

Posted on

OpenTelemetry's GenAI semantic conventions are NOT stable yet — here's what actually shipped in 2026

If you search for "OpenTelemetry GenAI semantic conventions" right now, you'll find a pile of blog posts confidently declaring that gen_ai.* "went stable" at some point — one popular claim pins it to "OTel 1.30". The primary sources say otherwise. As of mid-July 2026, every single gen_ai.* attribute, span, metric, and event in the official OpenTelemetry registry carries the stability badge "Development" (the status formerly called "experimental"). Not one is marked Stable.

That doesn't mean you should wait. It means you should know exactly what shipped, what got renamed, and where the moving parts are — because if you instrument against a blog post from 2025, you're emitting deprecated attributes today.

Everything below is checked against the OTel attribute registry, the semantic-conventions release changelogs, and the new GenAI repo, as of 2026-07-16.

The June 2026 split: GenAI moved out of the main repo

The biggest structural change happened on June 12, 2026, with semantic-conventions v1.42.0: all GenAI conventions — model/gen-ai/, the OpenAI-specific ones under model/openai/, and the MCP conventions under model/mcp/ — were deprecated in the main repo and moved to a dedicated repository: open-telemetry/semantic-conventions-genai.

Two things about that repo you should internalize:

  1. It has no tagged release yet. As of July 16, 2026, the releases page is empty. The conventions evolve on main.
  2. Its documents still say "Status: Development" — both gen-ai-spans.md and gen-ai-metrics.md.

For context, the main repo shipped at its usual cadence this year — v1.39.0 (January 12), v1.40.0 (February 19), v1.41.0 (April 28), v1.41.1 (May 11), v1.42.0 (June 12, the GenAI extraction), v1.43.0 (July 3). The last big GenAI additions inside the main repo landed in v1.41.0: streaming metrics (gen_ai.client.operation.time_to_first_chunk and .time_per_output_chunk), invoke_workflow as an operation, and the split of invoke_agent into client vs. internal spans.

So the honest status line is: a rapidly consolidating standard with its own repo, its own SIG momentum, real vendor adoption — and no stability guarantee on attribute names.

What you should emit today

Despite the Development status, the core signal set has settled enough to build on. Here's the short list that matters:

Signal Name Notes
Span attribute (required) gen_ai.operation.name chat, embeddings, execute_tool, invoke_agent, invoke_workflow, create_agent, retrieval, plan, memory ops …
Span attribute (required) gen_ai.provider.name e.g. openai, anthropic, aws.bedrock, gcp.vertex_ai
Span attributes (recommended) gen_ai.request.model, gen_ai.response.model, gen_ai.response.finish_reasons, gen_ai.conversation.id, error.type
Token usage (span) gen_ai.usage.input_tokens, gen_ai.usage.output_tokens
Metric gen_ai.client.token.usage Histogram, unit {token}, split by gen_ai.token.type (input/output)
Metric gen_ai.client.operation.duration Histogram, unit s
Streaming metrics gen_ai.client.operation.time_to_first_chunk, .time_per_output_chunk New as of April 2026
Server-side (self-hosted inference) gen_ai.server.request.duration, .time_to_first_token, .time_per_output_token
Content capture (opt-in only) gen_ai.system_instructions, gen_ai.input.messages, gen_ai.output.messages Not captured by default

You don't need an instrumentation library to emit this. A plain manual span with the standard Python SDK is fully conformant:

from opentelemetry import trace

tracer = trace.get_tracer("my-agent")

def call_llm(client, messages, model="claude-sonnet-4-5"):
    # Span name convention: "{operation} {model}"
    with tracer.start_as_current_span(f"chat {model}") as span:
        span.set_attribute("gen_ai.operation.name", "chat")
        span.set_attribute("gen_ai.provider.name", "anthropic")
        span.set_attribute("gen_ai.request.model", model)

        response = client.messages.create(model=model, messages=messages, max_tokens=1024)

        span.set_attribute("gen_ai.response.model", response.model)
        span.set_attribute("gen_ai.usage.input_tokens", response.usage.input_tokens)
        span.set_attribute("gen_ai.usage.output_tokens", response.usage.output_tokens)
        return response
Enter fullscreen mode Exit fullscreen mode

Note what's absent: no prompt text, no completion text. Under the current conventions, content is metadata-opt-in — a sane default if you operate under GDPR or handle customer data.

The rename traps

This is where those outdated blog posts actively hurt you. If your instrumentation (or your vendor's) was written against the 2024/2025 state of the conventions, run this migration checklist:

  • [ ] gen_ai.systemgen_ai.provider.name. The old attribute is deprecated in the registry. If your dashboards group by gen_ai.system, they'll go dark as libraries update.
  • [ ] gen_ai.usage.prompt_tokensgen_ai.usage.input_tokens and gen_ai.usage.completion_tokensgen_ai.usage.output_tokens. Cost dashboards built on the old names silently under-count once emitters switch.
  • [ ] gen_ai.prompt and gen_ai.completion are removed entirely — not renamed. Content capture now goes through the opt-in gen_ai.input.messages / gen_ai.output.messages (and gen_ai.system_instructions).
  • [ ] Grep your alert rules and saved queries, not just your code. The emitting side and the querying side drift independently.
  • [ ] Expect more of this. Development status explicitly means names can still change. OTel's usual transition mechanism is dual-emission via OTEL_SEMCONV_STABILITY_OPT_IN — plan for a window where you handle both old and new names.

Agents and MCP get first-class spans

The part most relevant to anyone running agents in production: the conventions now model the whole agent execution as a span tree, not just single LLM calls.

invoke_agent  (the agent run)
├── chat      (each model call)
│   └── execute_tool   (each tool invocation)
├── chat
└── execute_tool
Enter fullscreen mode Exit fullscreen mode

gen_ai.operation.name covers the full agent lifecycle — create_agent, invoke_agent, invoke_workflow, execute_tool, retrieval, plan, plus memory operations. And notably, the MCP conventions moved into the same GenAI repo with the v1.42.0 extraction, so MCP tool calls are part of the same trace vocabulary as the agent that issues them.

This is no longer theoretical: per the official OTel blog's 2026 GenAI observability post, coding agents like VS Code Copilot, OpenAI Codex, and Claude Code (the latter in beta) already emit OTel GenAI traces — while the same post stresses that the conventions remain under active development.

One thing traces won't give you, though, is whether the agent's output was any good. Latency and token counts are necessary but not sufficient — for the quality side you need evals wired into the same pipeline; we wrote up how we approach that for production agents here (German).

Who actually speaks gen_ai.* today

Adoption is real but uneven — schema fragmentation hasn't gone away:

  • Datadog supports the OTel GenAI semantic conventions natively in its LLM/Agent Observability product (they published a dedicated post on it).
  • Langfuse accepts traces through a full OTLP endpoint and maps gen_ai.* onto its own data model.
  • Arize Phoenix's native schema is OpenInference (its own llm.* namespace); it interoperates through a translation layer (e.g. an OpenInferenceSpanProcessor for converting OpenLLMetry traces), with an open RFC discussion about closer gen_ai.* alignment.
  • OpenLLMetry/Traceloop is OTel-based — parts of the original GenAI conventions actually came from an OpenLLMetry donation — but still emits some deprecated attributes (gen_ai.prompt/gen_ai.completion); there's an open issue tracking the migration.

Converters exist between these worlds, but fidelity varies. Which leads to the practical conclusion.

The playbook

  1. Instrument once, against gen_ai.*. Whether hand-rolled spans or an OTel-based library — the convention is the contract, not the vendor.
  2. Treat OTLP as the socket. Export via OTLP and Langfuse, Datadog, or a Grafana stack become swappable backends instead of lock-in decisions.
  3. Cost dashboards come free. gen_ai.client.token.usage + gen_ai.request.model + gen_ai.provider.name give you a standardized cost schema across every provider you use.
  4. Keep prompt capture opt-in. The default is metadata-only; only enable gen_ai.input.messages/gen_ai.output.messages where you've settled the privacy question.
  5. Budget for renames. Development status is a feature warning, not fine print. The gen_ai.system rename won't be the last one.

We run agent observability for German Mittelstand clients at azena.ai. If you read German, our deeper comparison of five observability tools for exactly this stack lives here.

Top comments (0)