Unified Tracing with OpenTelemetry GenAI Semantic Conventions

10 min read Module 8 of 10 Lesson 22 of 28

What you'll learn

  • Name the core OpenTelemetry GenAI span attributes and what each one records
  • Emit a real OpenTelemetry span around a gateway request using the standard gen_ai.* attribute names
  • Explain why a shared semantic convention matters specifically for a multi-provider gateway, versus a single-provider integration
  • Trace a request through the gateway's own span plus the underlying provider-call span using gen_ai.operation.name
Building this at your company? For enterprise and company teams taking this to production: book a 30-minute session with our AI engineers for architecture guidance, code review, and a rollout plan for your use case.
Book a Team Session

Module 7 logged security and cost events. This module instruments quality and performance, and the first question is what schema to log them in. OpenTelemetry’s GenAI semantic conventions answer that: a CNCF-backed, vendor-neutral standard for exactly this kind of telemetry, and for a gateway routing across many providers, using it instead of an ad hoc schema is the difference between one dashboard that works for every provider and a different query for each one.

The Core Attributes

The GenAI semantic conventions define a standard set of span attributes for any LLM call:

AttributeMeaning
gen_ai.provider.nameWhich provider actually served the request (anthropic, openai, etc.)
gen_ai.request.modelThe model requested (e.g. gpt-4o)
gen_ai.operation.nameThe kind of call (chat, embeddings, execute_tool)
gen_ai.usage.input_tokensPrompt/input tokens consumed
gen_ai.usage.output_tokensCompletion/output tokens generated
gen_ai.response.finish_reasonsWhy generation stopped (stop, tool_calls, etc.)

Recommended span naming follows the pattern {gen_ai.operation.name} {gen_ai.request.model}, e.g. chat gpt-4o, which is what makes traces from completely different providers instantly comparable in a trace viewer without translating naming schemes in your head.

Emitting a Real Span

from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode

tracer = trace.get_tracer("ai-gateway")

def traced_completion(gateway_client, model: str, messages: list[dict], provider_hint: str):
    with tracer.start_as_current_span(f"chat {model}") as span:
        span.set_attribute("gen_ai.operation.name", "chat")
        span.set_attribute("gen_ai.request.model", model)
        span.set_attribute("gen_ai.provider.name", provider_hint)  # known once routing (Module 4) resolves it

        try:
            response = gateway_client.chat.completions.create(model=model, messages=messages)
        except Exception as e:
            span.set_status(Status(StatusCode.ERROR, str(e)))
            raise

        usage = response.usage
        span.set_attribute("gen_ai.usage.input_tokens", usage.prompt_tokens)
        span.set_attribute("gen_ai.usage.output_tokens", usage.completion_tokens)
        span.set_attribute(
            "gen_ai.response.finish_reasons",
            [response.choices[0].finish_reason],
        )
        return response

Every field set here is a real, spec-defined GenAI attribute name, not an invented one, which is exactly what lets this span show up correctly in any OpenTelemetry-compatible backend (Jaeger, Grafana Tempo, an OTLP-compatible SaaS) without custom parsing logic on the backend side.

Why This Matters More for a Gateway Than a Single-Provider App

A single-provider application’s tracing only ever needs to describe one provider’s shape, custom field names would work fine, if awkwardly, since there’s nothing to compare them against. A gateway is different: the exact same logical request (model="production-chat") can be served by Anthropic today and a self-hosted vLLM deployment tomorrow, depending on the routing decisions from Module 4, retry and fallback behavior from Module 5, or the task classification from Lesson 12. A dashboard built on standard gen_ai.* attributes handles that transparently, a query grouping by gen_ai.provider.name just works regardless of how many providers are in the model_list; a dashboard built on provider-specific field names would need new logic every time a new provider is added.

flowchart LR
    Req["Gateway request span:\nchat production-chat"] --> Route["Routing decision\n(Module 4)"]
    Route -->|"served by Anthropic"| SpanA["gen_ai.provider.name: anthropic\ngen_ai.request.model: claude-sonnet-4-5"]
    Route -->|"served by self-hosted vLLM"| SpanB["gen_ai.provider.name: vllm\ngen_ai.request.model: llama-3.1-8b"]
    SpanA --> Dashboard["One dashboard,\none query,\ngen_ai.* attributes"]
    SpanB --> Dashboard

    style Req fill:#4338CA,stroke:#4338CA,color:#ffffff,rx:8px,ry:8px
    style SpanA fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style SpanB fill:#EEF0F7,stroke:#6366F1,color:#0F172A,rx:8px,ry:8px
    style Dashboard fill:#F7F8FC,stroke:#0D9488,color:#0F172A,rx:8px,ry:8px

One caveat worth flagging: the GenAI conventions are explicitly still under active development (Development stability, per OpenTelemetry’s own project status), so attribute names and structure can evolve. The practical response is to isolate the mapping from a raw provider response to gen_ai.* attributes in one place in the gateway’s codebase, much like the adapter pattern from Lesson 6, so a future spec revision means updating one function, not every call site that emits a span. With unified traces flowing, Lesson 23 uses this same data to answer a harder question than “what happened”: whether the answers themselves are still good.

Knowledge Check

3 questions to test your understanding

1 Why does it matter, specifically for a gateway routing across multiple providers, that OpenTelemetry's GenAI conventions define standard attribute names like gen_ai.request.model and gen_ai.usage.input_tokens rather than letting each integration invent its own field names?

2 What does the gen_ai.provider.name attribute represent in a trace span, and why is it useful in a gateway context specifically?

3 What is the current stability status of the OpenTelemetry GenAI semantic conventions, and what does that imply for how a gateway should consume them?

Go further with expert guidance

Ready to build production AI?
Talk to our R&D team.

These courses give you the foundation. Our embedded AI teams take you from prototype to production in 30–90 days, with your team, your codebase, your goals. Book a free strategy call to see how we can accelerate your AI initiative.

30 minutes · No obligation · Expert AI engineers, not sales reps

AI Architecture Review

Audit your current stack and identify high-impact improvements

Project Review

Get expert feedback on your AI implementation and codebase

Team Mentoring

Upskill your engineers with hands-on AI coaching sessions

AI Strategy

Define your AI roadmap, prioritization, and implementation plan