What Is Error Analysis?
Error analysis is the discipline of reading your failures. Instead of treating a 78% pass rate as one number, you inspect every failing case, group them into recurring failure modes, and trace each mode to its root cause.
It converts evaluation output into a prioritized backlog: "40% of our failures are the agent hallucinating order numbers" is something you can fix. "Our score is 78%" is not.
This is the highest-leverage habit in applied AI. Most quality gains come not from a bigger model, but from noticing that a single failure pattern accounts for a huge share of your errors.
"Read every failure, group the patterns, and fix the cause behind the biggest cluster first."
- A doctor diagnosing, not just measuring fever
- Root-cause analysis in an outage
- A Pareto chart of defects
Why Error Analysis Is High-Leverage
Fix Causes, Not Symptoms
One prompt fix can resolve an entire cluster of failures at once.
Pareto Prioritization
Usually a handful of failure modes cause most errors. Rank by frequency ร impact.
Builds Intuition
Reading real failures teaches you how the agent thinks โ and where it reliably breaks.
Feeds Other Layers
New failure modes become golden examples, coverage cells, and rubric dimensions.
Communicable
"Hallucinated IDs: 40%" is a story stakeholders act on; a raw score is not.
Measurable Progress
Track the size of each failure mode over time to prove a fix actually worked.
A Failure Taxonomy
Every failure gets annotated with a structured record so patterns become countable. A shared vocabulary of failure modes is the core artifact of this layer.
{
"example_id": "refund-shipped-014",
"failure_mode": "hallucinated_fact",
"severity": "high",
"stage": "generation",
"observed": "Claimed a 60-day window; policy is 30.",
"hypothesis": "Prompt omits the policy window value.",
"fixable_by": "prompt"
}
FAILURE_MODES = [
"hallucinated_fact", # invented information
"wrong_tool", # called the wrong tool / args
"missing_step", # skipped a required action
"format_violation", # broke the required output schema
"refusal_error", # over- or under-refused
"incomplete_answer", # partially correct
"tone_mismatch", # correct but off-brand
]
Failure Mode
The named category from a controlled vocabulary โ the unit you count and track.
Stage
Where it happened โ retrieval, planning, tool call, or generation โ to localize the fix.
Severity
Impact weighting so a rare catastrophic failure outranks a common cosmetic one.
Fixable By
The likely lever โ prompt, tool, retrieval, or model โ turning analysis into a task.
How Error Analysis Works
Two modes account for 65% of failures. Fixing hallucinated facts first yields the biggest gain.
Step-by-Step Implementation
Capture failures with context
A failure record keeps everything needed to diagnose it later without re-running the agent.
from __future__ import annotations
from dataclasses import dataclass
@dataclass(frozen=True)
class Failure:
"""A single failed evaluation captured for analysis.
Args:
example_id: The golden example that failed.
input: The input handed to the agent.
expected: The expected outcome.
actual: What the agent actually produced.
score: The numeric score assigned by the evaluator.
"""
example_id: str
input: str
expected: str
actual: str
score: float
def collect_failures(results, examples, threshold: float = 1.0) -> list[Failure]:
"""Extract failing items from an evaluation run.
Args:
results: Per-item results keyed by example id.
examples: The golden examples that were evaluated.
threshold: Scores below this are treated as failures.
Returns:
A list of :class:`Failure` records ready for annotation.
"""
by_id = {ex.id: ex for ex in examples}
return [
Failure(r.id, by_id[r.id].input, str(by_id[r.id].expected), r.actual, r.score)
for r in results if r.score < threshold
]
Auto-cluster with an LLM judge
An LLM can propose a failure-mode label for each case; a human then confirms or corrects it. This scales the first pass without sacrificing trust.
from collections import Counter
from .models import Failure
CLASSIFY_PROMPT = """You are triaging an AI agent failure.
Choose exactly one failure_mode from: {modes}.
Input: {input}
Expected: {expected}
Actual: {actual}
Respond with only the failure_mode."""
def classify_failure(failure: Failure, modes: list[str], judge) -> str:
"""Assign a failure mode to a single failure using an LLM judge.
Args:
failure: The failure to classify.
modes: The allowed controlled vocabulary of failure modes.
judge: Callable that maps a prompt to a model response.
Returns:
A failure-mode label; falls back to ``"unclassified"`` if invalid.
"""
label = judge(CLASSIFY_PROMPT.format(
modes=", ".join(modes),
input=failure.input, expected=failure.expected, actual=failure.actual,
)).strip()
return label if label in modes else "unclassified"
def summarize(failures: list[Failure], modes: list[str], judge) -> Counter:
"""Return a frequency count of failure modes across all failures."""
return Counter(classify_failure(f, modes, judge) for f in failures)
Produce a prioritized report
Weight each mode by severity so the top of the list is the highest-value fix.
SEVERITY_WEIGHT = {"low": 1, "medium": 3, "high": 9}
def prioritize(counts: dict[str, int], severity: dict[str, str]) -> list[tuple]:
"""Rank failure modes by ``frequency ร severity weight``.
Args:
counts: Number of occurrences per failure mode.
severity: Mapping of failure mode to a severity level.
Returns:
Modes sorted from highest to lowest priority score.
"""
scored = [
(mode, n, n * SEVERITY_WEIGHT.get(severity.get(mode, "medium"), 3))
for mode, n in counts.items()
]
return sorted(scored, key=lambda row: row[2], reverse=True)
Use Cases
Hallucination Hunt
Cluster fabricated facts to discover which prompts or contexts trigger them.
Wrong-Tool Patterns
Find the intents where the planner consistently selects the wrong tool.
Schema Violations
Group format errors to see which fields the model most often malforms.
Retrieval vs. Generation
Separate "bad context retrieved" from "good context, bad answer" to fix the right stage.
Best Practices & Pitfalls
Do
- Read raw failures yourself. The first 20โ50 by hand build irreplaceable intuition.
- Use a controlled vocabulary. Fixed failure-mode labels make counts meaningful.
- Localize by stage. Know whether retrieval, planning, or generation failed.
- Close the loop. Every mode becomes new golden examples and coverage cells.
Avoid
- Fixing one-offs. Chasing single bugs ignores the clusters that dominate.
- Blind trust in the LLM classifier. Always spot-check auto-assigned labels.
- Ignoring severity. Frequency alone can bury a rare but catastrophic mode.
- Analysis without action. A report nobody turns into fixes is wasted effort.
FAQ
How many failures should I read manually?
Read at least 20โ50 by hand before automating. That's usually enough to see the dominant patterns and define a good failure-mode vocabulary.
Can I skip straight to an LLM classifier?
No. The vocabulary must come from human reading first, or the LLM will invent inconsistent, unactionable labels. Automate after you understand the space.
Where does this fit with replay harnesses?
Replay (Layer 4) gives you cheap, reproducible runs to analyze; error analysis (Layer 3) tells you what to look for in them. In practice they iterate together.