Tracing & Observability

See inside your LLM app - traces, spans & observability

LLM chains and agents are non-deterministic black boxes: a single request fans out across retrieval, model calls, and tools. Tracing captures that execution as a tree of nested spans - with inputs, outputs, tokens, latency, and cost - so tools like LangSmith and Langfuse let you debug, measure, and trust what your app actually did.

Traces & spans Tokens, latency & cost OpenTelemetry Evals & alerts
01 - What

What is LLM tracing?

Tracing records the full execution of an LLM request as a trace - a tree of nested spans. Each span is one unit of work (a retrieval, an LLM call, a tool invocation, a chain step) with its own inputs, outputs, timing, and metadata. Observability is what you build on top: dashboards, evals, and alerts that turn those traces into insight about correctness, latency, and cost.

๐ŸŒณ Trace

The whole request, root to leaf - one user question producing one span tree. It carries a trace ID that ties every step together.

๐Ÿ“ Span

A single operation inside the trace - a retrieval, generation, or tool call - with start/end time, inputs, outputs, and a parent link.

๐Ÿท๏ธ Metadata

Model & version, prompt template, token counts, cost, user/session IDs, tags, and errors attached to spans for slicing later.

What a span should capture

FieldWhy it mattersExample
Inputs / outputsThe exact prompt sent and text returned - the core of debuggingRendered prompt, completion, retrieved chunks
TokensPrompt + completion counts drive cost and context-limit checksprompt: 1420, completion: 210
LatencyPer-span duration reveals which step is slowretrieval: 40ms, llm: 2.3s
CostDerived from tokens ร— model price for budgeting$0.0043 / request
Model & errorVersion pins regressions; errors show failed stepsclaude-sonnet-4-5, timeout, 429
Key mental model: a trace is a stack trace for probabilistic software. Logs tell you a request happened; a trace shows the exact tree of steps, prompts, and decisions that produced the answer - which is the only way to debug a non-deterministic chain.
02 - Why

Why observability is essential

The same input can produce different outputs, chains hide many steps behind one response, and agents decide their own path at runtime. Without traces you are guessing. Observability turns "it sometimes gives bad answers" into a specific span you can inspect and fix.

๐Ÿ”ฌ Debug non-deterministic chains

Reproduce a bad answer by replaying its exact trace - see the retrieved context, the rendered prompt, and where the model went wrong.

๐Ÿ•น๏ธ Understand agent behavior

Agents loop, branch, and call tools on their own. The span tree makes their reasoning path visible so you can see why they looped or picked a bad tool.

๐Ÿ’ธ Control tokens & cost

Every span records tokens and cost, so you can attribute spend per feature, user, or model and catch runaway prompts before the bill does.

โฑ๏ธ Find latency & regressions

Per-span timing exposes the slow step; pinning model & prompt versions lets you catch when a change made quality or latency worse.

In Plain Terms

LLM tracing explained with analogies

Same idea, four ways to picture it, so it clicks whoever you are.

๐ŸŽ“ For a student

A trace is the worked solution, not just the final answer. Instead of only seeing the grade, you see every step the model took, so you can spot exactly where the reasoning went wrong.

๐Ÿ‘ฉโ€๐Ÿ’ป For a developer

It is a stack trace for probabilistic code. Each span is a frame with its own inputs, outputs, and timing, nested under its parent, so you can drill into the call that actually broke.

๐Ÿข For a professional

Think of an audit trail for every request: who asked what, which sources were pulled, what each step cost, and how long it took, all reconstructable after the fact.

๐Ÿ›ซ Everyday version

A trace is a flight recorder, the black box on the plane. When something goes wrong, you replay the recording step by step instead of guessing what happened mid-flight.

03 - How

How tracing works under the hood

Your app is instrumented so each step opens a span, records its data, and closes it. Spans are batched and shipped asynchronously to a tracing backend (LangSmith, Langfuse, or any OpenTelemetry collector), which reassembles them into a trace tree and powers dashboards, evals, and alerts.

