Retrieval & Search

Retrieval Evaluation Guide

Measuring retrieval quality - recall@k, MRR, nDCG, and building golden test sets for RAG pipelines.

45 min readIntermediateLast reviewed: 20 July 2026

Quick Summary

Retrieval evaluation measures whether the right documents appear in top-k results before the LLM generates an answer.

One Analogy

Retrieval eval is a unit test for search — generation tests are integration tests.

Engineering Rule

Build a 50+ query golden set and gate every retrieval change on recall@k before touching prompts or LLMs.

Try the Retrieval Evaluation Lab

Measure retrieval quality with explicit relevance judgments using Recall@K, MRR, and nDCG@K — and see why more retrieval machinery does not automatically mean better results.

Try Interactive Lab

TL;DR

  • Retrieval evaluation measures whether the right documents are found - before the LLM ever generates an answer.

  • Recall@k is the most important metric - is the correct document in the top-k results? If not, no LLM can save you.

  • Build a golden test set of 50–200 query-document pairs from your actual corpus and expected queries.

  • Evaluate retrieval separately from generation - conflating them makes it impossible to diagnose which stage failed.

  • Run evals in CI - block deploys that regress retrieval metrics. Catch quality drops before users do.

Why This Matters

Most RAG quality problems are retrieval problems. When a user gets a wrong answer, the failure usually falls into one of three stages:

  1. The correct document wasn't retrieved (retrieval failure — often the dominant source of incorrect answers)
  2. The correct document was retrieved but ranked too low (ranking failure)
  3. The LLM ignored or misinterpreted the context (generation failure)

Generation quality cannot exceed retrieval quality — if the right document never reaches the LLM, no prompt or model upgrade can fully compensate.

If you only evaluate end-to-end answer quality, you can't distinguish between these. You end up prompt-engineering a generation problem that's actually a retrieval problem - or switching embedding models when chunking is the real issue.

Retrieval evaluation isolates the search step, gives you actionable metrics, and lets you iterate on chunking, embedding models, hybrid search, and reranking with clear feedback. It's the difference between guessing and engineering.

The Problem Retrieval Evaluation Solves

RAG pipelines have many configurable components - chunk size, embedding model, search method, reranker, filters. Each change can help or hurt retrieval quality. Without measurement:

  • You don't know if a change improved or degraded retrieval
  • You can't compare embedding models objectively on your data
  • You can't set quality gates in CI/CD
  • You can't diagnose user-reported failures systematically
  • You optimize the wrong component (prompts instead of chunking)

Retrieval evaluation provides a quantitative feedback loop for one of the highest-leverage stages in the pipeline. A 5% recall@5 improvement translates directly to fewer wrong answers in deployed systems.

How We Got Here

Search quality measurement existed long before RAG. TREC benchmarks, MS MARCO, and BEIR established IR metrics that RAG teams now reuse for embedding retrieval.

Diagram: Evolution of retrieval evaluation

timeline
    title Retrieval evaluation
    1990s : TREC ad-hoc benchmarks
    2016 : MS MARCO passage ranking
    2020 : DPR + RAG paper metrics
    2022 : BEIR zero-shot eval
    2023 : RAGAS automated RAG eval
    2024 : CI gates on recall@k in prod teams

Production RAG teams adapted classical IR metrics (recall, MRR, nDCG) because retrieval failure — not generation — causes most wrong answers.

Era Focus Lesson for RAG
TREC / BEIR Benchmark corpora Domain transfer matters — eval on your data
MS MARCO Cross-encoder labels Reranker gains measurable with nDCG
RAG era End-to-end faithfulness Still need isolated retrieval eval
Ops era CI regression gates Block deploys that drop recall@k

Use Retrieval Evaluation before RAG Evaluation for end-to-end faithfulness. Tune ANN indexes and re-ranking against the same golden set.

What Is Retrieval Evaluation?

Retrieval evaluation measures how well a search system finds relevant documents for a given query. You provide:

  1. A test set of queries with known relevant documents (ground truth)
  2. A retrieval system to evaluate
  3. Metrics that quantify search quality
