Engineering Atlas › Problem Solving & Incidents (AI era)

Debugging LLM-Powered Systems

12 min read · last reviewed 2026-10-17

Open in the interactive app: practice, quiz, mock interview →

In one minute

How it works

What is different

Classic bugs are deterministic: the same input gives the same wrong output, so a failing test reproduces it. LLM systems add three complications. Nondeterminism: sampling, and even batching on the provider's side, can change outputs between runs. Silent failure: a wrong answer looks exactly like a right one, so there is no exception or 500 to alert on. Global coupling: one prompt or model change affects every request, so a fix for one case can quietly break twenty others.

Trace everything

You cannot debug what you cannot see. For each request, record the model and version, sampling parameters, the full assembled prompt (system prompt, instructions, retrieved documents, conversation), every tool call with arguments and results, the raw output, the post-processed output, token counts and latency. Store traces with a request ID, so a user complaint ("it told me the wrong refund policy") becomes a trace you can open and read. Redact personal data before storage, as you would for any log.

Find the failing stage

A typical retrieval-augmented pipeline has stages: query rewriting, retrieval, prompt assembly, generation, tool use and post-processing. Localise the failure like any other bug, stage by stage:

  1. Retrieval: were the right documents in the context? If the correct refund policy was never retrieved, no prompt change will fix it. Check the query, the top results and their scores.
  2. Assembly: was the context actually included, in the right order, without truncation? Token limits silently cut the end of long prompts.
  3. Generation: given good context, did the model answer wrongly? Try the same prompt several times to see if it is consistent or flaky.
  4. Tools: did a tool return an error the model ignored, or did the model call it with wrong arguments?
  5. Post-processing: did a parser drop fields, or did a guardrail filter strip the answer?

This is bisection applied to a pipeline: test the midpoint, keep the half that is broken.

Evals as regression tests

An eval set is a list of real inputs, each with a check: an exact expected value, a rule ("must cite a policy document", "must not mention competitor names"), or a grader (a human or a model judging against a rubric). Build it from production failures and from the cases that matter most. Run it on every change and compare case by case with the last version, not just the average: a pass rate that goes from 91 to 92 percent can hide five critical cases that started failing. Because outputs vary, run important cases several times and track their pass rates.

Two habits make evals useful. Start from real failures and keep looking at traces: Hamel Husain describes cheap unit-test assertions first, then human review of logged traces and a model judge calibrated against human labels (Husain). For agents, run several trials per task. Anthropic's example: a 75% per-trial success rate gives only about 42% odds of passing three trials in a row, so measure consistency, not just best-of-k, when users need reliability, and read the transcripts to check that graders are not rejecting valid answers (Anthropic).

Change control

Version prompts in the repository. Pin model versions rather than floating aliases, so a provider update does not change behaviour under you unexpectedly. Roll changes out behind flags to a small share of traffic, compare production metrics (thumbs-down rate, escalations to humans, task completion), and keep the old version ready to restore. Pinning is not enough on its own: in 2025 Anthropic had three infrastructure bugs (misrouted requests, corrupted token output, a compiler bug in approximate top-k) that degraded answers for weeks while its standard evals stayed quiet, partly because the model often recovers from an isolated mistake. Its fixes were more sensitive evals and continuous quality evals on production traffic (Anthropic postmortem). Watch the provider's behaviour too, not just your own diffs.

Down the stack

Layer 1The contract

An LLM feature's contract is statistical: for its eval set, at least X percent of cases pass, critical cases always pass, latency and cost stay within budgets, and production quality metrics stay above thresholds. A change ships only if it keeps that contract, judged by evals run before rollout and metrics watched during it.

Layer 2The mechanism

Tracing uses the same machinery as distributed tracing: each request gets a trace ID, and each stage (retrieval, model call, tool call) is a span with attributes such as model name, input and output token counts and finish reason. OpenTelemetry defines semantic conventions for generative AI spans, now maintained in their own repository (OpenTelemetry GenAI conventions), so traces can flow into standard backends; check which version of the conventions your instrumentation library emits. Evals are a harness that loads cases, runs the pipeline at a fixed configuration, applies checks, and writes results keyed by case ID, so two runs can be diffed. A model's finish reason is a useful field: max_tokens (or length) means the output was cut off, which explains many "the JSON is invalid" bugs.

Layer 3The machine