๐Ÿช Instrumentation

Wrap functions with a decorator or callback, or auto-instrument the SDK. Each wrapped call emits a span with a parent link, so nesting is captured automatically.

๐Ÿšš Export & ingest

Spans are buffered and flushed in the background to keep the hot path fast. The backend stores them, joins them by trace ID, and indexes by tags, cost, and latency.

The architecture at a glance

Architecture - instrumented app emits spans; backend reassembles & serves them
flowchart LR
    subgraph App["๐Ÿ–ฅ๏ธ Instrumented app"]
        Q["โ“ Request"] --> RT["๐ŸŒณ Root span"]
        RT --> RET["๐Ÿ”Ž Retrieval span"]
        RT --> GEN["๐Ÿง  LLM span"]
        RT --> TOOL["๐Ÿ› ๏ธ Tool span"]
    end
    subgraph Pipe["๐Ÿšš Telemetry pipeline"]
        EXP["๐Ÿ“ค Span exporter"] --> COL["๐Ÿงฐ OTel collector"]
    end
    subgraph Backend["๐Ÿ“ก Tracing backend"]
        ING["๐Ÿ“ฅ Ingest + join by trace ID"] --> STORE[("๐Ÿ—„๏ธ Trace store")]
        STORE --> DASH["๐Ÿ“Š Dashboards"]
        STORE --> EVAL["๐Ÿงช Evals + datasets"]
        STORE --> ALERT["๐Ÿšจ Alerts"]
    end
    RET --> EXP
    GEN --> EXP
    TOOL --> EXP
    COL --> ING
        

Instrumenting a chain in pseudocode

@trace(name="rag_request")          # opens the root span
def answer(question, user):
    with span("retrieval") as s:    # child span
        chunks = vector_db.search(question, filter=user.acl)
        s.log(inputs=question, outputs=chunks, latency_ms=s.dt)

    with span("llm") as s:          # sibling child span
        out = llm.generate(prompt(question, chunks))
        s.log(model="claude-sonnet-4-5",
              prompt_tokens=out.usage.input,
              completion_tokens=out.usage.output,
              cost=price(out.usage))
    return out.text
# โ†’ backend reconstructs: rag_request โ†’ [retrieval, llm]

Each with span(...) nests under the active parent, so the backend rebuilds the exact tree. In high-volume systems you sample - keep 100% of errors and a percentage of the rest - to bound storage while still catching problems.

04 - Sequence Diagrams

Detailed sequence diagrams

Three views: capturing a trace across a chain, nested spans for a multi-step agent, and traces flowing to dashboards with alerting.

Diagram 1 - Capture: spans emitted as a chain runs retrieval, LLM, and tool steps
sequenceDiagram
    autonumber
    participant App as ๐Ÿ–ฅ๏ธ App
    participant VDB as ๐Ÿ—„๏ธ Vector DB
    participant LLM as ๐Ÿง  LLM
    participant TB as ๐Ÿ“ก Tracing backend

    App->>TB: Start root span (trace ID)
    App->>VDB: retrieve(question)
    VDB-->>App: top-k chunks
    App->>TB: Log retrieval span (in, out, latency)
    App->>LLM: generate(prompt + chunks)
    LLM-->>App: answer + token usage
    App->>TB: Log LLM span (tokens, cost, model)
    App->>TB: End root span
    Note over App,TB: Spans batched then flushed async
        
Diagram 2 - Nested spans: a multi-step agent forms a span tree
sequenceDiagram
    autonumber
    participant Ag as ๐Ÿค– Agent
    participant Tool as ๐Ÿ› ๏ธ Tools
    participant LLM as ๐Ÿง  LLM
    participant TB as ๐Ÿ“ก Tracing backend

    Ag->>TB: Open agent root span
    loop reason-act steps
        Ag->>LLM: plan next action
        LLM-->>Ag: chosen tool + args
        Ag->>TB: Log LLM child span
        Ag->>Tool: call tool(args)
        Tool-->>Ag: observation
        Ag->>TB: Log tool child span (nested)
    end
    alt goal reached
        Ag->>TB: End root span (success)
    else max steps hit
        Ag->>TB: End root span (halted)
    end
        