# Minimal retrieval eval
test_cases = [
    {"query": "refund policy for premium members", "relevant_doc_ids": ["doc_042"]},
    {"query": "API rate limit configuration", "relevant_doc_ids": ["doc_118", "doc_203"]},
    {"query": "SSO setup with Okta", "relevant_doc_ids": ["doc_087"]},
]

for case in test_cases:
    results = retriever.search(case["query"], top_k=10)
    retrieved_ids = [r.id for r in results]
    hit = any(doc_id in retrieved_ids for doc_id in case["relevant_doc_ids"])
    print(f"Query: {case['query']} → {'HIT' if hit else 'MISS'}")

The output tells you whether retrieval works for each query - independent of the LLM.

How Retrieval Evaluation Works

Core Metrics

Metric What It Measures Formula Intuition When to Use
Recall@k Is the relevant doc in top-k? Hits / total queries Primary metric - did we find it?
Precision@k What fraction of top-k is relevant? Relevant in top-k / k When false positives matter
MRR How high is the first relevant result ranked? Average of 1/rank of first hit When rank position matters
nDCG@k Are relevant docs ranked higher than irrelevant? Discounted cumulative gain When multiple relevant docs exist
Hit Rate Same as recall@k for single relevant doc Binary: found or not Simplest metric to start

Recall@k - The Primary Metric

Recall@k = (number of queries where relevant doc appears in top-k) / (total queries)

If recall@5 = 0.80, the correct document appears in the top 5 results for 80% of test queries.

def recall_at_k(retrieved_ids: list[str], relevant_ids: list[str], k: int) -> float:
    top_k = set(retrieved_ids[:k])
    return 1.0 if top_k & set(relevant_ids) else 0.0

def mean_recall_at_k(all_results: list[tuple], k: int) -> float:
    scores = [recall_at_k(retrieved, relevant, k) for retrieved, relevant in all_results]
    return sum(scores) / len(scores)

Target benchmarks:

  • recall@5 < 0.60 - retrieval is broken, fix before deploying
  • recall@5 0.60–0.80 - acceptable, optimize chunking and search
  • recall@5 0.80–0.90 - good, focus on reranking and generation
  • recall@5 > 0.90 - excellent, diminishing returns on retrieval

MRR (Mean Reciprocal Rank)

MRR = average of 1/rank_of_first_relevant_document

If the first relevant document is at rank 1, score = 1.0. At rank 3, score = 0.33. MRR rewards getting the right document to the top.

def reciprocal_rank(retrieved_ids: list[str], relevant_ids: list[str]) -> float:
    for rank, doc_id in enumerate(retrieved_ids, 1):
        if doc_id in relevant_ids:
            return 1.0 / rank
    return 0.0

Important when you pass only top-1 or top-3 to the LLM - rank matters, not just presence.

nDCG@k (Normalized Discounted Cumulative Gain)

Accounts for multiple relevant documents and their rank positions. Relevant documents ranked higher score better. Uses graded relevance (highly relevant = 3, partially relevant = 1, irrelevant = 0).

import math

def dcg_at_k(relevances: list[int], k: int) -> float:
    relevances = relevances[:k]
    return sum(rel / math.log2(i + 2) for i, rel in enumerate(relevances))

def ndcg_at_k(retrieved_ids: list[str], relevance_map: dict[str, int], k: int) -> float:
    relevances = [relevance_map.get(doc_id, 0) for doc_id in retrieved_ids[:k]]
    dcg = dcg_at_k(relevances, k)
    ideal = dcg_at_k(sorted(relevance_map.values(), reverse=True), k)
    return dcg / ideal if ideal > 0 else 0.0

Use nDCG when queries have multiple relevant documents with different relevance grades.

Diagram: Metric selection by scenario

flowchart TD
    A[Eval goal?] -->|Found at all?| B[Recall@k]
    A -->|First hit rank?| C[MRR]
    A -->|Multiple relevant docs?| D[nDCG@k]
    A -->|After reranker?| E[Precision@3]
    B --> F[Primary RAG gate]
    E --> G[Reranker A/B]

Recall@k is the primary gate; MRR and nDCG add rank-quality signal; precision@3 validates rerankers.

Architecture

A retrieval evaluation pipeline has four components:

