Development

Measuring Retrieval Quality: Recall and Precision

Antonio Evans10 min read

A split scene: on the left, a person labeling queries on a whiteboard with columns for query, relevant documents, and a checkmark.
Key points

Key points

  1. 1To measure retrieval quality you need three artifacts: a document collection, a set of real user queries, and human relevance labels for query-document pairs.
  2. 2Recall@k is the fraction of all relevant documents that appear in the top k results, and precision@k is the fraction of the top k that are relevant.
  3. 3Fifty information needs is the long-standing rule of thumb for a minimum viable test set in information retrieval.
  4. 4A metric is only useful if you fix the label set, the k value and the corpus before you change chunking, embeddings or the index, so the comparison is apples to apples.

Building the labeled query set

Retrieval evaluation rests on a test collection with three parts: the document collection, a suite of information needs expressed as queries, and relevance judgments for each query-document pair, usually binary relevant or nonrelevant (Evaluation in information retrieval). Without all three you are guessing.

The first decision is where queries come from. There are three practical sources, and they trade fidelity against cost (Qdrant: Measuring Retrieval Relevance):

  1. Human annotation. Domain experts judge which documents are relevant for a curated list of queries. This produces the highest-fidelity signal, but expert time is the bottleneck, so it usually stays small and high value.
  2. Real user queries from logs. Sample query-document pairs from production, using clicks or explicit feedback such as thumbs up and thumbs down as the relevance signal. This captures the vocabulary users actually type, which synthetic queries rarely match.
  3. LLM-based synthetic generation. An LLM writes plausible queries for sampled documents. This scales cheaply to thousands of pairs, but synthetic queries are typically easier to retrieve than real ones, which inflates offline scores.

For most teams the practical recipe is to start with logs, because production queries contain the misspellings, abbreviations and vague phrasing that break naive retrievers. If you have no traffic yet, use synthetic generation for volume and hand-label a subset for ground truth.

How many queries are enough

The classic guidance is that 50 information needs is a sufficient minimum, because results are highly variable across queries and you need to average over a reasonably large set (Evaluation in information retrieval). Modern retrieval teams often go higher: a few hundred labeled pairs to detect large metric differences, with substantially more for per-slice analysis or small ranking deltas (Qdrant: Measuring Retrieval Relevance). A workable default is 100 to 200 queries for a first benchmark and more when you plan to ship marginal changes.

Balance the sample so frequent queries do not crowd out rare ones. Group by intent class (navigational, informational, troubleshooting, transactional) and take a proportional or stratified slice from each. A benchmark that is 80 percent "reset my password" will tell you nothing about the long tail that actually generates support tickets.

Judging relevance and handling multi-document answers

Label relevance against the information need, not the literal words in the query. A document is relevant if it addresses the stated need, not because it happens to contain all the query terms (Evaluation in information retrieval). This distinction matters most for vector search, where the whole point is finding documents that use different words to say the same thing (Weaviate: Vector Search Explained).

Define the unit of judgment explicitly. Is a "document" a whole page, a section, or a chunk of roughly 300 tokens? If chunking produces the retrieval unit, your labels must be assigned to chunk IDs, not page IDs, or your metrics will drift every time you re-chunk. Keep a stable doc_id in the payload of every point for exactly this reason (Qdrant: Measuring Retrieval Relevance).

Multi-document answers are where most naive labeling breaks. If the question is "what are the refund windows for each plan," four different pages may each hold one plan. Label all four as relevant. If you label only one, recall is capped at 0.25 by construction and every subsequent improvement looks like noise.

A short labeling rubric removes most ambiguity:

RuleGood practiceWhy it matters
Relevance unitJudge at the retrieval unit (chunk), not the pagePrevents metric drift when you re-chunk
Multi-document needsLabel every chunk that carries a required factOtherwise recall is artificially capped
Partial matchesLabel binary relevant/not relevant, optionally add a graded scoreBinary is simplest to compute and defend
Judged poolPool top results from every system you compareNew systems find new relevant chunks you must label
Ambiguous casesRecord the disagreement and a tiebreak ruleAuditable, reproducible labels

The pooling point is subtle but essential. If you only label the documents your current system returns, a new retriever will surface relevant documents you never judged, and those will count as misses. Pool the top k results from every configuration you intend to compare before you finalize labels.

Computing recall@k and precision@k

Precision@k is the fraction of the top k results that are relevant. Recall@k is the fraction of all relevant items that appear in the top k (Evidently AI: Precision and recall at K). Both are easiest to reason about with binary relevance judgments (Unstructured: Evaluating Search Quality).

The formulas, stated plainly:

  • precision@k = (number of relevant items in the top k) / k
  • recall@k = (number of relevant items in the top k) / (total number of relevant items for that query)

A worked example

