Skip to content

Observability

You can't debug what you can't see. LLM systems have novel failure modes that require purpose-built tooling.

Rapid Recall

Every request becomes a trace; each step a span with input, output, tokens, cost, latency. Tools: Langfuse (OSS), LangSmith (LangChain-native, best for LangGraph), Phoenix (drift), Helicone (proxy), W&B Weave. Alert on: p95 latency, error rate, cost-per-query, CSAT drop, hallucination rate, retrieval recall. Feedback loop: user thumbs-down → surfaced to engineer → triage → fix → add to eval set → re-run → deploy. Shadow traffic to catch regressions before they ship. P50 / P95 / P99 matter all three; the user feels p99.

§3.1 Tracing

Every request becomes a trace. Each step in the pipeline is a span. Captures:

  • Input query + resolved prompt (post-retrieval, post-formatting)
  • All LLM calls with input/output tokens, model, temperature
  • All tool calls with args and responses
  • Retrieval: which chunks, which scores
  • Reranking: before/after ranks
  • Total latency + per-span latency
  • Total cost + per-span cost
  • User feedback (thumbs up/down) linked to the trace

What a trace looks like

For one user query, a trace captures:

[trace: "How do I get a refund?"]
├── retrieve_node (45ms)
│   ├── bm25_search (3ms)     → top-10 doc_ids
│   ├── dense_search (28ms)   → embedding call to OpenAI (2 tokens, $0.0001)
│   └── rrf_fuse (1ms)
├── rerank_node (110ms)
│   └── cross_encoder.predict (108ms, batch=10)
├── generate_node (1450ms)
│   └── chat_completion (1442ms, in=600 tokens, out=85 tokens, $0.003)
└── total: 1605ms, $0.0031

You want to filter on: "slowest 100 queries", "queries with cost > $0.01", "queries where faithfulness < 0.5", "queries with empty retrieval results."

Trace fan-out

flowchart TD
  REQ[User request] --> TR[Trace root span]
  TR --> S1[retrieve span<br/>45ms / $0.0001]
  TR --> S2[rerank span<br/>110ms / 0$]
  TR --> S3[generate span<br/>1450ms / $0.003]
  S1 --> SS1[bm25 child span]
  S1 --> SS2[dense embed span]
  S1 --> SS3[rrf_fuse child span]
  S2 --> SS4[cross-encoder predict]
  S3 --> SS5[chat_completion]

§3.2 Tooling (2026)

Tool Strengths
Langfuse Open source, self-hostable, strong tracing + eval
LangSmith LangChain-native, best for LangGraph agents
Arize Phoenix Open source, strong embeddings + drift analysis
Helicone Proxy-based, easy drop-in
Weights & Biases Weave Experiment tracking + traces

Pick one early. Cost of switching later is high.

The three contenders for RAG

LangSmith Langfuse Arize Phoenix
By LangChain team Independent OSS Arize
Hosting SaaS (free tier + paid) Self-host OR SaaS Self-host OR SaaS
LangChain integration Native Excellent Good
Eval integration Built-in Built-in (Ragas-native) Built-in
Best for LangChain-heavy teams OSS-first, self-host preference Drift detection, ML+LLM combined

§3.3 What to Alert On

Alert Threshold (example) Severity
p95 latency > 2s chat, > 800ms voice High
Error rate > 2% High
Cost per query > 1.5x baseline Medium
CSAT drop > 5% over 24h High
Hallucination rate > 1% High (domain-dependent)
Retrieval recall drop > 5% Medium
LLM provider errors any sustained 5xx High

Alert decision graph

flowchart TD
  M[Metric crosses threshold] --> S{Severity}
  S -- high --> P[Page on-call]
  S -- medium --> CH[Post to Slack ops channel]
  S -- low --> DG[Log to daily digest]
  P --> RB{Recent deploy?}
  RB -- yes --> RLB[Roll back deploy]
  RB -- no --> INV[Investigate: attribution + sample low-scoring traces]
  RLB --> CL[Close + postmortem]
  INV --> CL

§3.4 Feedback Loops

The tight loop that separates production-grade from hobby:

flowchart LR
  U[User] --> R[Response]
  R --> TD[Thumbs Down]
  TD --> D[Trace auto-surfaced to ops dashboard]
  D --> T[Triage: retrieval / prompt / model issue]
  T --> F[Fix - add chunk to KB,<br/>update prompt, adjust reranker]
  F --> GE[Add sample to golden eval set]
  GE --> RE[Re-run eval]
  RE --> DEP[Deploy fix]
  DEP --> U

Without this loop, bugs are reported, ignored, and repeat. With it, the system learns from every failure.

§3.5 Shadow Traffic

Run candidate system in parallel with production. Doesn't serve users, but processes every production query. Compare outputs offline. Catches regressions before they ship.

What to capture, what to skip

Always capture:

  • Query text, user ID, timestamp.
  • Retrieved doc IDs, scores, source metadata.
  • LLM prompt (template + filled values), response, token counts, cost.
  • Per-node latency.
  • Final answer, plus any eval metrics computed.

Don't capture (PII / regulatory issues):

  • Raw user input that may contain PII (mask emails, phone numbers, credit card numbers BEFORE logging).
  • Document contents from access-controlled sources unless the trace store is access-controlled.

Sampling: at high QPS, trace 100% of slow queries (>p95 latency) and 1-10% of normal ones. Full-trace sampling is expensive.

P50, P95, P99 latency — and why averages lie

Each percentile tells a different story:

  • P50 (median): typical experience. Half of requests are faster than this.
  • P95: 1 in 20 requests is slower. Where most user complaints originate.
  • P99: 1 in 100. The tail; worth tracking but optimize p95 first if budget is limited.

Why not the average? Take 100 requests: 99 take 50ms, one takes 5000ms. The average ≈ 99.5ms — hides the outlier and is dragged by it. P50 = 50ms (the typical truth). P99 = 5000ms (the ugly truth). Latency distributions are right-skewed with long tails, so the mean is a terrible summary.

Why the tail matters at scale:

  • Fan-out compounds. 100 internal calls each with a 1% chance of P99-slow → 1 − 0.99^100 ≈ 63% chance at least one is slow. Your user-facing P99 is dominated by internal P99 — the "tail at scale" effect (Dean & Barroso).
  • Users feel their request, not your median. 1% bad × 1M requests/day = 10,000 bad experiences daily.
  • SLAs are written on tail percentiles, never on averages.

What's sharp about LLM serving tails specifically:

  • Variable output length → naturally long tail (a 2000-token generation is slow).
  • TTFT (time to first token, prefill + queue) vs TPOT (time per output token, streaming speed) — tracked separately, each as percentiles.
  • Batching interference: one giant request slows others in the batch → throughput optimizations can worsen P99.
  • Cold starts: a request hitting a fresh replica eats the multi-GB model load → pure tail event.

The senior framing: the gap between P50 and P99 measures consistency. P50 100ms / P99 120ms = tight. P50 100ms / P99 3000ms = something nasty lurking in the tail. "How's the latency" is never one number.

Workload Driver What to track
Voice agents Sub-800ms p95 because conversation pauses past that feel awkward TTFT p95 + p99
Chat UIs Forgiving on p95 if streaming gets first token in ~200ms TTFT p50/p95, TPOT
Batch enrichment Percentiles barely matter Throughput (tok / sec / $)

Latency budget breakdown for RAG

Stage Typical Optimization
Embed query 5-50ms Cache, smaller model
Hybrid retrieval 20-100ms HNSW tuning, smaller efSearch
Cross-encoder rerank 50-200ms Smaller reranker, fewer candidates, ColBERT
LLM TTFT (time-to-first-token) 300-1500ms Streaming, smaller model, prompt caching
LLM completion 500-3000ms Smaller model, fewer output tokens
Total 1-5 seconds Stream! User sees output starting from TTFT

Streaming is the single biggest perceived-latency win. Users perceive latency as "how long until I see anything happen." If the first token arrives in 800ms and the full answer takes 3s, the experience feels like 800ms.

Interview Questions

Q5: Design observability for a production RAG system.

Every request becomes a trace. Spans for: input preprocessing, retrieval (with scores + chunks), reranking, LLM call (tokens + cost), tool calls, output filtering. Link user feedback (thumbs) to trace. Dashboard aggregates: p50/p95/p99 latency per span, cost per query, CSAT trend, retrieval recall, hallucination rate. Alerts on: latency regression, cost spikes, error rates, CSAT drops. Tool: Langfuse or LangSmith. Feedback loop: every thumbs-down surfaces trace to engineer → triage → fix → add sample to eval set.

Q12: Design a feedback loop that actually improves the system over time.

Step 1: capture thumbs up/down, link to trace. Step 2: auto-surface negative traces to engineer dashboard, cluster by failure type. Step 3: engineer triages, retrieval issue, prompt issue, model issue, data quality. Step 4: fix the specific issue (improve chunking, add doc, update prompt). Step 5: add the failing query to golden eval set. Step 6: re-run eval, confirm regression fixed, no new regressions. Step 7: deploy. Weekly review: which categories of failure recur? Systemic fixes.