Area 04 // EvalsLesson 74 / 14015 min read
Tracing and OpenTelemetry for LLMs
Tracing records every step of a request (retrieval, each model call with its prompt, tokens and latency, each tool call) as a tree of timed spans, and OpenTelemetry is the open standard for producing and shipping those traces, with shared GenAI attribute names so any tool can read them.
Think of it like this
Analogy
A parcel tracking page shows every step of a delivery: picked up 09:12, sorted at the hub 11:40, out for delivery 14:05, delivered 15:31. If a parcel is late, you can see exactly where it got stuck.
A trace is that tracking page for one AI request. Each step (search, model call, tool call) is a span with its time, details and result.
The problem it solves
A user says "the assistant gave me a wrong answer yesterday". Without tracing, you have the question and maybe the answer, but not what was retrieved, which prompt version ran, what the tool returned, how many tokens it used or which step was slow. Debugging becomes guesswork, evals can't use real failures, and cost or latency problems are invisible. Agents with dozens of steps make this far worse.
How it works: tracing
- A trace represents one request (or one agent task) end to end.
- It contains spans: timed steps with a name, start and end, a parent (so they form a tree), a status (ok or error) and attributes (key-value details).
- For LLM apps, record per span: inputs and outputs (prompts, completions, retrieved chunk IDs), model name, parameters, token counts, cost, latency, tool names and arguments, errors, and identifiers (user, session, prompt version, app version).
- Instrument your code: wrap each step in a span, manually or automatically with libraries that instrument LLM SDKs and frameworks.
- Export spans to a backend (Langfuse, LangSmith, Arize Phoenix, Datadog, Grafana Tempo, Honeycomb…), where you search, filter, view trees and build dashboards.
- Use traces everywhere: debugging, error analysis, agent evals, cost and latency metrics, and adding real failures to golden sets.
- Protect data: redact secrets and personal information, sample if volume is high, and control retention.
How it works: OpenTelemetry for LLMs
- OpenTelemetry (OTel) is the open, vendor-neutral standard (API, SDKs, and the OTLP wire protocol) for traces, metrics and logs, used across the software industry.
- Instrument once, export anywhere: a tracer creates spans; an exporter sends them (via OTLP) to any compatible backend, so switching tools doesn't mean re-instrumenting.
- GenAI semantic conventions define standard attribute names for model calls, such as
gen_ai.operation.name(e.g.chat),gen_ai.provider.name,gen_ai.request.model,gen_ai.response.model,gen_ai.usage.input_tokensandgen_ai.usage.output_tokens. As of November 2026 they live in their own OpenTelemetry repository and are still marked "Development" (they may change). - Auto-instrumentation packages (OpenTelemetry contrib and others like OpenLLMetry and OpenInference) wrap popular LLM SDKs and frameworks to emit these spans automatically.
- Context propagation links spans across services (a trace ID passed in HTTP headers), so a request through your API, a worker queue and a model call appears as one trace.
- Many LLM observability tools accept OTel directly (Langfuse's v5 SDK is built on OpenTelemetry), so the same spans power both general and AI-specific dashboards.
See it in code
- Install
pip install opentelemetry-sdk- Needs
- Python 3.10+. Runs offline: spans go to an in-memory exporter instead of a backend.
A request traced with OpenTelemetry: a root span, a retrieval span and a model-call span using GenAI attribute names.
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter
exporter = InMemorySpanExporter() # in production: an OTLP exporter to your backend
provider = TracerProvider()
provider.add_span_processor(SimpleSpanProcessor(exporter))
tracer = provider.get_tracer("rag-app")
def retrieve(q):
with tracer.start_as_current_span("retrieve") as span:
span.set_attribute("retrieval.top_k", 3)
return ["chunk-1", "chunk-2", "chunk-3"]
def call_llm(prompt):
# GenAI semantic conventions: standard attribute names any backend understands.
with tracer.start_as_current_span("chat claude-haiku-5-5") 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", "claude-haiku-5-5")
span.set_attribute("gen_ai.usage.input_tokens", 812)
span.set_attribute("gen_ai.usage.output_tokens", 64)
return "You get 25 days."
with tracer.start_as_current_span("POST /ask") as root: # one trace = the whole request
root.set_attribute("user.id", "u42")
call_llm(retrieve("leave days?"))
for s in sorted(exporter.get_finished_spans(), key=lambda s: (s.parent is not None, s.start_time)):
parent = s.parent.span_id if s.parent else None
depth = 0 if parent is None else 1
attrs = {k: v for k, v in s.attributes.items() if k.startswith(("gen_ai.usage", "retrieval", "user"))}
print(" " * depth + f"{s.name:24} {attrs}")POST /ask {'user.id': 'u42'}
retrieve {'retrieval.top_k': 3}
chat claude-haiku-5-5 {'gen_ai.usage.input_tokens': 812, 'gen_ai.usage.output_tokens': 64}To send real traces, replace InMemorySpanExporter with OTLPSpanExporter (from opentelemetry-exporter-otlp) pointed at your backend. In real code the token counts come from the API response's usage.
A real example
Example
Finding a 9-second answer in a 2-second app
An assistant's p95 latency jumped from 2 s to 9 s. Traces showed the root span taking 9 s, and inside it a "rewrite query" span of 6.5 s: a recent change had routed query rewriting to a large reasoning model at high effort. Switching that step back to a small model fixed it the same day.
The same traces revealed that 4% of requests retried the main model call after timeouts, which explained a cost increase nobody had noticed.
The easy way
Start here
Use an LLM observability tool's SDK (e.g. Langfuse) or decorator on your main request function and your model calls. Within an hour you'll see every request as a tree with prompts, outputs, tokens and timings.
When you grow: the production setup
Teams instrument with OpenTelemetry (manual spans plus auto-instrumentation for LLM SDKs), propagate context across services and queues, follow GenAI semantic conventions, export via OTLP to both AI-specific and general observability backends, redact sensitive data in processors, sample at high volume, and link traces to evals, feedback and cost dashboards.
Trade-offs
What you gain
- Makes every request debuggable: what was asked, retrieved, called and answered.
- Feeds error analysis, evals, cost and latency metrics.
- OpenTelemetry avoids vendor lock-in.
What it costs you
- Traces hold sensitive content; privacy and retention need care.
- Storage and backend costs grow with traffic.
- GenAI conventions are still evolving.
When not to use it
- Never skip tracing in production LLM apps; sample or redact instead of turning it off.
Common mistakes
- Logging only the final answer, not the retrieved context and tool calls.
- Sending secrets or personal data to a third-party tracing service.
- Spans without user, session or version identifiers, so you can't filter.
- Inventing custom attribute names instead of the standard ones.
- Traces that break across services because context isn't propagated.
Interview questions
Try answering out loud first, then open the model answer.
What would you trace in an LLM application?
Model answer
A root span per request or task with user, session, app and prompt versions; child spans for each retrieval (query, chunk IDs, scores), each model call (model, parameters, input and output, token usage, cost, latency, stop reason), each tool call (name, arguments, result, errors) and guardrail checks. Plus errors and retries. Redact sensitive data and link traces to feedback and eval scores.
Why use OpenTelemetry for LLM observability?
Model answer
It's a vendor-neutral standard: instrument once and export via OTLP to any backend, combine LLM spans with existing service traces, metrics and logs, propagate trace context across services, and use auto-instrumentation libraries. GenAI semantic conventions give standard attribute names so dashboards and tools interpret model calls consistently.
How do traces help with evaluation?
Model answer
They supply the full context for error analysis, provide the data for agent tool-call and trajectory evals, let you sample production requests for online judging, attach user feedback to exact inputs and outputs, and turn real failures into golden dataset cases. They also provide latency and cost per step for performance evals.
Quiz
Pick an answer to see whether it’s right and why.
0 of 5 answered
Key terms
- Tracing
- Recording every step of a request (model calls, tool calls, retrieval) with timing, inputs and outputs, so you can debug and measure it.
- Span
- One timed step inside a trace (such as a retrieval or a model call), with a parent, a status and attributes.
- Trace
- The full record of one request or task, made of nested timed spans for every step it took.
- OpenTelemetry (OTel)
- An open, vendor-neutral standard and toolkit for producing and exporting traces, metrics and logs.
- GenAI semantic conventions
- OpenTelemetry's standard attribute names for model calls, such as gen_ai.request.model and gen_ai.usage.input_tokens.
Before and after
Progress is saved in this browser only.