Evaluating RAG Systems
Retrieval-Augmented Generation — fetch documents, then answer using them — is the most common production LLM architecture, so it's the one you're most likely to be asked to evaluate. The trick that makes it manageable: it has two separate failure surfaces.
The idea that makes RAG eval tractable: a RAG system has two failure surfaces, measured separately before together. When an answer is wrong, the first question is — did retrieval fail, or did generation fail? Different fixes; a single end-to-end number can't tell them apart.
Surface 1 — retrieval
Did the right documents reach the context? Classic IR: recall@k, precision@k, MRR, nDCG. If retrieval fails, no prompt cleverness saves the answer — the facts aren't in front of the model.
Surface 2 — generation (given the context)
- Faithfulness / groundedness — every claim supported by the context? (Anti-hallucination.)
- Answer relevance — does it address the question?
- Context precision — was retrieved context useful or padded?
- Citation accuracy — do cited sources support their claims?
| Case | recall@5 | faithfulness | Diagnosis | Fix |
|---|---|---|---|---|
| A | 0.30 | — | Retrieval | Chunking, embeddings, reranking |
| B | 0.95 | 0.60 | Generation | Prompt, model, grounding |
Two identical-looking "wrong answers," two opposite fixes. Separating the surfaces is what turns RAG debugging from guesswork into a decision.
These generation-side checks — context relevance (did retrieval fetch the right material?), groundedness / faithfulness (is the answer supported by it?), and answer relevance (does it address the question?) — are popularly branded the RAG Triad (TruLens's term). Name it in an interview, then add the retrieval-side context precision & recall (RAGAS's metrics) and you've covered both surfaces with the exact vocabulary a reviewer listens for. Keep the attribution straight: the Triad is those three; precision/recall are RAGAS.
A perfectly faithful system that always says "the context doesn't say" is useless; a maximally helpful one that invents details is dangerous. Good RAG eval tracks both and picks an operating point per use case — high faithfulness for legal/medical, more latitude for brainstorming.
Diagnose a broken RAG system
For each wrong answer, decide retrieval vs. generation from the evidence. Faithfulness ships as a free mock; one flag flips it to the real SDK. Goal: the right verdict per query, justified by recall@3 and faithfulness.
Lab code — runs free (mock judge by default)
USE_REAL_API = False # free mock by default; True needs a key
def recall_at_k(retrieved, gold, k):
top = retrieved[:k]
return len(set(top) & set(gold)) / len(gold) if gold else 0.0
if USE_REAL_API:
import anthropic
from pydantic import BaseModel
client = anthropic.Anthropic()
class Grounded(BaseModel):
reason: str
grounded: bool
def faithful(context, answer):
r = client.messages.parse(model="claude-opus-4-8", max_tokens=300,
messages=[{"role": "user", "content":
f"Is every claim in the ANSWER supported by the CONTEXT? "
f"Reason, then grounded: true/false.\n\nCTX:\n{context}\n\nANS:\n{answer}"}],
output_format=Grounded)
return r.parsed_output.grounded
else:
def faithful(context, answer):
return "[INVENTED]" not in answer # mock: ungrounded if it invents
CASES = [ # (query, retrieved_ids, gold_ids, context, answer)
("refund window?", ["d2","d9","d4"], ["d1"], "",
"You have 30 days. [INVENTED]"),
("reset password?", ["d5","d1","d7"], ["d5"], "Click 'Forgot password'.",
"Click 'Forgot password' and check your email."),
("export data?", ["d8","d3"], ["d8"], "Export is on the Settings page.",
"Data export is under Billing. [INVENTED]"),
]
for q, retrieved, gold, ctx, ans in CASES:
r = recall_at_k(retrieved, gold, k=3)
if r < 1.0:
verdict = "RETRIEVAL -> fix chunking / embeddings / reranking"
elif not faithful(ctx, ans):
verdict = "GENERATION -> fix prompt / grounding"
else:
verdict = "OK"
print(f"{q:16} recall@3={r:.2f} {verdict}")
refund window? recall@3=0.00 RETRIEVAL -> fix chunking / embeddings / reranking
reset password? recall@3=1.00 OK
export data? recall@3=1.00 GENERATION -> fix prompt / grounding
Case 1: the gold doc never reached the context (recall 0) — the model answered blind; fix retrieval. Case 3: the gold doc was retrieved (recall 1.0) but the answer says "Billing" when the context says "Settings" — retrieval did its job; generation went off-script. The same symptom ("wrong answer") split into two root causes the instant you measured the surfaces separately . Retrieval scoring (recall@k, nDCG) is classic information-retrieval work you'll use constantly.
Beacon: measuring the right stage
Beacon's documentation assistant gave wrong answers, and the team was about to spend a sprint on prompt engineering. First they measured the two stages separately. Retrieval: was the answer-bearing passage even retrieved? Generation: given the right passage, did the model answer faithfully? The numbers were decisive — 40% of wrong answers were retrieval failures (the passage never reached the model), while generation, when handed the right context, was faithful 94% of the time.
A single end-to-end "accuracy" number would have hidden this and sent them tuning the wrong stage. Because they measured retrieval recall and generation faithfulness as distinct metrics, they knew the bottleneck was upstream and fixed chunking and retrieval instead. The lesson for evaluating any RAG system: a blended score tells you whether it's bad; separate per-stage metrics tell you where — and only the second kind tells you what to fix.
This chapter: Remi answers from Meridian's billing-policy docs, so they evaluate it as a RAG system. Retrieval recall is high overall, but on edge-case fee questions Remi cites the wrong fee schedule 18% of the time — a retrieval failure (an outdated doc still in the corpus), not a generation one. Measuring the stages separately points the fix upstream, at the corpus, not at Remi's prompt. (Ch 8: Remi starts taking refund actions — a whole new risk.)
Quiz · Chapter 7
- A RAG answer is wrong; recall@5 = 0.30. First fix:
- Different system: recall@5 = 0.95 but faithfulness = 0.60. The problem is:
- The foundational principle of RAG evaluation:
- A RAG system that always replies "the context doesn't say" scores near-perfect faithfulness. Why isn't that a win?
- "Faithfulness/groundedness" measures whether: