Development

Evaluating a RAG Pipeline End to End: Golden Sets, Judges, Drift

Antonio Evans9 min read

A split diagram showing a golden set of questions feeding a pipeline of retrieve, rerank and generate stages, with scores checked on one side and a drift chart on the other.
Key points

Key points

  1. 1A RAG pipeline fails in two distinct places, retrieval and generation, so each needs its own fixed test set and its own metric.
  2. 2A golden set of 50 to 200 real questions, each mapped to the exact passage IDs a correct answer must use, is the measurement anchor for everything else.
  3. 3Retrieval is scored with recall@k and mean reciprocal rank, which lets you judge a reranker change on its own effect before touching the generation prompt.
  4. 4Answers are scored with a deterministic checker for numbers and named entities plus a rubric-based judge run with an identical prompt every time, so score changes mean the pipeline changed, not the grader.

Build the golden set first

Every later number depends on this artifact, so treat it as versioned code, not a spreadsheet someone edits. The goal is a file where each row contains a question, the IDs of the passages a correct answer must be able to cite, and a short reference answer you trust.

Where the questions come from matters more than how many you write. Pull them from real traffic: support tickets, search logs, the questions sales engineers answer by email. Synthetic question generation from your own corpus is a legitimate bootstrap, and Microsoft's walkthrough of building a golden dataset describes generating silver data and then curating it into gold, with a human correcting both the questions and the supporting references (the path to a golden dataset). The curation step is not optional. Synthetic questions tend to mirror the phrasing of the chunk they were generated from, which makes retrieval look better than it is.

Size: 50 is the floor for a useful signal, 200 is comfortable. With 100 questions, one question flipping is one percentage point. With 50, it is two points, so a small regression can hide inside noise. Stratify deliberately: roughly half should be simple fact lookups, a quarter multi-hop questions that need two passages, and a quarter hard or ambiguous phrasing.

Passage IDs must be stable. If you re-chunk the corpus, IDs change and the golden set breaks. Pin IDs to a content hash or to a stable document ID plus a chunk ordinal, and record the chunker configuration alongside the set. Chunking decisions change what a passage even is, and how you split documents determines whether retrieval can find a concept at all: chunks that are too large average several ideas into a noisy embedding, and chunks that are too small lose the context that made them meaningful (Weaviate on chunking strategies). Pinecone makes the same point about embedding context windows and the lost-in-the-middle effect when oversized chunks are passed downstream (Pinecone's chunking guide).

A row looks like this:

{
  "id": "q_0042",
  "question": "Which plan covers on-call escalation for a 40-seat team?",
  "required_passages": ["doc_881#c3"],
  "supporting_passages": ["doc_902#c1"],
  "reference_answer": "The Business plan covers it; the Team plan requires an add-on.",
  "stratum": "multi_hop",
  "notes": "Multi-hop: plan table plus escalation policy page."
}

Mark required_passages as the passages without which the answer cannot be correct, and supporting_passages as helpful but not mandatory. This distinction is what makes recall@k meaningful. Recall against a vague "relevant documents" list will always look poor and will never tell you whether a change helped.

Score retrieval on its own

Retrieval metrics measure whether the right evidence reached the context window. Evidently's RAG evaluation guide separates retrieval from generation for exactly this reason: ranking metrics with ground truth tell you whether the retriever found useful information, independent of what the model did with it (Evidently's RAG evaluation guide).

The two metrics worth running on every commit:

  • Recall@k: the share of questions where at least one required passage appears in the top k results. Recall@5 of 0.82 means 82 of 100 questions had a required passage in the top five.
  • MRR (mean reciprocal rank): for each question, take 1 divided by the rank of the first required passage (1 if it is first, 0.5 if second, 0.333 if third), then average. MRR punishes putting the right passage at position 9 even when recall@10 is perfect.

A worked example. Take 10 questions, retrieve top 5 for each, and mark where the first required passage lands. The tutorial walkthrough of recall@k with a recipe corpus shows the per-question scoring: a target at position 3 counts as a failure for recall@1 and a success for recall@3 and recall@5, while a target at position 23 fails all three (recall@k and MRR explained).

QuestionFirst required passage rankRecall@1Recall@5Reciprocal rank
q11111.000
q23010.333
q38000.125
q42010.500
q51111.000
q6not found000.000
q74010.250
q81111.000
q96000.167
q102010.500
Totals3/10 = 0.306/10 = 0.604.875/10 = 0.488

Read the three numbers together. Recall@5 of 0.60 says four questions never saw their evidence in the top five, and no prompt change can fix those. MRR of 0.488 says that even among successes, a lot of evidence sits at rank 3 or 4, where a tight context budget may still cut it. If your generation step only feeds the top 3 passages to the model, q2 and q7 are effectively lost, and your real recall is closer to 0.30.

Log the full ranked list at a generous depth (top 50) so you can recompute recall at any k without re-running the pipeline. Store question_id, ranked_passage_ids, config_hash and corpus_version per row.