Component Purpose Implementation
Golden test set Ground truth query-document pairs JSON/CSV file, 50–200 cases
Retriever under test The search pipeline to evaluate Your RAG retrieval code
Metrics calculator Computes recall, MRR, nDCG Custom script or RAGAS
Reporting Tracks metrics over time Dashboard, CI output, logs

The retriever scores document chunks against the query embedding and returns the top-k passages that ground the generator.

RAG retriever component

Source: Meta AI

Run evals on every change to chunking, embedding model, search config, reranker, or query transformation. Block deploys that drop recall@5 by more than 2%.

Diagram: Eval pipeline architecture

flowchart TB
    GS[Golden test set] --> EV[Evaluator]
    RET[Retriever under test] --> EV
    EV --> M[Metrics recall MRR nDCG]
    M --> CI[CI gate]
    M --> DASH[Dashboard]
    CI -->|fail| BLOCK[Block deploy]
    CI -->|pass| SHIP[Ship change]

Offline eval runs on every retrieval PR; production logs feed back into the golden set.

Diagram: Eval request flow

sequenceDiagram
    participant CI as CI pipeline
    participant E as Evaluator
    participant R as Retriever
    participant M as Metrics
    CI->>E: run eval suite
    loop Each test case
        E->>R: search query
        R-->>E: top-k doc IDs
        E->>M: compute recall MRR nDCG
    end
    M-->>CI: pass or fail

Each query in the golden set produces metric contributions; use exact search when ANN non-determinism matters.

Step-by-Step Flow

Step 1: Build a golden test set. Collect 50–200 real queries and identify the correct source document(s) for each. Sources: user query logs, support tickets, manually written test cases.

Step 2: Define relevance criteria. Binary (relevant/not) for recall@k. Graded (0–3) for nDCG. Document what makes a document "relevant" for each query.

Step 3: Run baseline evaluation. Measure recall@5, MRR, and nDCG@5 on your current retrieval pipeline. This is your benchmark.

Step 4: Iterate and compare. Change one component at a time (chunk size, embedding model, hybrid search, reranker). Re-run eval. Keep changes that improve metrics.

Step 5: Automate in CI. Run eval on every PR that touches retrieval code. Fail if recall@5 drops below threshold.

Step 6: Monitor in production. Sample live queries, log retrieved documents, and periodically audit retrieval quality with human review.

Step 7: Expand the test set. Add failure cases from production. Every user-reported wrong answer becomes a new test case.

Real Production Example

An eval pipeline that runs in CI and blocks regressions:

import json
from dataclasses import dataclass

@dataclass
class EvalCase:
    query: str
    relevant_doc_ids: list[str]
    relevance_grades: dict[str, int] = None  # for nDCG

@dataclass
class EvalResult:
    recall_at_5: float
    recall_at_10: float
    mrr: float
    ndcg_at_5: float
    failures: list[dict]

