Atlas / RUN / LLMOps / Failure Analysis
DEEP-DIVE · LLMOPS

Debugging AI Failures: Incident Analysis for LLM Systems

An AI failure is not a classic bug. It is non-deterministic, hard to reproduce, and its cause is smeared across prompt, model, data, and retrieval. This is the discipline that makes it tractable.

TL;DR
  • An LLM failure is non-deterministic and multi-layered: the same input may fail one run in twenty, and the cause can sit in the prompt, the model, the data, or retrieval. Step-through debugging does not fit, so the discipline starts from the captured trace, not the stack.
  • The workflow is fixed: capture the full trace, reproduce with bounded non-determinism, localize the failing layer before touching anything, categorize the failure, then remediate at the correct layer and leave a regression test behind.
  • Treat every confirmed failure as a future eval. Incident response for AI adds severity tiers, a kill switch, and rollback, and the postmortem is not closed until the failure is a durable case in the eval set.

Why AI failures are not bugs

A classic bug is a broken promise about a deterministic machine. Given the same input, the code takes the same path and produces the same wrong answer every time, which is exactly what makes a debugger useful: you set a breakpoint, step through, and watch the state go wrong at a specific line. An LLM system breaks all three of those assumptions. The same input can produce a correct answer nineteen times and a fabricated one on the twentieth. There is no line where the fault lives. And the thing you would step through, a forward pass over billions of weights, is not legible in the way a call stack is.

The harder problem is that the root cause is smeared across four layers that a single output blends together. A confidently wrong answer might be a retrieval miss (the right passage never reached the context), a prompt defect (the instruction was ambiguous), a model limitation (the reasoning was simply beyond it), a tool error (a downstream call returned stale data), or bad source data (the retrieved document was wrong to begin with). The output looks identical in several of these cases. You cannot tell them apart by reading the answer, which is precisely why reading the answer is where most teams get stuck.

So the discipline has to change shape. You cannot reproduce on demand, so you capture everything the first time. You cannot step through, so you reason about layers instead of lines. You cannot trust a single run, so you measure over many. Debugging an AI system is closer to epidemiology than to setting breakpoints: you work from recorded evidence toward the most probable failing layer, then confirm by intervention.

The reframe: you are not finding the line that is wrong. You are localizing which of prompt, model, data, or retrieval most probably produced a non-deterministic bad output, from a trace you captured before you knew you would need it.

Capture, then reproduce

Because you usually cannot make a failure happen again on request, the trace you captured at the time of the failure is the entire case file. If it is thin, the investigation is over before it starts. A usable trace records the full causal chain, not just the final text: the raw user input, the fully rendered prompt including system instructions, the retrieved chunks with their scores and source IDs, every tool call with its arguments and returned payload, the model and its exact version, the sampling parameters, and the final output. Anything you did not log, you cannot examine, and you rarely get a second chance to observe it. This is why failure analysis is downstream of tracing and observability; the quality of your debugging is capped by the quality of your traces.

Reproduction is where non-determinism fights back. Replaying the same input can yield a different, passing output, which tempts teams to declare the problem gone. It is not gone; it is hiding behind sampling. The move is to bound the variability deliberately so that a replay is meaningful. Feed back the exact logged context rather than re-running retrieval, which may now return different chunks. Pin the model version, because a silent provider-side upgrade changes the machine under you. Set temperature to zero and fix the seed where the provider exposes one, to strip out sampling noise. What remains after you have held all of that constant is the real, reproducible defect.

Bounding the noise: replay with logged context (not fresh retrieval), a pinned model version, temperature at or near zero, and a fixed seed where available. If the failure survives all four constraints it is a genuine defect; if it vanishes, you were chasing sampling variance and should reason about it as a rate, not a single event.