Use the retrieval numbers to judge rerankers

This is where the framework pays for itself. A reranker is a second-stage model that rescores a candidate list from first-stage retrieval, and cross-encoders read the query and document jointly, which is why they reorder well (cross-encoder mechanics and integration). Typical production shapes: retrieve 50 to 100 candidates, rerank, pass 3 to 10 chunks to the model, with reported precision gains of 15 to 35 percent and 20 to 150 milliseconds of added latency (the same integration guide).

A comparison of retrieval and rerank depths, with MRR@10 and latency reported per configuration, shows the shape you should expect: retrieving 20 and reranking to 5 gave MRR@10 of 0.612 at 80 milliseconds, 50 to 10 gave 0.683 at 105 milliseconds, 100 to 10 gave 0.695 at 125 milliseconds, and 200 to 10 gave 0.698 at 180 milliseconds, which is where the curve flattens (reranker benchmark summary). The decision rule: find the knee. If doubling the candidate pool buys less than the noise band of your golden set, stop at the smaller number.

Splunk's writeup on reranker selection adds two failure modes to test for specifically: phantom hits and context truncation, both often fixed by adjusting how text is fed to the reranker rather than by swapping the model (Splunk on selecting a reranking model). Add a handful of golden questions built around negation ("does not require") and rare exact terms (error codes, parameter names) so these cases are visible in the numbers.

Because retrieval is measured before generation, you can accept a reranker on evidence. Run the golden set three times: first-stage only, first-stage plus reranker A, first-stage plus reranker B. Compare recall@k and MRR, and log latency and token cost. A reranker that lifts MRR from 0.49 to 0.71 while recall@5 stays at 0.60 has improved precision, not coverage, and the four missed questions still need a different fix.

Score answers separately

Answer scoring answers a different question: given whatever context was retrieved, did the pipeline produce a correct, grounded, complete response? Split this into two graders.

Deterministic checks first. Extract every number, date, currency amount, product code and named entity from the answer and compare against the reference answer and the retrieved passages. This catches the failure mode no judge needs to arbitrate: a model that says the SLA is 4 hours when the passage says 24. These checks are cheap, exact, and run on every commit. Keep a per-question list of tokens that must appear (or must not appear) so the checker has something to compare against.

A rubric judge second. Use a language model as a judge with three scores per answer, each 1 to 5:

DimensionQuestion the judge answersAnchor for a 1 (low)
FaithfulnessIs every claim supported by the retrieved passages?Invents a fact not in context
CompletenessDoes it cover everything the question needs?Misses half of a two-part answer
Citation correctnessDo the cited passages actually support the claims?Cites an irrelevant or nonexistent passage

