๐Ÿ”Ž
Layer 3 of 6 ยท Agent Evaluation Stack
Error Analysis

Find What's
Actually Broken.

A score tells you how much is wrong. Error analysis tells you what is wrong and why. Cluster failures into patterns, find root causes, and fix the issue behind ten bugs instead of ten symptoms.

Cluster
Group similar failures
Label
Name failure modes
Prioritize
Fix by impact
Layer 3
Turns scores into action

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.

In One Sentence

"Read every failure, group the patterns, and fix the cause behind the biggest cluster first."


Analogy
  • 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.

errors/annotations.jsonl
{
  "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"
}
Common failure modes
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

The Analysis Loop
flowchart LR F["โŒ Collect failures"] --> R["๐Ÿ‘€ Read & annotate"] R --> C["๐Ÿงฉ Cluster by mode"] C --> P["๐Ÿ“Š Rank by frequency ร— severity"] P --> H["๐Ÿ’ก Form root-cause hypothesis"] H --> X["๐Ÿ› ๏ธ Fix"] X --> V["๐Ÿ” Re-evaluate"] V --> F
Failure Pareto (example)
hallucinated_fact42%
wrong_tool23%
format_violation18%
incomplete_answer11%
tone_mismatch6%

Two modes account for 65% of failures. Fixing hallucinated facts first yields the biggest gain.

Step-by-Step Implementation

1

Capture failures with context

A failure record keeps everything needed to diagnose it later without re-running the agent.

errors/models.py
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
    ]
2

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.

errors/classify.py
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)
3

Produce a prioritized report

Weight each mode by severity so the top of the list is the highest-value fix.

errors/report.py
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

Chat Agents

Hallucination Hunt

Cluster fabricated facts to discover which prompts or contexts trigger them.

Tool Agents

Wrong-Tool Patterns

Find the intents where the planner consistently selects the wrong tool.

Extraction

Schema Violations

Group format errors to see which fields the model most often malforms.

RAG

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.

Resources