Nondeterminism has concrete causes. With temperature above zero, the model samples the next token from a probability distribution, so different runs pick different tokens and diverge. Even at temperature zero, outputs can differ, because floating-point addition is not associative and inference servers batch requests together: different batch sizes change the order of operations in matrix multiplications on the GPU, which changes the last bits of the logits, which occasionally flips which token is most likely. That is why "it worked when I tried it" is weak evidence, and why evals run each important case several times. The batching part is the real driver: Thinking Machines ran one prompt 1,000 times at temperature 0 on a 235B model and got 80 distinct outputs, first diverging at token 103. They traced it to kernels that are not batch-invariant, so an answer depends on how busy the server was; with batch-invariant kernels all 1,000 matched, at a speed cost (Thinking Machines, 2025). The popular "concurrent threads plus floating point" story is incomplete, since the forward pass is deterministic for a fixed batch.

Why it's built this way

  1. Outputs are sampled and can vary, so single runs are anecdotes, not evidence.
  2. Failures are silent, so you need explicit checks rather than error rates.
  3. Pipelines have many stages, so traces of each stage are needed to localise faults.
  4. Changes are global, so per-case comparison against the previous version catches hidden regressions.
  5. Therefore: trace, evaluate case by case, version and roll out gradually.

Diagram

flowchart LR
  Q["User question"] --> RW["Query rewrite"]
  RW --> RT["Retrieve docs"]
  RT --> AS["Assemble prompt"]
  AS --> G["Model generates"]
  G --> TL["Tool calls"]
  TL --> G
  G --> PP["Post-process"]
  PP --> A["Answer"]
  RT -.->|"right docs?"| CHK["Check each stage in the trace"]
  AS -.->|"truncated?"| CHK
  G -.->|"consistent?"| CHK

Worked example

A support assistant answers 40,000 questions a day. After a release, thumbs-down feedback rises from 6 to 9 percent, mostly on refund questions. The release changed two things: a new prompt section about tone, and a switch to a cheaper embedding model for retrieval.

The engineer opens 30 thumbs-down traces. In 22 of them, the retrieved documents are about shipping, not refunds: the refund policy document is ranked 7th, and only the top 5 go into the prompt. In the other 8, the right document was retrieved but the answer was still wrong; rerunning those prompts 10 times gives the right answer 9 times out of 10, so those are mostly ordinary model variance that existed before the release.

Hypothesis: the new embedding model ranks the refund policy lower. Test: run the 400-case eval set with the old and new embedding models while keeping the new prompt. Pass rate on refund cases: 94 percent with the old embeddings, 71 percent with the new ones; other categories barely change. The team rolls back the embedding change, adds 50 refund questions from production to the eval set, and later reintroduces the cheaper model with a reranker, which recovers 95 percent on the refund cases. The tone change was innocent; without traces and per-category evals, it would likely have been blamed first, because it was the change people remembered.

In the wild

Trade-offs

TechniqueStrengthsWeaknessesChoose when
Full prompt and output tracingDebuggable, auditableStorage cost, privacy handlingAlways, with redaction and retention limits
Exact-match evalsCheap, deterministicOnly fits structured outputsClassification, extraction, JSON
Rule-based checksClear, fastMiss subtle quality issuesMust-include and must-not-include rules
Model-graded evalsScales to open-ended answersGrader can be wrong or biasedLarge sets, calibrated against humans
Human reviewHighest judgmentSlow, expensiveCritical cases, calibrating graders

Pitfalls in production

Leadership lens

Check yourself

  1. Why might a prompt change not fix a wrong answer about refund policy?

    Show answer

    If retrieval never put the policy document into the context, the model cannot use it; the failure is in retrieval, not generation.

  2. Why can outputs differ between runs even at temperature zero?

    Show answer

    Batching and floating-point non-associativity on the inference servers change computations slightly, which can flip the most likely token.

  3. Your eval pass rate rose from 91 to 92 percent after a change. Why is that not enough to ship?

    Show answer

    Averages hide per-case regressions; some important cases may have started failing while others improved, so compare case by case.

  4. What does a finish reason of max_tokens tell you?

    Show answer

    The output was cut off at the token limit, which explains truncated answers and invalid structured output.

Sources and further study

Primary

Systems docs

Books

Free resources

Open in the interactive app: practice, quiz, mock interview →