class RetrievalEvaluator:
    def __init__(self, retriever, test_set: list[EvalCase]):
        self.retriever = retriever
        self.test_set = test_set

    def evaluate(self, k_values: list[int] = [5, 10]) -> EvalResult:
        recalls = {k: [] for k in k_values}
        mrrs = []
        ndcgs = []
        failures = []

        for case in self.test_set:
            results = self.retriever.search(case.query, top_k=max(k_values))
            retrieved_ids = [r.id for r in results]

            for k in k_values:
                hit = any(d in retrieved_ids[:k] for d in case.relevant_doc_ids)
                recalls[k].append(1.0 if hit else 0.0)

            rr = 0.0
            for rank, doc_id in enumerate(retrieved_ids, 1):
                if doc_id in case.relevant_doc_ids:
                    rr = 1.0 / rank
                    break
            mrrs.append(rr)

            if case.relevance_grades:
                ndcg = self._ndcg(retrieved_ids, case.relevance_grades, k=5)
                ndcgs.append(ndcg)

            if not any(d in retrieved_ids[:5] for d in case.relevant_doc_ids):
                failures.append({
                    "query": case.query,
                    "expected": case.relevant_doc_ids,
                    "got": retrieved_ids[:5],
                })

        return EvalResult(
            recall_at_5=sum(recalls[5]) / len(recalls[5]),
            recall_at_10=sum(recalls[10]) / len(recalls[10]),
            mrr=sum(mrrs) / len(mrrs),
            ndcg_at_5=sum(ndcgs) / len(ndcgs) if ndcgs else 0.0,
            failures=failures,
        )

    def _ndcg(self, retrieved_ids, relevance_map, k):
        import math
        rels = [relevance_map.get(d, 0) for d in retrieved_ids[:k]]
        dcg = sum(r / math.log2(i + 2) for i, r in enumerate(rels))
        ideal = sorted(relevance_map.values(), reverse=True)[:k]
        idcg = sum(r / math.log2(i + 2) for i, r in enumerate(ideal))
        return dcg / idcg if idcg > 0 else 0.0

    def assert_quality(self, min_recall_at_5: float = 0.75):
        result = self.evaluate()
        print(f"Recall@5:  {result.recall_at_5:.3f}")
        print(f"Recall@10: {result.recall_at_10:.3f}")
        print(f"MRR:       {result.mrr:.3f}")
        print(f"nDCG@5:    {result.ndcg_at_5:.3f}")
        print(f"Failures:  {len(result.failures)}/{len(self.test_set)}")

        if result.failures:
            print("\nFailed queries:")
            for f in result.failures[:5]:
                print(f"  Q: {f['query']}")
                print(f"  Expected: {f['expected']}")
                print(f"  Got: {f['got']}")

        assert result.recall_at_5 >= min_recall_at_5, (
            f"Recall@5 {result.recall_at_5:.3f} below threshold {min_recall_at_5}"
        )

# CI usage
test_set = [EvalCase(**c) for c in json.load(open("golden_test_set.json"))]
evaluator = RetrievalEvaluator(retriever, test_set)
evaluator.assert_quality(min_recall_at_5=0.75)

Design Decisions

Decision Option A Option B When to choose
Test set size 50 cases 200+ cases 50 for CI speed; 200+ for comprehensive coverage
Primary metric Recall@k nDCG@k Recall@k for single-answer Q&A; nDCG when multiple relevant docs
k value 5 10 Match k to what you pass to the LLM; also measure @10 for reranking headroom
Eval frequency Every PR Weekly Every PR for retrieval changes; weekly for monitoring
Relevance labels Binary Graded (0–3) Binary to start; graded when queries have multiple relevant docs
Synthetic vs real Real user queries Synthetic Real queries always; supplement with synthetic for edge cases

Common patterns

| Pattern | Description | | ----------------------- | ---------------------------------- | ---------------------------------------- | | Golden set CI gate | Block if recall@5 drops >2% | Standard production discipline | | Component isolation | Change one knob at a time | Attribute improvements correctly | | Failure case mining | Add prod misses to test set | Continuous improvement loop | | Exact vs ANN eval | Exact search for eval consistency | ANN tuning uses recall vs exact baseline | | Shadow comparison | Run new index beside old on sample | Safe rollout for index changes |

Comparisons

Recall@k vs MRR vs nDCG vs precision@k

Metric Question answered Best for
Recall@k Is relevant doc in top-k? Primary RAG gate
MRR How high is first relevant hit? Top-1 / top-3 to LLM
nDCG@k Are multiple relevant docs ranked well? Multi-doc answers
Precision@k What fraction of top-k is relevant? After reranking

Retrieval eval vs end-to-end RAG eval

Layer Measures Tooling
Retrieval only recall@k, MRR, nDCG Custom script, ranx, Pyserini
Generation only faithfulness, correctness RAGAS, DeepEval
End-to-end user-facing answer quality Human review + LLM judge

Decision tree: which metric to optimize

Decision tree: retrieval metrics

flowchart TD
    A[Single correct doc per query?] -->|Yes| B[Recall@5 primary]
    A -->|No| C[nDCG@5 primary]
    B --> D{Pass to LLM top-3?}
    D -->|Yes| E[Also track MRR]
    D -->|No| F[Recall@10 for rerank headroom]
    C --> G[Graded relevance labels]
    E --> H[Add precision@3 after rerank]

Start with recall@5; add MRR when rank position matters; add nDCG for multi-relevant queries.

Compare retrieval stacks via Best Vector Databases: Milvus vs Qdrant · Qdrant vs Pinecone.