Even with everything bounded, some failures are inherently probabilistic: they occur in a small fraction of runs no matter how you pin the inputs. Those are not reproduced, they are estimated. Run the frozen case many times, measure the failure rate, and treat that rate as the metric you are trying to move. A defect that appears three times in a hundred is real even though no single replay is guaranteed to show it.

Localize the failing layer

Before you change a single prompt word or swap a model, isolate the layer. Most wasted debugging effort comes from editing the prompt to fix what was actually a retrieval miss, then declaring victory because the sampling happened to land well on the next run. Localization is a decision procedure you run against the captured trace, and it is diagnostic only: you are reading evidence, not yet applying fixes.

Walk the trace in causal order and ask a falsifiable question at each layer. The order matters, because a failure upstream contaminates everything after it, and fixing a downstream symptom while an upstream cause remains guarantees the failure returns.

 bad output
     |
     v
[1] Were the right chunks retrieved?
     |-- no  --> RETRIEVAL layer (index, embeddings, chunking)
     |-- yes
     v
[2] Was the context correct but the answer ignored it?
     |-- yes --> PROMPT / MODEL layer
     |-- no
     v
[3] Did a tool return wrong or stale data?
     |-- yes --> TOOL / INTEGRATION layer
     |-- no
     v
[4] Was the source document itself wrong?
     |-- yes --> DATA layer (fix at source)
     |-- no  --> MODEL limitation (reasoning ceiling)
            
A fault tree read top-down against one trace. Resolve the highest failing node first; an upstream cause makes every downstream signal untrustworthy.

Each node maps to a concrete check you can run without guessing. For node one, inspect the retrieved chunks: if the passage that answers the question is absent, the fault is retrieval, and you head toward the index, the embedding model, or the chunking strategy rather than the prompt. Poor recall on exact-match or rare terms often points at a dense-only setup that needs hybrid search. For node two, the tell is that the correct fact was sitting in the context and the model still contradicted it, which is a prompt or model problem, not a retrieval one. For node three, replay the tool call in isolation and compare its payload to ground truth. Only when every upstream layer is clean should you attribute the failure to a model reasoning ceiling, because that is the most expensive conclusion to act on and the easiest to reach for prematurely.

A taxonomy of failures

Layer tells you where; category tells you how to triage. Two failures in the same layer can demand opposite responses, so once you have localized, name the failure type, because the first diagnostic and the likely cause differ sharply by type. The categories below are the ones worth wiring into your incident tooling as distinct labels rather than a single generic bad output bucket.

Failure typeWhat the user seesFirst diagnosticTypical cause
HallucinationA confident, fabricated fact or citationCheck whether the claim was supported by the retrieved contextRetrieval miss, or model ignoring context
Wrongful refusalThe system declines a legitimate requestInspect system prompt and guardrail triggers against the inputOver-broad safety instruction or filter
Format breakMalformed JSON, missing fields, prose where structure was requiredValidate output against the schema; check the format instructionWeak or contradicted output contract
Tool errorWrong action, stale data, or a silent no-opReplay the tool call in isolation; compare payload to truthBad arguments, integration fault, stale source
LatencySlow or timed-out responseBreak the trace into per-step timingsExcess tool hops, oversized context, retries
InjectionThe system follows attacker text, leaks, or acts out of scopeTrace which input segment carried the instructionUntrusted content treated as instruction

Two categories deserve a caution. Injection is a security incident, not a quality bug: an output that obeys instructions hidden in a retrieved document or a tool result is an attack surface, and its remedy lives with prompt-injection defense and red-teaming, not with prompt tuning. Latency is the one category where the diagnostic is timing rather than content; it hides real cost and reliability bugs, such as an agent that reaches the right answer in forty tool calls instead of six, that a pass/fail on the final output would never reveal.

Fix at the right layer

The remedy is dictated by the layer you localized, and applying the right fix at the wrong layer is how failures become permanent. A prompt patch cannot repair a retrieval gap; a model swap will not fix a document that is wrong at the source. Match the intervention to the diagnosis.

