Evaluation & Testing
IC5IC6

Agent & RAG Evals: Component-Level Scoring That Survives Production

Decompose RAG and agents into the components that can break independently, score each one, and gate every change so a regression points at the broken layer instead of a vibe.

15 min read · 12 sections
Runnable: ai-eng-wiki/examples/evals/eval_harness.py

1. Quick anchor

A RAG answer and an agent run are both pipelines of things that can fail independently, and the entire job of evaluation is to attribute a bad output to the specific stage that produced it. For RAG that means scoring retrieval (did the right chunks come back?) and generation (did the model use them honestly?) as separate numbers, because a hallucinated answer and a missing-context answer demand opposite fixes. For agents it means scoring the trajectory (right tools, right order, valid arguments) separately from the outcome (task completed), because a correct answer reached through the wrong path is a brittle answer waiting to break. A single end-to-end "was it good?" score tells you nothing actionable; component-level evals turn a regression into a pointer. Build these as a runnable harness with CI gates so every prompt tweak, model swap, and chunking change runs the same suite before it ships.

2. Why interviewers probe this

  • IC5 — can you decompose? They want to see you refuse a single opaque "answer quality" metric and instead name the components (retrieval vs generation; trajectory vs outcome), pick the right metric per component, and explain what a low score on each one means operationally. They're checking whether you'd ship blind or with attribution.
  • IC5 — do you know the failure modes of the judge? RAGAS and LLM-as-judge are not ground truth. If you can't name position bias, verbosity bias, and self-enhancement bias — and the fact that RAGAS cannot verify whether the source corpus is even true — you'll over-trust a number and ship a regression.
  • IC6 — can you build the system, not the metric? Offline gates plus online monitoring, golden sets sourced from production failures, contamination defenses, and the specific signal that catches a silent provider update. They want the org-level eval strategy, the cost/latency budget of judges in CI, and how you keep the eval itself from rotting.
  • All levels — honesty. The strongest signal is admitting where the eval lies: token-overlap faithfulness misfires on paraphrase, exact-match trajectory punishes valid alternate paths, and a saturated benchmark stops discriminating. Naming the lie is the senior move.

3. Concept build-up

Beginner explainerNew here? The words first

The words first.

  • RAG — retrieval-augmented generation: fetch relevant text chunks, then have an LLM answer using them.
  • Retrieval vs generation — two stages: finding the right context, then writing an answer from it. Each can fail alone.
  • Faithfulness — does every claim in the answer actually follow from the retrieved context? (Hallucination detector.)
  • Context recall — of the chunks you needed, how many did retrieval actually fetch? Sets the ceiling on quality.
  • Context precision — of the chunks you fetched, how many were actually relevant? (Signal-to-noise.)
  • Trajectory — the sequence of tool calls an agent made to reach its answer.
  • Tool correctness — did the agent pick the right tool with valid arguments?
  • Task completion — did the agent actually accomplish the user's goal? (The outcome.)
  • Gate — a CI threshold; if a metric drops below it, the build fails and the change doesn't ship.

Step by step.

  1. Take a fixed set of test inputs (a golden set), ideally mined from real production failures.
  2. Run your RAG/agent on each input, capturing the intermediate steps (retrieved chunks, tool calls), not just the final text.
  3. Score each component separately with a metric in [0, 1].
  4. Average each metric across the dataset to get one number per metric.
  5. Compare each number against a threshold (the gate) and against the previous baseline.
  6. Fail the build on any regression; only promote a change when every gate passes.
  7. In production, re-run a sample of these metrics on live traffic to catch drift the offline set never saw.

Remember this: evals exist to tell you which component broke, not just that something broke.

3.1 RAG decomposes into a retrieval ceiling and a generation honesty check

The single most useful mental model: context recall is the ceiling, faithfulness is the floor. If the fact needed to answer never made it into the retrieved context (low context recall), the generator cannot be right no matter how good it is — you have a retrieval bug, and tuning the prompt is wasted effort. If the context was there but the answer asserts things the context doesn't support (low faithfulness), you have a generation bug — the model is hallucinating, and you fix it with grounding instructions, a smaller temperature, or a better model, not with better retrieval.

The four RAGAS metrics map cleanly onto the two stages (RAGAS docs):