Diagram 3 - Alerting: traces feed dashboards that trip on latency, cost, or errors
sequenceDiagram
    autonumber
    participant App as ๐Ÿ–ฅ๏ธ App fleet
    participant TB as ๐Ÿ“ก Tracing backend
    participant Dash as ๐Ÿ“Š Dashboards
    participant Ops as ๐Ÿง‘โ€๐Ÿ’ป On-call

    App->>TB: Stream spans continuously
    TB->>TB: Aggregate p95 latency, cost, error rate
    TB->>Dash: Update live metrics
    alt threshold breached
        TB->>Ops: Fire alert (slow / costly / failing)
        Ops->>TB: Open offending trace
        TB-->>Ops: Root-cause span
    else within budget
        TB->>Dash: Show healthy trend
    end
        
05 - Step by Step

The 0 โ†’ 100 flow

From an uninstrumented app to a fully observable one - traces feeding dashboards, evals, and alerts, in order.

00
Instrument

Wrap the entry point

Add a tracer to your app and decorate the top-level handler so every request opens a root span with a unique trace ID.

10
Nest

Span each step

Wrap retrieval, LLM calls, and tool calls so each becomes a child span linked to its parent, forming the trace tree.

20
Capture

Record inputs & outputs

Log the rendered prompt, retrieved context, and completion on each span - the raw material for every later diagnosis.

30
Measure

Attach tokens, latency & cost

Add token counts, per-span duration, model and version, and derived cost so spans are quantitative, not just descriptive.

40
Tag

Add metadata

Attach user, session, tenant, and feature tags plus environment, so traces can be sliced and filtered later.

50
Export

Ship spans asynchronously

Buffer and flush spans in the background to LangSmith, Langfuse, or an OpenTelemetry collector without slowing the request.

60
Sample

Bound volume at scale

Keep 100% of errors and a percentage of healthy traffic so storage and cost stay bounded while problems still surface.

70
Reassemble

Join into a trace tree

The backend stitches spans by trace ID into a navigable tree and indexes them by tag, cost, latency, and error.

80
Evaluate

Link traces to evals & datasets

Promote real traces into evaluation datasets and score outputs - connecting production behavior to offline quality checks.

90
Monitor

Dashboards & alerts

Chart p95 latency, cost, and error rate; set thresholds that page on-call when a metric breaches its budget.

100
Improve

Debug, fix, repeat

Open the offending trace, find the root-cause span, ship a fix, and watch the metric recover - a closed observability loop.

Common Pitfalls

Pitfalls & anti-patterns

Tracing is easy to bolt on badly. These are the mistakes that make traces leaky, expensive, or useless.

๐Ÿ”“ Logging PII into traces

Dumping raw prompts and outputs captures names, emails, and secrets in plaintext. Redact or hash sensitive fields at capture time and set retention rules before you ship.

๐Ÿ’ฅ No sampling at scale

Persisting 100% of traffic looks fine in dev and explodes storage and cost in production. Keep every error and a sampled fraction of healthy traffic to stay bounded.

๐Ÿ“ƒ Flat logs instead of spans

Emitting unstructured log lines throws away the parent-child tree. Without nested spans you cannot see which step called which, or where time was actually spent.

๐Ÿท๏ธ Missing token & cost metadata

Spans with no token counts, model, or cost turn observability into pretty timelines you cannot budget against. Attach usage to every model span.

๐Ÿšจ Tracing only errors

Capturing spans just when something throws hides the slow-but-successful and the confidently-wrong. Trace the full context, not only the exceptions.

๐Ÿ”— Traces not linked to evals

