Overview

A single end-to-end accuracy number hides which half of a RAG system is broken. Score retrieval (did the right chunks come back?) and generation (did the answer use them correctly?) separately, on a fixed golden set, and rerun the suite on every change. For the general agent eval pattern, see evaluation.

Build the golden set from real failures

  • Each row: question, gold_chunk_ids[], gold_answer, slice_tag.
  • Start at 50 rows; grow toward 200 to 500 when you need to detect small differences between models. A 1-point recall gap on 200 queries is probably noise, so read the disagreements instead of trusting the aggregate.
  • Sample queries from real logs. Include short queries, multi-hop questions, exact identifiers, rare terms, and time-sensitive questions.
  • Version the set like code. Never edit a row to make a failing test pass.

Score retrieval with recall, MRR, and nDCG

  • recall@k: the fraction of gold chunks that appear in the top k. Track k = 5, 10, and 50. Use hit@k (at least one gold chunk in the top k) when one chunk suffices.
  • MRR@k: the mean of 1 / rank of the first gold chunk. Use MRR@5 when the prompt only sees five chunks.
  • nDCG@k: use when chunks have graded relevance instead of binary.
def recall_at_k(retrieved, gold, k):
    return len(set(retrieved[:k]) & gold) / len(gold)
 
def mrr_at_k(retrieved, gold, k):
    return next((1 / r for r, c in enumerate(retrieved[:k], 1) if c in gold), 0.0)

Read the gap between depths. High recall@50 with low recall@5 means the reranker is the lever (see rag-reranking). Low recall@50 means the candidate pool is missing the gold chunks, so fix chunking, the embedding model, or hybrid search first; a reranker cannot recover chunks that were never retrieved.

Score generation with faithfulness and answer relevance

  • Faithfulness: is every claim in the answer supported by the retrieved context? Score per claim with a judge model or a RAGAS-style rubric.
  • Answer relevance: does the answer respond to the question? An on-topic but evasive answer fails.

Faithfulness without relevance ships safe, useless answers; relevance without faithfulness ships confident hallucinations. Score citation validity and coverage too (see rag-citations).

Audit the judge

Judge models drift. Use a rubric with yes/no questions, not a vibe score, and prefer a stronger judge than the model under test. Each release, have humans rate 30 to 50 sampled rows and compare; when agreement is low, rewrite the rubric or retire the judge. See evaluation for the harness.

A/B embedding models and index settings on one golden set

When comparing models, dimensions, or index parameters, hold everything else fixed: same golden set, same chunks, same k, same query preprocessing. Change one variable at a time.

  1. Embed the corpus and queries with each candidate (same dimension unless dimension is the variable).
  2. Build identical indexes.
  3. Compute recall@10, MRR@10, and latency per candidate, plus per-slice deltas.
  4. Inspect the queries where the candidates disagree.

See embeddings for the models and embeddings-dimensionality for the dimension procedure.

Track slices, not just the aggregate

An average hides regressions on small slices. A change that is 2 points better overall but 10 points worse on adversarial is a regression. Tag rows (easy, edge, adversarial, time-sensitive, multi-hop, keyword-heavy) and report recall@5, faithfulness, answer relevance, p95 latency, and cost per query per slice.

Gate CI on the suite

prompt, model, chunker, or index change -> run suite -> diff vs baseline ->
block merge when any slice regresses past your tolerance
  • Run a sample on every PR and the full set nightly.
  • Cache embeddings and reranker scores where deterministic so the suite stays cheap.
  • Rerun after every embedding-model change, chunker change, or re-index.

Triage failures by stage

Read ten failed queries each release.

  • Gold chunk never retrieved: retrieval failure (rag-chunking, rag-retrieval).
  • Retrieved but outside the top k: reranker failure.
  • In the prompt but the answer is wrong: generation or citation failure.