Stage Metric Question it answers Formula
Retrieval Context recall Did we fetch everything needed? (# necessary chunks retrieved) / (# necessary chunks)
Retrieval Context precision Is retrieval low-noise? (# relevant retrieved chunks) / (# retrieved chunks)
Generation Faithfulness Is the answer grounded, not hallucinated? (# claims supported by context) / (# claims in answer)
Generation Answer relevancy Is the answer on-topic for the question? semantic similarity of answer to question

All four are reference-free — they need no gold answer, only the question, the retrieved context, and the generated answer (context recall additionally needs a human-marked set of necessary chunks). That's what makes RAGAS cheap to run on production traffic.

The mechanism for faithfulness is worth understanding precisely because it's where interviewers push. You extract atomic factual claims from the answer (ideally with an LLM; cheaply with sentence-splitting), then for each claim run a Natural Language Inference (NLI) check against the context: is this claim entailed, contradicted, or neutral relative to the context? Faithfulness is the fraction entailed. NLI catches logical contradictions that pure embedding similarity misses — "the tower is 200m tall" and "the tower is 300m tall" are embedding-similar but NLI-contradictory (FutureAGI NLI).

The critical limitation, and a classic IC6 trap: RAGAS operates only at the inference layer. Faithfulness asks "is the answer grounded in the retrieved context" — it does not ask "is the retrieved context true." If your corpus contains a wrong fact, a perfectly faithful answer repeats it and scores 1.0. Independent benchmarking found no RAGAS-style tool reliably distinguished factually correct from incorrect contexts (Atlan). Source trustworthiness is a separate, upstream problem.

Faithfulness — on real numbers

Faithfulness = (claims the context supports) / (total claims in the answer). It measures honesty of generation, not correctness of the source.

Answer: "The Eiffel Tower was built in 1889. It is made of iron. It is the tallest building in the world." Split into 3 claims:

  • c1 = "built in 1889"
  • c2 = "made of iron"
  • c3 = "tallest building in the world"

Retrieved context:

  • "The Eiffel Tower was completed in 1889."
  • "The Eiffel Tower is constructed of wrought iron."

NLI check each claim against the context:

  • c1 -> entailed (1889 appears) -> supported
  • c2 -> entailed (wrought iron) -> supported
  • c3 -> NOT in context, and false -> unsupported (hallucination)

Faithfulness = 2 / 3 = 0.667.

What it did: flagged exactly one fabricated claim (c3) without needing a gold answer. The score dropping below a 0.80 gate is your hallucination alarm — and it points at the generator, because the context never mentioned "tallest building", so retrieval is innocent.

3.2 Alternative RAG framings: the TruLens triad and where to draw boundaries

TruLens reframes the same idea as the RAG triad: context relevance (query↔context), groundedness (answer↔context, ≈ faithfulness), and answer relevance (answer↔query) (Atlan). Same two-stage decomposition, different names. Its real edge is OpenTelemetry-based span-level tracing, which lets you localize failures inside a multi-step agentic pipeline rather than scoring only the final output. The cost is a steeper learning curve.

The boundary that matters in interviews: answer relevancy and faithfulness are orthogonal. An answer can be faithful (every claim grounded) but irrelevant (grounded in the wrong part of the context, or evasive). It can be relevant (clearly about the question) but unfaithful (confidently wrong). You need both numbers; neither subsumes the other. This orthogonality is the answer to the IC4 question below.

3.3 Agents: score the trajectory, not just the destination

Agent evaluation's central insight is that the final answer is necessary but not sufficient. An agent that returns the right answer by calling a payments API it should never have touched, or by lucking into the answer after three wrong tool calls, has a broken trajectory — and that brittleness shows up as a production incident the moment inputs shift. So you score three layers (Confident AI; Medium Vinod):

  1. Trajectory accuracy — did the agent take a correct path? Not just which tools, but the order, the reasoning, and the decision sequence. Reaching the right answer via wrong tools is a trajectory failure despite a correct outcome.
  2. Tool correctness — three sub-checks: selection (right tool chosen?), argument accuracy (parameters syntactically and semantically valid?), execution validation (did the tool output match expectations?). Tool correctness correlates strongly with final-answer accuracy and is the single most diagnostic agentic signal.
  3. Task completion (a.k.a. goal accuracy / task success) — binary or scalar: did the agent accomplish the stated user goal? This is the outcome, measured as a success rate across trajectories, often with intermediate checkpoints for multi-step tasks.

Layer in the non-quality dimensions that gate production: system efficiency (token usage, latency, tool-call count, API cost) and behavioral signals (action advancement — is each step making progress; agent flow — is it following the intended workflow). These are measured at two granularities: session level (the whole trajectory) and node level (each individual step). When something regresses, node-level scores tell you which step failed.

The hard design choice — and a frequent IC6 question — is how strict to make trajectory scoring. Exact tool-sequence match is brittle: many valid paths reach the same goal, and a too-strict scorer fails good agents (false negatives that train you to chase noise). The harness below uses longest common subsequence (LCS) of called-vs-expected tools, normalized by expected length: it rewards the right tools in the right relative order while tolerating extra detours. That's a deliberate tradeoff — LCS forgives a redundant call but still penalizes a wrong order or a skipped step. For open-ended agents you often graduate to an LLM-as-judge over the trajectory ("given the goal and these steps, was this a reasonable path?"), accepting the judge's bias and cost in exchange for flexibility.

▶ Live agent loop

3.4 The judge is biased — design around it

Whenever a metric is computed by an LLM (faithfulness via NLI-prompting, G-Eval rubrics, trajectory judging), you inherit three systematic biases you must name in an interview (Arxiv 2602.02219):

  • Position bias — in rubric/pairwise settings the judge prefers options at specific positions (start or end of the list), independent of content. Mitigation: balanced permutation — evenly distribute each score option across all positions and aggregate; this both reveals the latent bias and improves correlation with human judges. Or simpler: randomize candidate order and average across randomizations.
  • Verbosity bias — judges equate longer with better, because more text gives more surface to find supporting phrases. Mitigation: explicit anti-length instructions, reference-based metrics that penalize padding, or ensembling judges with differing verbosity preferences.
  • Self-enhancement bias — a judge favors outputs from its own model family (in-group preference). Mitigation: ensemble judges from different model families, anonymize the generation source, or use an external reference model as judge.

A senior tell: pairwise comparison has lower calibration variance than pointwise absolute scoring. Asking "is A better than B?" is more stable than asking "rate A from 1–10," because absolute scales drift run-to-run. When you can afford it, prefer pairwise for ranking and reserve pointwise for cheap pass/fail gates.

4. Minimal implementation

The harness in examples/evals/eval_harness.py implements all of the above with a pluggable judge: the default StubJudge is deterministic token-overlap so the suite runs offline in CI with no API key, and you swap in a real model (AnthropicJudge-shaped: claim extraction + NLI entailment) for production fidelity. The companion file examples/evals/metrics.py holds the from-scratch retrieval/classification primitives (ndcg_at_k, recall_at_k, roc_auc).

# RAG: retrieval ceiling vs generation honesty, scored separately.
def faithfulness(sample: RagSample, judge: Judge) -> float:
    """RAGAS faithfulness = supported_claims / total_claims. Reference-free."""
    claims = split_claims(sample.answer)              # 1 claim per sentence (cheap)
    if not claims:
        return 0.0
    context = "\n".join(sample.retrieved)
    supported = sum(1 for c in claims if judge.entails(c, context))  # NLI per claim
    return supported / len(claims)
 
def context_recall(sample: RagSample, judge: Judge) -> float:
    """Coverage: fraction of NECESSARY (gold) chunks actually retrieved.
    This is the upper bound on RAG quality — if a fact never reached context,
    no prompt fix can recover it."""
    if not sample.gold_contexts:
        return 1.0
    retrieved = "\n".join(sample.retrieved)
    covered = sum(1 for g in sample.gold_contexts if judge.entails(g, retrieved))
    return covered / len(sample.gold_contexts)
 
# Agent: order-aware path score, tolerant of detours, strict on order.
def trajectory_accuracy(case: AgentCase) -> float:
    """LCS(called tools, expected tools) / len(expected). Rewards right tools
    in right order; forgives extra calls; punishes skips and reorderings."""
    called, expected = [c.name for c in case.trace.calls], case.expected_tools
    if not expected:
        return 1.0
    m, n = len(called), len(expected)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            dp[i][j] = (dp[i-1][j-1] + 1 if called[i-1] == expected[j-1]
                        else max(dp[i-1][j], dp[i][j-1]))
    return dp[m][n] / len(expected)

The harness then runs each metric over a dataset, averages it, and applies CI regression gates (faithfulness >= 0.80, context_recall >= 0.90, task_completion >= 1.0), emitting a machine-readable ci_report.json for artifact storage and dashboards, and exiting non-zero if any gate fails. Running it on the bundled dataset — which deliberately plants one hallucinated claim ("tallest building in the world") in sample 2 — produces:

=== RAG component evals ===
  faithfulness          0.417  FAIL (gate >= 0.8)
  answer_relevancy      0.625  PASS (no gate)
  context_precision     0.583  PASS (no gate)
  context_recall        1.000  PASS (gate >= 0.9)
=== Agent evals ===
  trajectory_accuracy   1.000  PASS (no gate)
  tool_correctness      1.000  PASS (gate >= 0.9)
  task_completion       1.000  PASS (gate >= 1.0)
GATE FAILED: 1 metric(s) below threshold.

Read the attribution: context_recall is 1.0 (retrieval fetched the needed chunk) but faithfulness is 0.417 (the answer hallucinated) — the failing layer is the generator, not retrieval. That's the whole point of component-level scoring. Note also that the StubJudge's token-overlap is conservative: it rejects valid paraphrases, so 0.417 understates the true faithfulness. That's a feature in CI (no flaky API, deterministic) and a reminder for production: swap in a real NLI model before trusting the absolute number. In a real pipeline this main() is your pytest body — DeepEval and Inspect AI wrap exactly this loop with typed metric objects and richer scorers.

5. Production tradeoffs

Lever Cheap / fast Expensive / accurate What changes at scale
Judge Token-overlap / embedding similarity stub LLM-as-judge with CoT + NLI claim extraction LLM judge cost dominates CI; cache judgments, sample traffic
RAG metric set Faithfulness + answer relevancy only + context precision/recall (needs gold contexts) Gold-context labeling is the bottleneck; mine from prod failures
Agent scoring Final-answer exact match Trajectory LCS + per-step tool/arg validation + judge Node-level traces explode storage; sample + retain failures
Gate strictness Absolute thresholds Relative-to-baseline (no significant degradation) Absolute gates rot as the bar moves; relative gates catch drift
Coverage One golden set Offline golden + online prod sampling Online catches what offline never imagined

The dominant production cost is the judge in CI. An LLM judge on a 500-case suite, run on every PR, is real money and 30–120s of latency; teams cache deterministic judgments keyed on (input, model, prompt) hash, run the full judged suite nightly, and gate PRs on a cheaper deterministic subset. Budget gates concretely — e.g. faithfulness > 0.85, latency < 2s, cost < $0.01/call (Latitude).

The dominant quality failure mode is the eval lying to you. Three flavors: (1) the judge bias of §3.4 inflating or deflating scores; (2) benchmark saturation — as a metric approaches its ceiling it stops discriminating, and further "gains" don't transfer to production; (3) data contamination — test items leak into pretraining, and paraphrase evades string-match detection, so a model "passes" by memorization. Defenses: rephrased/paraphrased benchmarks to expose concealed contamination, production-derived fresh golden sets (the distribution shift is itself a freshness signal), and treating any single offline number with suspicion (Arxiv 2505.18102).

At scale the architecture splits in two. Offline evals gate CI/CD on a curated golden set — they catch regressions the team introduces (a prompt edit, a model swap). Online evals run a sampled subset of the same metrics on live production traffic — they catch changes that happen to the product: a silent provider model update, a new user cohort with novel inputs, behavioral drift. The two are synergistic, not redundant: offline alone misses the silent provider update; online alone can't gate a deploy (Arize). Online drift is detected via input-embedding KL divergence / Population Stability Index (data drift) and behavioral signals — refusal-rate spikes, schema-violation rate, tool-call error rate (concept drift). When drift fires: increase offline eval frequency, fold the new production data into the golden set, prompt-tune or retrain on the shifted distribution, and re-baseline if it improves.

6. How it's asked

[IC4] Faithfulness is high but answer-relevancy is low. What broke, and what do you fix? The answer is grounded in the retrieved context (no hallucination) but isn't actually addressing the question — the model latched onto the wrong part of the context or gave an evasive, on-corpus-but-off-question reply. These metrics are orthogonal: grounding and relevance are independent axes. The fix is on the generation side — sharpen the prompt to answer the specific question and possibly improve retrieval ranking so the most query-relevant chunk is surfaced first, but it is not a hallucination problem and you should not reach for grounding constraints.
[IC5] Your agent reaches the correct final answer on 95% of cases but trajectory accuracy is 0.6. Is that a problem? How do you score the path? Yes, it's a latent problem. A correct outcome via a wrong path means the agent is calling unnecessary or wrong tools, getting lucky, or relying on a fragile shortcut — that brittleness surfaces as an incident the moment inputs shift, costs add up (extra tool calls), or a wrong tool has side effects (it called issue_refund when it shouldn't have). I'd score the path with an order-aware metric like LCS of called-vs-expected tools normalized by expected length, plus per-call tool-selection and argument-validity checks, and for open-ended agents an LLM-as-judge over the trajectory. The combination of high task-completion and low trajectory accuracy is precisely the "right answer, wrong path" signal I want to catch before it generalizes badly.
[IC5] Why not just use accuracy, and why is RAGAS reference-free a big deal? Accuracy is meaningless on the imbalanced, open-ended distributions these systems see, and for RAG/agents there's often no single gold string to match. Reference-free metrics (faithfulness, answer relevancy, context precision) need only the question, context, and answer — so you can run them on raw production traffic where no human ever wrote a gold answer. That's what makes online evaluation possible at all; reference-based metrics can't follow you into production.
[IC6] Design the offline + online eval system for a customer-support RAG agent shipping daily. Where are the regression gates, and what catches a silent model-provider update? Offline: a golden set seeded from production failures and edge cases, scored on the four RAG component metrics plus agent trajectory/tool/task metrics, run in CI on every PR with relative-to-baseline gates (no metric degrades significantly) and a cheap deterministic-judge subset for PR latency, full LLM-judged suite nightly. Online: sample ~1–5% of live traffic, run the reference-free subset (faithfulness, answer relevancy, context precision, tool validity) continuously, and monitor behavioral signals — refusal-rate, schema-violation rate, tool-error rate — plus input-embedding KL divergence for data drift. The thing that catches a silent provider update is the online layer: a model swap upstream shows up as a faithfulness/refusal regression on production data even though the offline golden set (which the team didn't touch) still passes. The trigger then is automatic: fold the drifted traffic into the golden set, re-baseline, and gate the next deploy on it. I'd build this on Inspect AI or DeepEval for the scoring and Braintrust for production tracing + dataset management.
[IC6] Your LLM-judge faithfulness scores disagree with human raters. How do you debug and fix it? First quantify the disagreement with correlation against a human-labeled subset, then test for the three known biases: run balanced permutation to expose position bias, check whether the judge systematically rewards longer answers (verbosity bias) by regressing score on length, and check self-enhancement by seeing if it favors outputs from its own model family. Fixes: ensemble judges across model families, anonymize generation source, switch pointwise to pairwise to cut calibration variance, and pin the judge model version so it doesn't silently drift. If correlation is still poor, the rubric is underspecified — I'd have the judge emit chain-of-thought and evaluation steps (G-Eval style) so I can read why it scored as it did and tighten the criteria.

7. Pitfalls & flashcards

  • Single end-to-end score with no attribution. "Answer quality 0.7" tells you nothing to fix. Always decompose: retrieval vs generation, trajectory vs outcome.
  • Trusting faithfulness as a truth check. It only checks grounding in retrieved context, never whether the corpus is correct. A faithful answer can repeat a corpus lie. Source trust is a separate problem.
  • Context recall ignored. If retrieval didn't fetch the needed fact, no generation fix can save you. Recall is the ceiling — check it first when an answer is wrong.
  • Over-strict trajectory matching. Exact tool-sequence equality fails valid alternate paths and trains you to chase false negatives. Use LCS or a judge, and tolerate detours while penalizing skips/reorderings.
  • Ignoring judge bias. Position, verbosity, and self-enhancement bias are systematic, not noise. Permute, ensemble, anonymize, prefer pairwise.
  • Contaminated or saturated benchmarks. Memorized test items and ceiling-bound metrics stop measuring anything. Use paraphrased and production-fresh golden sets.
  • Offline-only. Offline can't see a silent provider update or a new user cohort. You need online sampling of reference-free metrics plus drift signals.

Flashcard. RAG: context recall is the ceiling, faithfulness is the floor — recall says the fact made it into context, faithfulness says the answer didn't invent anything; check recall before you touch the prompt. Agents: score the trajectory and the outcome separately — a right answer via the wrong path is a brittle answer.

8. Further reading

Next: Eval-driven development & CI regression gates for wiring this harness into the deploy pipeline, and /rag for the retrieval levers that move context precision and recall.

Primary sources
← More in Evaluation & Testing