
Observability for AI Systems: Traces, Prompts, Tokens, and the SLOs That Actually Matter
A 30-minute production guide to instrumenting LLM applications — what to log (and what never to), how to trace agent calls end-to-end, the four golden signals for AI, drift detection on outputs, and the dashboards you actually check at 2 a.m.
Traditional APM tells you the request took 3 seconds — it doesn't tell you the LLM hallucinated, the retrieval missed, or the agent burned $4 running the wrong tool six times. AI observability adds four layers on top of tracing: prompt/response capture (with PII scrubbing), token + cost attribution, quality signals (evals, groundedness, user feedback), and drift detection. Ship all four from day one; retrofit is 3× the work.
- Trace every LLM call, every tool call, and every retrieval as spans on the same request trace — no exceptions.
- Log prompts and responses (scrubbed) with a request id, or you cannot debug a single bad answer ever again.
- The four AI golden signals: latency (TTFB + total), cost (tokens × price), quality (eval score, groundedness), and safety (refusals, blocked).
- Attribute cost to user, feature, and route — 'total LLM spend' is a useless number.
- Alert on p95 latency and error rate; alert on cost only for sudden step-changes, not absolute levels.
- Drift detection on outputs (embedding distance from a reference set) catches silent regressions no eval script would.
Why AI systems need their own observability layer
Classic APM (Datadog APM, New Relic, Sentry) tells you a request took 3 seconds and returned 200. That's completely useless for an LLM app: the request may have taken 3 seconds AND cost $0.40 AND cited three chunks that don't exist AND been rated 1/5 by the user. None of that is visible without an AI-specific observability layer on top.
This article is the concrete set of signals, spans, and dashboards I put on every serious AI system in 2026. It pairs with evaluating LLMs and classical ML (the offline side), RAG that actually works (what to trace in retrieval), and MLOps on a shoestring (the deploy/rollback plumbing).
The trace: one span per LLM call, tool call, and retrieval
Distributed tracing (OpenTelemetry) is the backbone. Every user request creates a root span. Each downstream unit of work — LLM call, vector search, tool invocation, DB query — is a child span with typed attributes:
from opentelemetry import trace
tracer = trace.get_tracer("ai-app")
async def answer(query: str, user_id: str):
with tracer.start_as_current_span("answer") as root:
root.set_attribute("user.id", user_id)
root.set_attribute("query.length", len(query))
with tracer.start_as_current_span("retrieve") as s:
hits = await hybrid_search(query, k=20)
s.set_attribute("hits.count", len(hits))
s.set_attribute("hits.top_score", hits[0].score if hits else 0.0)
with tracer.start_as_current_span("rerank") as s:
top = await rerank(query, hits, k=5)
s.set_attribute("reranker.model", "cohere-rerank-3")
with tracer.start_as_current_span("llm.chat") as s:
resp = await llm.chat(messages=build_prompt(query, top))
s.set_attribute("llm.model", resp.model)
s.set_attribute("llm.tokens.prompt", resp.usage.prompt_tokens)
s.set_attribute("llm.tokens.completion", resp.usage.completion_tokens)
s.set_attribute("llm.cost.usd", cost_of(resp))
s.set_attribute("llm.finish_reason", resp.choices[0].finish_reason)
return respAttribute names matter. Use the OTel gen_ai semantic conventions where they exist (gen_ai.request.model, gen_ai.usage.input_tokens) — dashboards and alerting rules become portable across tools.
Prompt and response capture: log it, but scrub PII first
Without the exact prompt and response bytes, debugging a single bad answer is impossible. But raw prompt logs are also the fastest way to leak PII into every downstream log store. The rule: capture, but scrub, and gate by classification.
- Always log: request id, user id, model, temperature, seed, prompt template id + variables (not the rendered prompt), token counts, cost, finish_reason, latency.
- Log scrubbed: rendered prompt and full response, with email/phone/credit-card regex-scrubbed and named-entity redaction for names and addresses.
- Never log: raw payload from a user marked 'sensitive' (health, financial, legal) — hash and reference.
- Retain narrowly: 30 days for full traces, 1 year for aggregates. Longer retention = larger blast radius on a breach.
For sensitive systems, consider a replay id pattern: store the full prompt in an encrypted bucket keyed by request id, and log only the id in traces. Debuggers with the right role can decrypt on demand.
The four golden signals for AI
Classic SRE golden signals are latency, traffic, errors, saturation. For AI systems the useful reframing is:
| Signal | What it measures | SLO shape |
|---|---|---|
| Latency | TTFB (first token) and total completion time, per model and route | p95 TTFB < 800ms for chat UIs |
| Cost | USD per request, per user, per feature — attributed via trace tags | Alert on 30% week-over-week change, not absolute |
| Quality | Eval score on golden set, groundedness, user thumbs, refusal rate | Golden-set score never drops > 5% between deploys |
| Safety | Blocked outputs, jailbreak attempts, policy violations, refusals | Blocked rate stable ± 20% week-over-week |
'Traffic' and 'saturation' still matter — they're just the same as any other service. The four above are the ones you don't get for free.
Cost attribution: 'total LLM spend' is a useless number
Every LLM call span carries cost.usd, user.id, feature, and route. That's enough to slice cost by any dimension in your warehouse. The dashboards I ship on every project:
- Cost per active user per day — the number that decides pricing.
- Cost per feature per day — which feature is running away? Usually one specific agent.
- Cost per model — surfaces the 'we forgot to switch off Claude 4 Opus after the experiment' bug.
- Top 100 most expensive requests today — one-off runaway generations show up here first.
Move the raw trace data into your warehouse nightly and query with SQL. See modern data stack 2026 for the pipes, and BigQuery cost control that scales for the FinOps discipline.
Quality signals: online evals, groundedness, and thumbs
Three quality signals in production, in order of leading vs lagging:
- Groundedness (leading) — for RAG, does the answer's claims appear in the cited chunks? Compute with a small model on a sample of requests; alert when it drops.
- Golden-set score (leading) — a 30–200 example golden set replayed on every deploy. Non-negotiable. Full pattern in evaluating LLMs and classical ML.
- User feedback (lagging) — thumbs-up/down, star ratings, task completion. Trailing indicator but the only true one.
Route online-eval scores back into the trace as attributes (quality.groundedness=0.92) so a single query can slice by feature × time × score.
Drift detection: the silent regression catcher
The nastiest AI regressions don't crash — they degrade. A model update, a prompt tweak, a data change, and suddenly answers are 5% worse in a way no unit test catches. Drift detection catches this by comparing the distribution of production outputs to a reference distribution:
- Embedding drift: embed responses, compute cosine distance from a reference set weekly; alert on 2σ moves.
- Length drift: response length p50/p95 per route; a 30% jump usually means the model started rambling.
- Refusal drift: sudden increase in 'I cannot help with that' means either a jailbreak wave OR an over-cautious model swap.
- Topic drift: cluster embeddings weekly; new clusters = new user behaviors (opportunity) or hallucinated topics (bug).
None of these tell you what broke. They tell you something broke and where to look, days before user feedback would.
Agent-specific tracing: tool calls, loops, and cost per turn
Agents make the trace tree explode: one user turn can become 8 LLM calls + 12 tool calls + 3 retrievals. Instrument every tool call as a child span with input, output, latency, and error. Track two extra metrics per agent turn:
- Steps per turn — p50 and p95. A p95 of 15 means your agent is looping.
- Cost per turn — the number that decides whether the feature is viable at scale.
- Tool error rate — per tool. A single flaky tool poisons the whole loop.
Cap step count in code (see building production AI agents with n8n). Alert when steps_per_turn.p95 exceeds the cap × 0.8 — that's the leading indicator of runaway loops.
Sampling and cardinality: don't trace yourself into bankruptcy
Tracing every request in a chat app at 100 QPS produces terabytes of span data per month. Two tactics keep the bill sane:
- Tail-based sampling: keep 100% of errored + slow + expensive traces, sample 5–10% of the rest.
- Attribute hygiene: never put unbounded cardinality (user ids, full URLs, prompt text) as metric labels — only as span attributes. A metric with a million label values will bankrupt any TSDB.
Prompt text belongs on spans (queryable, sampled), not on Prometheus metrics (aggregated, cardinality-bounded). Learn the difference or your first monthly Datadog bill will.
The tools I actually use in 2026
- Traces: OpenTelemetry SDK → any backend (Tempo, Datadog, Honeycomb). Vendor-neutral is worth the small setup cost.
- LLM-specific: Langfuse (open source, hostable), Helicone, Arize Phoenix. All speak OTel now.
- Metrics: Prometheus + Grafana for the boring golden signals.
- Logs: whatever your platform gives you; structured JSON with a request id is the whole game.
- Evals: Promptfoo or a small in-house harness — see the evals article.
The one non-negotiable is that all four layers (traces, metrics, logs, evals) share the same request_id. When you can pivot from a Slack alert → trace → prompt → eval score in three clicks, you've won.
SLOs and error budgets for LLM apps
Realistic SLOs I ship on production AI systems:
| SLO | Target | Error budget |
|---|---|---|
| Availability (HTTP 2xx or 4xx, not 5xx) | 99.5% | 3.6h/month |
| p95 TTFB (chat UIs) | < 1200ms | 5% of window |
| Groundedness on sampled RAG requests | > 0.85 | monthly rolling avg |
| Golden-set eval score | no regression > 5% | per deploy |
| Cost per active user per day | within 20% of budget | weekly |
99.9% is usually not achievable when your critical dependency is a third-party LLM provider with its own uptime story. Set SLOs you can actually hit; burning error budget every month means the SLO is wrong.
The 2 a.m. runbook
When the pager fires:
- Open the trace for the alerting request. Which span is red? Which is slow?
- Check the provider's status page. 40% of AI incidents are upstream.
- Diff the golden-set eval from this deploy vs last. Regression there = code/prompt problem.
- Check cost dashboard for a step-change. A cost spike with normal latency = usually a prompt-injection or a loop.
- Check drift dashboards. Slow degradation vs sudden = different remediations.
- If unclear, roll back the last deploy. You'll debug it in the morning; users won't wait.
Anti-patterns I've stopped shipping
- Only logging errors — you need the successful traces too, or you can't compare good vs bad.
- No cost attribution — 'we spent $80k on OpenAI last month' with no breakdown = paralyzed team.
- Per-request eval on 100% of traffic — costs more than the app itself; sample.
- Prompt text in metric labels — kills the metrics store.
- Ignoring drift because 'evals pass' — evals are a fixed set; drift catches the unknown-unknowns.
- Alerting on absolute cost — noisy; alert on rate of change instead.
- One dashboard for everything — separate ops, cost, and quality dashboards, each with one owner.
Frequently asked
Do I need a dedicated LLM observability tool (Langfuse, Helicone), or is Datadog enough?
Datadog + OTel gets you 70% of the way. A dedicated tool adds LLM-native views (prompt diffs, session replays, per-model cost) that pay off once you're past a few features. Start with what you have; add a specialist when the pain is clear.
How much should I sample?
Keep 100% of errors, slow requests (p95+), and high-cost requests. Sample the rest at 5–20% depending on volume. Always keep 100% for the first 90 days after a new feature launches.
Where does user feedback fit?
Thumbs and comments join the trace by request id. Aggregated feedback drives eval-set expansion — bad-thumbs requests are your best source of new golden-set examples.
Should I trace vector-database calls?
Yes, with attributes for index, k, top-1 score, and latency. Retrieval is where most RAG bugs actually live.
How do I detect prompt injection?
It rarely shows up as an error. Watch for sudden cost spikes on a single user, refusal-rate spikes, and off-topic drift. All three are drift-detection signals, not exception signals.
Building something similar?
I help teams ship production-grade AI agents, n8n workflows, and data platforms. Let's talk about what you're building.
Work with me
Islam Gamal
AI, Data & Automation Engineer. I design and ship production AI agents, n8n workflows, and cloud data platforms — with a focus on reliability, cost, and measurable business impact. Founder of Tashghil and Tek bil Arabi.
More from Islam Gamal
Evaluating LLMs and Classical ML: A Practical Metrics Guide
The metric you pick decides the model you ship. A deep field guide to choosing, computing, and defending metrics for classification, ranking, generation, RAG, and agents — with the traps that catch senior teams.
RAG That Actually Works: Chunking, Hybrid Search, Reranking, and Evals
The full production playbook: why naïve RAG demos die on the second question, how to chunk, index, retrieve, rerank, cite, evaluate, and cost-engineer a retrieval-augmented system that survives real users.
Prompt Engineering for Agents: A Structured, Testable, Version-Controlled Approach
Prompt engineering isn't vibes. Treat prompts like code — with schemas, versions, evals, diffs, and rollback. The full production workflow that scales past a single developer, with copy-paste patterns for structured output, few-shot design, LLM-as-judge, and prompt registries.
Browse every article by Islam Gamal on the author page.