Traces that never feed datasets or scorers stay a debugging toy. Promote real traces into evals so production behavior drives offline quality checks.

How to Measure

What to capture and watch

Good observability is a handful of numbers you can alert on. Capture these per span and per trace.

MetricWhat it tells youGood sign
p50 / p95 latency per spanWhich step is slow, and how bad the tail isWithin SLA; small p95-to-p50 gap
Tokens & cost per traceSpend attributed to a request, feature, or userStable and within budget
Error / exception rateShare of traces that failed or threwLow and trending flat
Tool-call success rateHow often agent tool calls return usable resultsHigh: agents rarely retry or loop
Trace sampling coverageWhat fraction of traffic you actually captureEnough to catch issues without blowing cost
Time-to-debugHow fast you go from alert to root-cause spanMinutes, not days
Rule of thumb: if p95 latency spikes but the error rate is flat, hunt the slow span; if cost climbs without more traffic, find the chatty prompt. Alert on the aggregates, then open the offending trace to fix the specific span.
06 - Case Studies

Real-world case studies

Three representative patterns showing tracing and observability in production-style use.

๐Ÿค–

1 ยท Debugging a misbehaving agent

Pattern: root-cause from the span tree

An agent occasionally loops forever or answers from the wrong tool, and logs only show the final failure.

  • The trace tree exposes every reason-act step, the tool chosen, and the args passed at each turn.
  • Inspecting spans reveals the model picked a search tool when it should have called the database.
  • The fix - a sharper tool description and a step limit - is verified by replaying the trace.
โœ… Outcome: A vague "the agent is flaky" becomes a specific bad decision on a specific span, fixed and confirmed instead of guessed at.
๐Ÿ’ธ

2 ยท Cost & token monitoring across a product

Pattern: spend attribution from spans

An LLM feature's bill is climbing and no one knows which flow or user is driving it.

  • Every span records tokens and cost, tagged by feature, tenant, and model.
  • A dashboard groups spend so one chatty prompt template stands out as the top cost driver.
  • Trimming that prompt and switching a step to a cheaper model cuts cost with no quality loss.
โœ… Outcome: Cost is attributed to the exact feature and prompt, so optimization targets the real driver instead of blanket rate limits.
โฑ๏ธ

3 ยท Latency optimization by finding the slow span

Pattern: per-span timing breakdown

A RAG endpoint feels slow, but the team can't tell whether retrieval, reranking, or generation is to blame.

  • Per-span latency shows reranking, not the LLM, eats most of the p95 budget.
  • The reranker is made async and its candidate set trimmed, shrinking that span.
  • The dashboard confirms p95 drops and no error or quality regression follows.
โœ… Outcome: The slow step is measured, not assumed, so effort lands on the span that actually moves the latency number.
07 - Future

Where observability is heading

LLM observability is converging on open standards and tightening the loop between production traces and quality.

๐Ÿ“ OpenTelemetry standard

GenAI semantic conventions are standardizing span names and attributes, so traces move between vendors without lock-in.

๐Ÿงช Online evals on live traces

LLM-as-judge and heuristic scorers run continuously on sampled production traces, flagging quality drops in real time.

๐Ÿ” Trace-to-dataset loops

Interesting or failing traces flow straight into eval datasets, closing the gap between what shipped and what you test.

๐Ÿง  AI-assisted root cause

Models summarize a failing trace and suggest the offending span and fix, cutting time-to-diagnosis on complex agents.

๐Ÿ•น๏ธ Agent-native observability

Purpose-built views for loops, branches, and tool graphs make deep agent traces navigable instead of overwhelming.

๐Ÿ” Privacy-aware tracing

Automatic PII redaction and sampling let teams keep rich traces while respecting data-handling and retention rules.

Bottom line: tracing is how you make a probabilistic system operable - turning invisible chains and agents into a debuggable, measurable span tree. As standards mature and evals move online, observability becomes the backbone of every serious LLM product.