Suppose you have a query "how do I rotate an API key," and human judges mark four chunks as relevant: A, B, C and D. Your retriever returns ten results, and the relevant ones land at positions 1, 2 and 7 (A, B and C). D never appears.

  • precision@10 = 3 / 10 = 0.30
  • recall@10 = 3 / 4 = 0.75
  • precision@3 = 2 / 3 = 0.67 (only A and B are in the top three)
  • recall@3 = 2 / 4 = 0.50

This one query shows why you report both metrics at several k values. At k=10 the system finds most of the answer but pads the result list with seven irrelevant chunks. At k=3 it is cleaner but has already lost half the relevant material.

Now average across queries. If you have 100 queries, precision@10 is the mean of 100 per-query precision values, not the total relevant chunks divided by 1,000. Query-level averaging keeps one query with many relevant chunks from dominating the score.

Precision and recall move in opposite directions as you change retrieval breadth: adding candidates raises recall and lowers precision, while aggressive filtering does the reverse (Unstructured: Evaluating Search Quality). So a single number is never the whole story.

A compact implementation for computing both from a golden set is short enough to keep in your repository:

def precision_recall_at_k(retrieved_ids, relevant_ids, k):
    top_k = retrieved_ids[:k]
    hits = len(set(top_k) & set(relevant_ids))
    precision = hits / k
    recall = hits / len(relevant_ids) if relevant_ids else 0.0
    return precision, recall

results = []
for query in golden_set:
    retrieved = retriever.search(query["text"], limit=10)
    p, r = precision_recall_at_k(retrieved, query["labels"].keys(), k=10)
    results.append({"query_id": query["id"], "precision": p, "recall": r})

mean_precision = sum(x["precision"] for x in results) / len(results)
mean_recall = sum(x["recall"] for x in results) / len(results)

Libraries such as ranx cover recall@k, precision@k, MRR, MAP and NDCG through one consistent interface, which saves you from hand-rolling each metric (Qdrant: Measuring Retrieval Relevance).

Choosing k

Pick k to match what your product does with the results. If a chat assistant stuffs eight chunks into the prompt, measure recall@8, because that is your real ceiling on grounding. If a search UI shows ten results per page, measure precision@10, because that is what the user sees first.

A useful pattern is a two-number report: recall@k where k equals your generation context budget, and precision@k at the depth a human would scan. Watch the gap. A large gap means your ranker is pushing relevant material too far down and a reranking stage may be worth more than a better index.

A decision rule for whether a change actually helped

Metrics are noisy. A jump from 0.71 to 0.74 recall@10 across 100 queries may be real or may be query mix. The decision rule is:

  1. Change one thing at a time: chunking, embedding model, index type or reranking.
  2. Run the full golden set against the unchanged control and the variant.
  3. Compare per-query outcomes, not just the mean. Count how many queries improved and how many regressed.
  4. Only ship when the mean improves and the regressions are explained. Regressions concentrated in one intent class are a warning sign, not noise.

To make step 3 concrete, store results as a small table keyed by query and configuration:

ConfigurationRecall@10Precision@10Queries improvedQueries regressed
Baseline, 500-token chunks0.710.34n/an/a
300-token chunks, 15% overlap0.780.36319
300-token chunks plus HNSW index0.790.3663

The chunk-size change earns its complexity: a broad improvement with comparatively few regressions. The index change adds a point of recall, which likely reflects that the approximate index finds slightly different neighbors than the exact scan, not a better representation. Approximate nearest neighbor search trades a little accuracy for speed at scale, so small recall swings between exact and approximate search are expected (Elastic: What is vector search?).

Why chunking changes move these numbers

Chunks that are too large mix several ideas into one embedding, producing a noisy averaged vector that matches nothing precisely. Chunks that are too small lose the context a human needs to interpret them (Weaviate: Chunking Strategies for RAG). Both failure modes show up as lost recall, but for opposite reasons: large chunks never score high enough, small chunks score high for the wrong query.

The practical test for a chunk is whether it makes sense to a human when read alone; if it does, it will make sense to the model (Pinecone: Chunking Strategies). Your labeled set lets you confirm that intuition with a number instead of an opinion.

Reading index and hybrid search effects

Index choice is a separate axis from chunking, and your golden set can separate them. Vector indexes use approximate nearest neighbor algorithms that sacrifice perfect accuracy for efficiency in high-dimensional space (Elastic: What is vector search?). In pgvector, IVFFlat divides vectors into clusters and searches only the nearest clusters, while HNSW builds a navigable graph; both are approximations with tunable accuracy, and both can be measured against an exact scan (PGVector: HNSW vs IVFFlat).

The evaluation move is to run one configuration with exact search and the same configuration with the approximate index, holding chunking fixed. The recall difference is your index's accuracy cost, cleanly isolated from every other variable. Qdrant documents this as a distinct concern (ANN recall) from retrieval relevance, precisely because mixing them hides which one regressed (Qdrant: Measuring Retrieval Relevance).