Common Mistakes

  1. Only evaluating end-to-end answers. Conflates retrieval and generation failures. Evaluate retrieval independently first.

  2. Test set too small. Five queries isn't evaluation - it's anecdote. Minimum 50 cases for statistical confidence.

  3. Test set not representative. Only testing easy queries inflates metrics. Include hard cases: paraphrases, ambiguous queries, multi-document answers.

  4. Not updating the test set. A static test set goes stale as the corpus changes. Add production failures as new test cases continuously.

  5. Changing multiple variables at once. Switching embedding model AND chunk size AND adding hybrid search - you can't attribute the improvement. Change one thing at a time.

  6. Ignoring failure analysis. A recall@5 of 0.82 tells you 18% fail. Reading the actual failed queries tells you WHY - and what to fix.

  7. Not running evals in CI. Manual evals get skipped under deadline pressure. Automate or it won't happen.

Where It Breaks Down

Subjective relevance - "What is our culture?" may have many partially relevant documents. Binary relevance labels don't capture this well. Use graded relevance or LLM-as-judge with human calibration.

Evolving corpora - Documents are added, updated, and removed. A test case referencing a deleted document becomes invalid. Version your test set alongside your corpus.

Expensive ground truth - Labeling 200 query-document pairs requires domain expertise. Start with 50 high-confidence cases. Expand incrementally from production failure logs.

Non-deterministic retrieval - Approximate nearest neighbor search can return slightly different results across runs. Run evals multiple times or use exact search for evaluation consistency.

Retrieval eval doesn't measure generation. High recall@5 with a bad LLM still produces bad answers. Evaluate both stages - see RAG Evaluation for end-to-end metrics.

When NOT to Use Automated Retrieval Eval Alone

Skip relying only on automated retrieval metrics when:

  1. Relevance is subjective — culture, policy interpretation; supplement with human labels or calibrated LLM judges.
  2. Corpus changes daily without versioned test sets — stale ground truth produces false failures; version tests with corpus snapshots.
  3. You have fewer than 30 labeled queries — metrics are noise; expand the golden set before gating CI.
  4. You only measure end-to-end answer quality — cannot diagnose retrieval vs generation; add isolated retrieval eval.
  5. ANN non-determinism is high — run multiple seeds or use exact search for eval consistency.

Always pair automated metrics with periodic human audit of failed queries.

Running in Production

Best Practice

Best Practices - Instrument every stage, version embedding models, enforce access control at retrieval time, and evaluate on a fixed golden set before shipping changes.

Dimension Consideration
Scaling Eval is offline - run on a test set, not live traffic. 200 test cases evaluate in 1–5 minutes depending on retrieval latency.
Latency Not on the query path. Run in CI (minutes) or as a scheduled job (hourly/weekly).
Cost Embedding costs for 200 queries: negligible (~$0.001). LLM-based eval (RAGAS): ~$1–5 per run.
Monitoring Track recall@5 over time in a dashboard. Alert on drops > 2%. Log production retrieval results for spot-checking.
Evaluation Meta-evaluation: is your test set still representative? Review quarterly. Add production failures as new cases. TREC, BEIR, and other public benchmarks are useful for comparison but should not replace evaluation on your corpus and query distribution.
Security Test set may contain sensitive queries. Store securely. Include access control test cases (cross-tenant queries).

Important

Evaluate retrieval before generation. If recall@5 is below 0.70, fix retrieval first - no amount of prompt engineering or LLM upgrading will compensate.

Concept guides

Rankings

Comparisons

Tools

If you understood this topic, read next:

Diagram: Evaluation learning path

flowchart LR
    A[RAG pipeline] --> B[Retrieval Eval]
    B --> C[Reranking]
    C --> D[RAG Eval]
    D --> E[Production]

Prerequisites: RAG · Embedding Models

Next topics: Re-ranking · Chunking Strategies · RAG Evaluation

