RAG Evaluation Lab
A harness that tells you whether your retrieval changes actually helped
By Renjith ·
Prerequisites
- Python and pandas or Polars
- An existing document corpus to test against
- Roughly 50 questions with known correct source passages
The problem
Almost every RAG system in production was tuned the same way: someone changed the chunk size, asked it three questions they remembered, decided it felt better, and shipped.
The reason is not laziness. It is that a RAG pipeline has at least six independent knobs — chunk size, overlap, embedding model, retrieval depth, reranking, prompt — and no obvious way to attribute a change in output quality to any one of them. So people stop attributing and start guessing.
What was built
A harness that treats a RAG configuration as an experiment. You define configurations declaratively, run them all against a fixed question set, and get a comparison table with retrieval metrics and generation metrics reported separately.
That separation is the point. A bad answer has two very different causes — the right passage was never retrieved, or it was retrieved and the model ignored it — and they demand opposite fixes. A single quality score hides which one you have.
How it works
Retrieval first, always#
Run the retrieval metrics before touching anything else. In every corpus tested here, retrieval recall explained more of the variance in final answer quality than every generation-side change combined.
If recall@5 is 0.62, no prompt engineering will save you. The answer is not in the context. Prompt work at that point is rearranging furniture in a room with no floor.
evaluate.pypythondef retrieval_metrics(results: list[Result], k: int = 5) -> dict[str, float]:
hits = [r for r in results if r.gold_chunk_id in r.retrieved_ids[:k]]
reciprocal_ranks = []
for r in results:
if r.gold_chunk_id in r.retrieved_ids[:k]:
reciprocal_ranks.append(1 / (r.retrieved_ids.index(r.gold_chunk_id) + 1))
else:
reciprocal_ranks.append(0.0)
return {
"hit_rate": len(hits) / len(results),
"mrr": sum(reciprocal_ranks) / len(results),
}
What the sweep actually showed#
Across three corpora — technical documentation, a policy handbook, and a set of transcripts — the ranking of what mattered was consistent and not what the internet says:
| Change | Typical effect on recall@5 |
|---|---|
| Adding a reranker | +0.11 to +0.18 |
| Respecting document structure when chunking | +0.06 to +0.14 |
| Switching embedding model | +0.02 to +0.05 |
| Tuning chunk size within 256–768 | ±0.02, mostly noise |
Chunk size is the most-discussed parameter and, within a sensible range, close to the least important. Reranking is the least-discussed and consistently the largest single gain.
One golden set is not a benchmark
These numbers come from three corpora and one annotator. Treat them as a hypothesis to test on your corpus, which is exactly what the harness is for. Publishing a comparison table like this without saying that is how the field ended up full of confident, unreproducible claims.
Why DuckDB#
Results live in a single DuckDB file committed alongside the corpus. No service to run, no schema migration to manage, and analytical queries across hundreds of runs return instantly. For an experiment log that one person queries interactively, anything more is overhead.
Build steps
- 1
Build the golden set
Fifty questions, each annotated with the passage that actually answers it. This is the slowest step and the one that determines whether anything downstream means anything.
- 2
Define configurations as data
A YAML matrix of chunking, embedding and retrieval settings. Configurations as data rather than code branches is what makes the sweep reproducible.
- 3
Measure retrieval alone
Recall@k, MRR and hit rate — computed with no generation involved at all. This is the cheapest and most diagnostic signal in the whole pipeline.
- 4
Measure generation given perfect retrieval
Feed the known-correct passage directly and score faithfulness and answer relevance. This isolates the generation half completely.
- 5
Run the full pipeline and compare
The gap between the isolated scores and the end-to-end score tells you exactly where the loss is occurring.
- 6
Store every run
Results go to DuckDB with the full configuration. Six weeks later, "did we already try 256-token chunks with reranking?" is a query, not an argument.
Lessons learned
Separate retrieval from generation before optimising either. A combined score cannot tell you which half is broken, and the fixes are opposite.
The golden set is the project. Everything else is scaffolding around fifty carefully annotated questions.
Reranking is underrated; chunk size is overrated. The community discourse has these almost exactly backwards.
Log configurations, not just scores. Without the full configuration stored beside the result, the log is a list of numbers you cannot act on.
Limitations
Fifty questions is enough to catch a regression and far too few to make a general claim. Confidence intervals on these metrics are wide.
Faithfulness scoring uses a language model as judge, which has its own biases — it tends to reward fluent answers that stay close to the source wording.
Only tested on English text corpora. Multilingual and heavily tabular corpora behave differently and are not covered.
Future improvements
Automatic golden-set expansion by mining questions from real user logs, with human confirmation.
Cost and latency reported alongside quality, since a configuration that is 3% better and 4× more expensive is usually the wrong choice.
Per-question regression alerts, so a configuration change that breaks a specific question class is visible immediately.
Premium version
RAG Evaluation System
A production-ready evaluation harness: configuration sweeps, retrieval and generation metrics measured separately, and CI regression gates.
Related course
Production AI Engineering: RAG, Agents, Evals & MCP
Retrieval that holds up, agents that fail safely, evaluation that catches regressions, and MCP for tool integration.
Related builds and resources
RAG Evaluation System
A production-ready evaluation harness: configuration sweeps, retrieval and generation metrics measured separately, and CI regression gates.
RAG Chunking Calculator
Work out how many chunks a corpus produces, what retrieval depth fits your context window, and what indexing will cost.
Production AI Engineering: RAG, Agents, Evals & MCP
Retrieval that holds up, agents that fail safely, evaluation that catches regressions, and MCP for tool integration.
Why Your RAG Evaluation Is Wrong
Most RAG evaluations measure the wrong thing, on the wrong data, with a judge that rewards the wrong behaviour. Here is what to measure instead.
Six RAG Chunking Strategies Compared
Fixed, recursive, semantic, structural, sentence-window and parent-document chunking measured on retrieval recall across three corpora.
Building a RAG Evaluation Harness From Scratch
Why retrieval and generation must be measured separately, and how to build the harness that does it.