LLM-judged evaluation is a standard part of RAG evaluation practice, alongside reference-based comparison to correct answers and reference-free checks for faithfulness and completeness (Evidently's guide). Redis frames the same three dimensions as context relevance, groundedness and answer relevance, and notes that missing any one of them means you are optimizing the wrong bottleneck (Redis on RAG evaluation).

Three rules make the judge trustworthy:

  1. Freeze the prompt. Store the judge prompt in the repo with a version hash. Any run that mixes prompt versions is not comparable.
  2. Grade blind. Do not pass the judge the config name or which variant produced the answer. Order the answers randomly within a batch.
  3. Calibrate against humans once. Have a person score 30 answers with the same rubric, then measure agreement with the judge. If the judge and the person disagree on a category more than a quarter of the time, fix the rubric wording before trusting any pipeline comparison.

Keep the judge model fixed even when you upgrade the model under test. If the generator and the judge both change, you cannot tell which one moved the score.

Wire it into a regression gate

Evaluation only pays off when it blocks a bad deploy. Treat the golden set like a test suite: run it in CI on every change to chunking, embedding model, index parameters, rerank depth, prompt or generator model.

Index parameters belong on that list because approximate nearest neighbor search trades recall for speed. IVFFlat partitions vectors into clusters and searches only nearby ones, and HNSW builds a graph that trades build time and memory for search quality; changing lists, probes or the HNSW m and ef_construction settings can quietly move recall (pgvector indexing deep dive). The explainer on IVFFlat in pgvector describes the same mechanism: queries touch only the closest clusters, so the index is approximate by design (IVFFlat in pgvector). A golden run after an index rebuild is the only cheap way to catch a silent recall drop.

Hybrid search changes need the same gate. Reciprocal rank fusion merges ranked lists by position rather than score, which sidesteps the problem that BM25 scores are unbounded while cosine similarity is bounded, and rank-based fusion is more resistant to outliers than min-max or L2 normalization (OpenSearch on RRF). Adding a keyword retriever to a dense pipeline changes rankings in ways reasoning will not predict, so measure it (MongoDB on RRF and hybrid search). PostgreSQL users can now generate sparse vectors alongside dense embeddings and fuse them with RRF inside the database (hybrid search in PostgreSQL).

A minimal gate:

THRESHOLDS = {"recall_at_5": 0.80, "mrr": 0.65, "faithfulness": 4.2}

def gate(baseline, candidate):
    for metric, floor in THRESHOLDS.items():
        if candidate[metric] < floor:
            fail(f"{metric} below floor: {candidate[metric]:.2f} < {floor}")
        if candidate[metric] < baseline[metric] - TOLERANCE[metric]:
            fail(f"{metric} regressed vs baseline: "
                 f"{baseline[metric]:.3f} -> {candidate[metric]:.3f}")

Set tolerances from observed variance: run the same unchanged pipeline three times, take the spread, and set the tolerance at roughly twice that spread. If repeated runs vary by two points, a one-point drop is noise and gating on it will make the suite untrustworthy and eventually ignored.

Comment on the PR with the metric table and the list of questions that flipped from pass to fail. That list is the most useful artifact in the whole system, because it usually points at one chunking decision or one query type.

Watch for drift after launch

Golden-set scores measure a frozen snapshot. Production changes underneath it: new documents land, old ones are edited, users ask questions nobody wrote down. Split monitoring into three streams.

Corpus drift. Track the share of golden-set required_passages that still resolve to an existing chunk. When documents are re-chunked or deleted, that number falls, and a falling number means your scores are partly stale rather than the pipeline getting worse.

Query drift. Cluster production questions and compare the distribution with the golden-set strata. When a new cluster grows past a set share of traffic, promote five to ten real examples into the golden set with hand-labelled required passages. Grow the set; never silently edit history.

Score drift. Run the golden set on a schedule against production configuration and alert on movement beyond the tolerance band. A slow slide is easy to miss in per-query logs and impossible to miss in a chart.

The discipline that ties all of it together: treat eval data, judge prompt, chunker config and index parameters as versioned artifacts shipped together. When someone asks why last month's answers were better, the answer is a diff, not a debate.

If your challenge is running the same golden set across several generators or rerankers without rewriting provider code for each one, Quanta Inference exposes text, image, video, audio and embedding models through one OpenAI-compatible API key with prepaid USD pricing: https://quantaintelligence.ai/inference.

FAQ

FAQ

How many questions does a golden set need?

Fifty is the minimum for a signal you can act on, and 100 to 200 is the comfortable range. The reason is noise: with 50 questions each flip is worth two percentage points, so small regressions are indistinguishable from chance. If you must start small, start at 50 and grow by promoting real production failures.

Should retrieval and answer quality be scored in one number?

No. A single score hides which stage failed, and the fix differs completely: misses in recall@k call for chunking, embedding or reranker changes, while low faithfulness with high recall points at the prompt or the generator. Score retrieval with recall@k and MRR and answer quality with deterministic checks plus a rubric judge, then read them side by side.

How do I keep an LLM judge from drifting between runs?

Freeze three things: the judge model, the judge prompt and the rubric wording, and record a hash of each with every run. Grade blind so the judge cannot see which configuration produced an answer, and calibrate once against human scores on 30 examples. If agreement is poor, fix the rubric before trusting comparisons.

When should I trust a reranker change?

When the golden set says so, not when a benchmark blog post does. Run first stage only, then first stage plus each candidate reranker, and compare recall@k, MRR, latency and token cost. Accept the change only if MRR moves beyond your measured noise band and recall@k does not drop.

How often should the whole evaluation run?

On every change to code, chunking, index parameters, prompts or models, and on a fixed schedule against production configuration to catch corpus and query drift. Keep the run cheap enough to be routine: the deterministic checks take minutes, and the judge can score a stratified subsample on the fast path and the full set nightly.

Sources

Sources

  1. The path to a golden dataset, or how to evaluate your RAG?
  2. Chunking Strategies to Improve LLM RAG Pipeline Performance | Weaviate
  3. Chunking Strategies for LLM Applications - Pinecone
  4. A complete guide to RAG evaluation: metrics, testing and ...
  5. Retrieval Metrics Tutorial: Recall@k and MRR Explained
  6. Reranking in RAG: Cross-Encoders, Latency & Costs
  7. Cross-Encoder Reranking Improves RAG Accuracy by 40%
  8. How to Select the Right Reranking Model
  9. RAG evaluation guide: metrics, frameworks & infrastructure
  10. Optimize generative AI applications with pgvector indexing: A deep dive ...
  11. Nearest Neighbor Indexes: What Are IVFFlat Indexes in Pgvector and ...
  12. Introducing reciprocal rank fusion for hybrid search - OpenSearch
  13. Better RAG Results With Reciprocal Rank Fusion
  14. Hybrid Search in PostgreSQL: BM25, Sparse Vectors, and Reciprocal ...
About the author

About the author

Antonio Evans
Antonio Evans

Antonio Evans is the founder of Quanta Intelligence and a fractional CTO who builds HIPAA-ready AI systems for health and life-science companies. Before that he was technical co-founder of FriendsEAT for twenty years, CEO of Socialdraft, and a data engineer at IBM. Antonio lives with his dog Santino in Lisbon, Portugal.

All articles