Interview Questions

  1. Why evaluate retrieval separately from generation?

    • Expected: isolate failure stage; retrieval failures are often the dominant source — prompt tuning won't fix missing docs.
  2. What is recall@k and why is it primary for RAG?

    • Expected: fraction of queries where relevant doc in top-k; LLM cannot use doc not retrieved.
  3. Recall@5 vs MRR — when use each?

    • Expected: recall for presence; MRR when only top-1/top-3 passed to LLM and rank matters.
  4. How do you build a golden test set?

    • Expected: real queries + expert-labeled relevant doc IDs; 50–200 cases; grow from prod failures.
  5. How do you evaluate reranking?

    • Expected: same test set; compare precision@3 and nDCG@5 with/without reranker.
  6. What recall@5 threshold for production?

    • Expected: 0.80+ target; below 0.60 broken; 0.60–0.80 iterate chunking/search.
  7. How do CI gates work for retrieval?

    • Expected: run eval on PR; fail if recall@5 drops >2% vs baseline.
  8. How do you eval metadata filtering / tenant isolation?

    • Expected: cross-tenant queries must return zero unauthorized docs in top-k.

Key Takeaways

  • Retrieval evaluation measures whether the right documents are found — independent of LLM quality.
  • Recall@5 is the primary metric; target 0.80+ for production.
  • Build a golden test set of 50–200 real query-document pairs from your corpus.
  • Change one retrieval component at a time and re-evaluate.
  • Run evals in CI to block deploys that regress retrieval quality.
  • Analyze failures individually — aggregate metrics tell you how much; failure cases tell you why.

FAQs

What is the most important retrieval metric?

Recall@k - specifically recall@5 for most RAG pipelines. It answers: "Is the correct document in the top 5 results?" If not, the LLM can't produce a correct answer.

How many test cases do I need?

Minimum 50 for basic confidence. 100–200 for deployed CI gates. Expand continuously from operational failure logs.

How do I build a golden test set?

Collect real user queries (or write representative ones). For each query, identify the correct source document(s) with a domain expert. Store as query → relevant_doc_ids pairs.

Should I evaluate retrieval and generation separately?

Yes. Always. Retrieval eval tells you if the right documents were found. Generation eval tells you if the LLM used them correctly. Combined eval hides which stage failed.

What recall@5 score is good enough?

0.80+ for production. Below 0.60 means retrieval is broken. Between 0.60–0.80, iterate on chunking, embeddings, and search before deploying.

How do I evaluate reranking?

Compare precision@3 and nDCG@5 with and without reranking on the same test set. The reranked pipeline should show higher precision at lower k values.

Can I use LLMs to evaluate retrieval?

Yes - RAGAS uses LLM-as-judge for context precision and recall. Useful for scale, but calibrate against human labels on a sample. LLM judges can be inconsistent.

How often should I run retrieval eval?

On every PR that touches retrieval code (CI gate). Weekly as a scheduled job for monitoring. After any corpus update (new documents, re-indexing).

What is a good MRR score?

MRR > 0.70 means the first relevant document is typically in the top 2 results. MRR > 0.85 is excellent. Low MRR with high recall@5 means the right docs are found but ranked too low - add reranking.

How do I handle queries with multiple relevant documents?

Use nDCG@k with graded relevance scores (0–3). A query about "security policies" may have 3 relevant docs with different relevance levels.

Should I include negative test cases?

Yes. Include queries where the answer is NOT in the corpus. Verify the retriever returns low-scoring results, enabling the system to say "I don't know."

How do I evaluate metadata filtering?

Include test cases that verify tenant isolation (cross-tenant queries return zero results) and permission boundaries (employee queries don't return admin docs).

References

Further Reading

Next Topics

Learning Path

Continue Learning

Related Guides

Related Tools

ToolCategoryPurposeWebsiteBest For
LangChain
PopularOpen SourceAPI
frameworksFramework for building LLM-powered applications and workflows.langchain.comRAG systems
LlamaIndex
Open SourceAPI
frameworksData framework for connecting LLMs to private and structured data.llamaindex.aiRAG over documents
Qdrant
Open SourceAPI
Vector DBOpen-source vector database with filtering and hybrid search.qdrant.techRAG systems
Cohere
APICloud
LLMEnterprise NLP platform with strong embedding and reranking APIs for RAG.cohere.comProduction embeddings
RAGAS
Open SourcePython SDK
EvaluationReference-free evaluation framework specifically for RAG pipelines.docs.ragas.ioRAG faithfulness scoring

Related Rankings

Related Comparisons