Hybrid retrieval deserves its own row in your comparison table. Sparse keyword scoring such as BM25 excels on exact identifiers, and dense vector search excels on paraphrase, so combining them covers more query types than either alone (Hybrid Search: BM25, Vector and Reranking). Fusion is usually done with reciprocal rank fusion, which scores each document by 1/(k + rank) summed across result lists, so a document ranked highly by both methods wins without any score normalization (OpenSearch: Reciprocal rank fusion).

When you benchmark a hybrid configuration, split your golden set by query type first. Keyword-heavy needs (error codes, parameter names, SKUs) and conceptual needs behave differently, and a single aggregate recall number can hide the case where hybrid helped exact-match queries and hurt paraphrase. Slice-level reporting is how you catch that (Qdrant: Measuring Retrieval Relevance).

Pitfalls that make benchmarks lie

Tuning on the test set. It is wrong to report results obtained by tuning parameters to maximize performance on the same collection. Keep a development set for tuning and report on a held-out set for an unbiased estimate (Evaluation in information retrieval).

Unstable labels. If you re-chunk the corpus and your labels were attached to chunk IDs, every label is now invalid. Attach labels to stable source document IDs with a mapping to current chunks, or accept a re-labeling cost each time.

Synthetic query inflation. LLM-generated queries tend to be easier than real ones, so offline scores built only on synthetic data overstate production quality (Qdrant: Measuring Retrieval Relevance).

One-metric tunnel vision. A retriever can find one useful result, miss everything else, rank exact matches well and handle paraphrase poorly, all at once. Pick metrics that match the failure mode you care about rather than treating a single score as a scoreboard (How to Evaluate Information Retrieval Systems).

Ignoring the top of the list. Precision and recall count relevant items in the top k without rewarding where inside that window they appear (Evidently AI). If position within the prompt matters for generation quality, add mean reciprocal rank or NDCG alongside them.

What this looks like in practice

A first evaluation pass, in order:

  1. Export 200 production queries stratified across intent classes.
  2. Define the retrieval unit and confirm a stable ID exists on every point.
  3. Have two people label independently on 30 queries, compare, and write down tiebreak rules until agreement is high.
  4. Label the remaining queries. Pool top results from the current system plus one alternative.
  5. Compute precision@k and recall@k at two k values per query, then report means and per-intent breakdowns.
  6. Commit the golden set and the scoring script to the repository. Treat the label file like test fixtures: reviewed changes only.
  7. Re-run on every retrieval change and record the comparison table.

Step 6 is the one teams skip and then regret. A golden set that lives in a spreadsheet dies the moment its owner changes teams. A golden set in version control with a scoring script becomes a regression test suite for the entire retrieval layer.

If you are still assembling the pipeline itself, generating and comparing embeddings for many document types, the per-model API work adds up. Quanta Inference puts text, image, video, audio and embedding models behind one OpenAI-compatible API key, which shortens the loop between "run the golden set against a new embedding model" and seeing the recall number. When the measurement habit is in place, the rest of retrieval tuning becomes an ordinary, auditable engineering workflow.

FAQ

FAQ

How many queries do I need before the metrics mean anything?

Fifty information needs has long been the minimum for averaging out query-level variance (Evaluation in information retrieval). For detecting large differences between configurations, a few hundred labeled pairs is more comfortable, and per-slice or small-delta analysis needs more than that (Qdrant).

Should I use binary or graded relevance labels?

Start binary. Binary relevant and nonrelevant is the standard ground truth format and is simplest to compute against precision and recall (Evaluation in information retrieval). Move to graded labels only when you need NDCG and your judges can apply the scale consistently.

What should k be?

Set k to your actual context budget or display depth. If the generator receives eight chunks, recall@8 is your grounding ceiling; if users see ten results, precision@10 is what they experience. Report both so you see whether you are missing material or padding the list.

Why did recall drop after I changed the embedding model?

Check whether your labels still align with the retrieval unit, and whether the new retriever surfaced relevant chunks you never judged. If you did not pool results from both systems before labeling, the new system's correct answers may be counted as misses (Qdrant).

Can I skip human labeling and use an LLM as judge?

You can, but treat it as a supplement. Log-based feedback and synthetic generation both add value cheaply, yet synthetic queries are easier than real ones and inflate scores, so human labels on a subset remain the anchor for trusting any automated signal (Qdrant).

Sources

Sources

  1. Evaluation in information retrieval
  2. Measuring Retrieval Relevance
  3. Vector Search Explained - Weaviate
  4. Precision and recall at K in ranking and recommendations - Evidently AI
  5. Evaluating Search Quality in Information Retrieval Systems
  6. What is vector search? Better search with ML - Elastic
  7. Chunking Strategies to Improve LLM RAG Pipeline Performance | Weaviate
  8. Chunking Strategies for LLM Applications - Pinecone
  9. PGVector: HNSW vs IVFFlat, A Comprehensive Study - Medium
  10. Hybrid Search: BM25, Vector & Reranking 2026 - Digital Applied
  11. Introducing reciprocal rank fusion for hybrid search - OpenSearch
  12. How to Evaluate Information Retrieval Systems
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