The remediation is not complete when the failing case passes. It is complete when the failure cannot silently return. Every confirmed defect becomes a permanent case in the eval set, the direct analog of writing a regression test from a bug report. This is the mechanism that makes an AI system get more reliable over time instead of drifting: without it, the next prompt edit or model bump can quietly reintroduce the exact fault you just fixed, and you will rediscover it through a customer months later.

The rule that compounds is simple: no AI fix ships without leaving an eval case behind. The regression case is the durable asset, not the patch; models and prompts churn underneath you, but the case keeps every future change honest. This is the bridge from debugging into evals and AI CI/CD, where the case runs on every behavior-changing merge.

Incident response for AI

Most bad outputs are quality defects that flow through the pipeline above. Some cross a line and become incidents: a data leak, a harmful or policy-violating response at scale, a confidently wrong answer in a regulated workflow, or an injection that turns the system against its user. The distinguishing test is blast radius and reversibility, not how embarrassing the single output looks. An incident needs a posture borrowed from site reliability and adapted to non-determinism.

  1. Severity tiers. Define them before you need them. A cosmetic format break is low; a systematic hallucination in a financial answer or a confirmed exfiltration is the top tier, and the tier sets who gets paged and how fast.
  2. Kill switch. A single control that disables the affected capability or routes to a safe fallback, deployable in seconds without a redeploy. For AI features this is non-negotiable, because you cannot always predict the next bad output.
  3. Rollback. Revert to the last known-good version of the whole behavioral surface: prompt, model version, retrieval index, and tool schemas together, since any one of them can be the regression.
  4. Blameless postmortem. Reconstruct the trace, name the failing layer, and record the timeline. The output is not a person to blame; it is a durable fix plus new coverage.

The step that separates AI incident response from generic firefighting is the last one. The postmortem is not closed when service is restored. It is closed when the incident has been distilled into eval cases that would have caught it, added to the suite, and confirmed to fail against the old version and pass against the fix. An incident you merely resolved will happen again in a different shape; an incident you converted into coverage cannot silently recur through the same layer. Over time this turns your worst days into your most valuable regression cases, encoding exactly the failure modes your real users and adversaries produced, which is coverage no synthetic test set would have anticipated.

The architect view

Everything above only works if the platform was built to allow it, and that is an architecture decision made long before the first incident. A bad output you cannot trace is unsolvable, a failure you cannot reproduce is unfixable, and a regression you cannot roll back is a crisis instead of a click. Debuggability is not a property you add after a system misbehaves; it is a property you design in, or spend the outage wishing you had.

Three commitments make an LLM platform diagnosable. First, capture complete traces by default, because you are always debugging yesterday's failure with yesterday's logs, and the causal chain you skipped recording is the one you will need. Second, make rollback and a kill switch first-class controls over the entire behavioral surface, so any failure is reversible in seconds rather than a redeploy away. Third, close every loop into the eval set, so that debugging is not cleanup but accumulation: each failure permanently raises the floor.

Skeptical CTOs are right to ask what happens on the day the model does something none of us predicted, because with a non-deterministic system that day is a certainty, not a risk. The honest answer is not that it will never happen. It is that when it does, you will see it in a trace, reproduce it under bounded conditions, localize it to a layer, fix it there, and walk away with a regression case that makes the same failure impossible to ship twice. That capability, not any single model choice, is what makes an AI system safe to run in production.

Make debuggability a platform property. Treat observability, reversible rollback, and the failure-to-eval loop as core infrastructure, not per-team afterthoughts. The organizations that operate AI at scale are not the ones whose models never fail; they are the ones whose failures are cheap to diagnose and impossible to repeat. A working reference lives in the Self-Correcting Agent in the Lab.
← Versioning and Regression Testing for AI Systems ALL OF LLMOPS CI/CD for LLM Applications: Shipping Prompts and Models Safely →