RAG Evaluation Metrics: Diagnose Retrieval Before You Fine-Tune (2026)

Khimananda Oli 12 min read Linux, Virtualization
RAG Evaluation Metrics: Diagnose Retrieval Before You Fine-Tune (2026)

By Khimananda Oli | Last reviewed: September 2026

RAG evaluation metrics split into two families because a RAG system is two systems. Retrieval metrics — recall@k, precision@k, MRR, nDCG@k — ask whether the right passage was found at all. Generation metrics — faithfulness, answer relevancy, answer correctness — ask what the model did with it. Score them separately, or you cannot tell which half failed, and you will fine-tune a model to fix a broken retriever.

What are RAG evaluation metrics, and why score the two stages separately?

The single most expensive mistake in production retrieval-augmented generation is diagnosing the wrong half. A support bot answers 68% of questions correctly, the team concludes the model is not good enough, and three weeks disappear into a fine-tuning run. The evaluation that would have taken a day shows recall@5 sitting at 0.61 — for nearly four questions in ten, the passage containing the answer was never retrieved. No amount of fine-tuning fixes that, because the model never saw the information.

This is why RAG evaluation metrics come in two families. An end-to-end score — "is the final answer good" — is a single number produced by a pipeline with at least two independent failure modes. It tells you that something is wrong and nothing about where. Decompose the pipeline, attach metrics at each boundary, and the same test run becomes a diagnosis instead of a verdict.

Where RAG evaluation metrics attachTwo stages, two failure modes, two families of metricQueryuser questionRetrieverembed + searchtop-k chunksContextk passagesinto the promptGeneratorLLM answersfrom contextAnswershown to userRetrieval metricsDid we find the right passage?recall@k · precision@kMRR · nDCG@kcontext recalldeterministic · no LLM neededGeneration metricsWhat did the model do with it?faithfulnessanswer relevancyanswer correctnessusually LLM-judged · calibrateA single end-to-end score cannot tell you which of the two stages failed.
RAG evaluation metrics attach at two boundaries: retrieval metrics score the passages returned, generation metrics score what the model produced from them.

Which retrieval metrics actually matter?

Retrieval metrics are cheap, deterministic and require no LLM at all — you need a golden set that maps each question to the document IDs that genuinely answer it. Run them first, every time, because they set a hard ceiling on everything downstream.

MetricWhat it asksA low score means
recall@kOf the passages that answer this question, how many appeared in the top k?The information never reached the model. This is the ceiling metric.
precision@kOf the k passages returned, how many were actually relevant?The prompt is padded with noise, raising cost and distracting the model.
MRRHow high did the first correct passage rank?Correct context exists but sits low, where models weight it less.
nDCG@kAre the most relevant passages ranked highest, with graded relevance?Ordering is poor — usually a reranking problem, not a recall problem.
context recallWhat fraction of the reference answer's claims are supported by retrieved context?Retrieval is partially right — enough for a plausible answer, not a complete one.

All four of the classic ones fit in a few lines and belong in your own repository rather than behind a framework, because you will want to slice them by query type later:

import math

def recall_at_k(relevant, retrieved, k):
    """relevant: set of doc ids that answer the question."""
    if not relevant:
        return 0.0
    hits = set(relevant).intersection(retrieved[:k])
    return len(hits) / len(relevant)

def precision_at_k(relevant, retrieved, k):
    top = retrieved[:k]
    if not top:
        return 0.0
    return len(set(relevant).intersection(top)) / len(top)

def reciprocal_rank(relevant, retrieved):
    for rank, doc in enumerate(retrieved, start=1):
        if doc in relevant:
            return 1.0 / rank
    return 0.0

def ndcg_at_k(relevant, retrieved, k):
    dcg = sum(1.0 / math.log2(i + 2)
              for i, doc in enumerate(retrieved[:k]) if doc in relevant)
    ideal = sum(1.0 / math.log2(i + 2)
                for i in range(min(len(relevant), k)))
    return dcg / ideal if ideal else 0.0

Read recall@k first and everything else second. If recall@5 is 0.61, your pipeline's best possible accuracy is roughly 61% no matter which model generates the answer. Chasing faithfulness or swapping in a larger LLM while that number sits low is effort spent on the wrong stage. Recall is usually raised by fixing chunking, the embedding model, or k — the territory covered in building an embeddings pipeline and vector databases for RAG.

Which generation metrics catch the rest?

Once the right context is reliably retrieved, the remaining failures belong to the model. Three metrics cover almost all of them:

  • Faithfulness (groundedness) — what fraction of the claims in the answer are supported by the retrieved context? This is the direct measure of the hallucination the whole architecture exists to prevent. A faithfulness of 0.72 means roughly a quarter of your answer's assertions are invented.
  • Answer relevancy — does the answer address the question actually asked? Catches the model that produces a beautifully grounded summary of the wrong topic.
  • Answer correctness — how close is the answer to the reference answer in your golden set? The closest thing to end-to-end accuracy, and the least diagnostic on its own.

These are typically scored by an LLM judge, which brings its own failure modes. Judges show position bias (favouring whichever candidate appears first), self-preference bias (rating output from their own model family higher), and length bias (mistaking verbosity for quality). Three habits keep a judge honest: hand-label 50 to 100 examples and check your judge's agreement against those humans before trusting it, randomise candidate order on every pairwise comparison, and pin the judge model version so a silent upstream upgrade does not shift your baseline overnight. The general methodology is covered in how to evaluate LLM outputs; what follows is the RAG-specific application.

from datasets import Dataset
from ragas import evaluate
from ragas.metrics import (
    faithfulness, answer_relevancy,
    context_precision, context_recall,
)

samples = Dataset.from_list([
    {
        "question": row["question"],
        "answer": run_pipeline(row["question"]),
        "contexts": retrieve(row["question"], k=5),
        "ground_truth": row["reference_answer"],
    }
    for row in golden_set
])

report = evaluate(
    samples,
    metrics=[faithfulness, answer_relevancy,
             context_precision, context_recall],
)
print(report)

What does a golden set for RAG need that a generic eval set does not?

A hundred to two hundred well-chosen items beats a thousand scraped ones. Two requirements are specific to RAG and routinely skipped.

First, every item needs document-level labels, not just a reference answer. Without knowing which chunk IDs genuinely answer the question, you cannot compute recall@k at all — which is exactly why teams end up with only an end-to-end score and no diagnosis.

Second, include questions your corpus cannot answer, and make the reference answer an explicit refusal. Roughly 10 to 15% of the set should be unanswerable. This is the only way to measure whether your system declines gracefully or confabulates, and it is the slice that most often regresses when someone raises k or loosens a similarity threshold. Stratify the rest across the query shapes you actually see: single-fact lookups, multi-hop questions needing two documents, comparisons, and time-sensitive questions where the corpus holds several versions.

Generating candidate questions from your own documents with an LLM is a reasonable way to seed the set, provided a human reviews every item. Unreviewed synthetic questions tend to be phrased in the document's own vocabulary, which flatters your retriever badly — real users do not use your internal terminology.

When do the numbers actually say fine-tune?

Plot faithfulness against retrieval recall and the diagnosis falls out of the quadrant an item lands in:

Reading the two scores togetherLow recall · high faithfulnessThe model is honest about thin context.It answers partially or refuses.Fix: chunking, embeddings, kFine-tuning changes nothing hereHigh recall · high faithfulnessThe pipeline works. What is left istone, format and house style.Fine-tuning is now justifiedSmall LoRA on style, not knowledgeLow recall · low faithfulnessNothing useful was retrieved and themodel filled the gap by guessing.Fix retrieval, then re-measureThe most commonly misdiagnosed cellHigh recall · low faithfulnessThe context was there and the modeltalked over it.Fix: prompt, reranker, then tuneGrounding tuning is a real last resortretrieval recall@k →faithfulness ↑
Reading retrieval recall and faithfulness together turns a RAG evaluation run into a diagnosis: only the top-right quadrant makes fine-tuning the right next move.

The honest summary is that fine-tuning is usually the wrong answer to a knowledge problem and the right answer to a behaviour problem. Facts change; a fine-tune freezes them into weights you must retrain to update, while a retriever picks up a corrected document the moment it is indexed. But if your evals show the model reliably grounded and still producing the wrong shape — ignoring your citation format, missing required disclaimers, drifting from house tone, or burning tokens where a smaller tuned model would do — that is exactly what fine-tuning fixes well. RAG vs fine-tuning: which to choose covers the trade-off in general terms, and fine-tuning an LLM: when and how covers the mechanics once the numbers point that way.

Work up the ladder, not straight to the top

The RAG fix ladder, cheapest firstPrompt andinstructionshoursChunk sizeand khoursBetterembeddingsdaysAdd arerankerdaysFine-tune theembeddingsweeksFine-tune theLLMweeks + GPUwhere teams jump without measuringincreasing cost, lead time and lock-in →
The RAG fix ladder: each rung costs more than the one below it, and RAG evaluation metrics tell you which rung you are actually standing on.

Re-run the eval after every rung. Two of these changes commonly make things worse — raising k often lifts recall while dropping precision and faithfulness together, and a stronger embedding model tuned on general web text can underperform a weaker one on heavy domain jargon. Without a before-and-after number you will not notice either.

What does walking the ladder look like in practice?

A worked example makes the diagnosis concrete. Take an internal policy assistant answering staff questions over about 4,000 documents, reported by its team as "roughly two thirds right" with no further detail. The first eval run over a 150-item golden set produced this:

Changerecall@5precision@5faithfulnessWhat it revealed
Baseline (1,000-token chunks, k=5)0.610.440.93Low recall, high faithfulness — the bottom-left of the matrix. The model was behaving well with bad context.
k raised to 100.740.290.81Recall up, precision and faithfulness down. More near-miss context distracted the model — a net loss.
Chunks 1,000 → 400 tokens, 15% overlap, k back to 50.870.580.91The real fix. Policy answers lived in short clauses that large chunks had diluted.
Cross-encoder reranker over top 200.890.790.94Precision jumped; ordering, not coverage, was the remaining retrieval issue.

Two things are worth pulling out. The k=10 experiment made the product worse while improving the metric the team had been watching — visible only because precision and faithfulness were measured alongside recall. And the change that actually worked was chunk size, which costs an afternoon and a reindex, not the fine-tuning run that had been proposed at the start.

After the reranker, the residual failures were no longer factual. The assistant was answering correctly but omitting the clause reference that compliance required, and writing three paragraphs where staff wanted two sentences. High recall, high faithfulness, wrong shape: the top-right quadrant, and the first point in the project where fine-tuning was the rational move. A small LoRA on 800 curated question-answer pairs fixed the format; no further attempt was made to teach the model policy facts, which stayed where they belong — in the index, updatable without retraining.

How do you gate this in CI?

An eval you run manually decays within a month. Treat the golden set as a test suite and fail the build on regression:

import json, pytest

THRESHOLDS = {
    "recall@5":         0.85,
    "faithfulness":     0.90,
    "answer_relevancy": 0.80,
    "refusal_accuracy": 0.95,   # the unanswerable slice
}

@pytest.mark.parametrize("metric,floor", THRESHOLDS.items())
def test_no_regression(metric, floor):
    scores = json.load(open("eval_report.json"))
    assert scores[metric] >= floor, (
        f"{metric} = {scores[metric]:.3f}, floor {floor}"
    )

def test_no_slice_collapses():
    """A healthy mean can hide one query type falling apart."""
    by_slice = json.load(open("eval_report.json"))["by_slice"]
    weak = {s: v for s, v in by_slice.items() if v["recall@5"] < 0.70}
    assert not weak, f"slices below floor: {weak}"

That second test earns its place. Averages hide structure: a pipeline can hold a healthy 0.88 mean recall while multi-hop questions collapse to 0.40, because single-fact lookups dominate the set. Always gate per slice as well as overall. Pin your judge model, your embedding model version and your golden set with the code — an eval whose baseline moves on its own is worse than no eval, and versioning them is the same discipline as prompt versioning and A/B testing.

Pitfalls worth naming

  1. Optimising the mean while a slice dies. Report per query type, always.
  2. Letting the golden set leak into the corpus. If your reference answers were generated from the same chunks you retrieve, scores will be flattering and meaningless.
  3. Trusting an uncalibrated judge. Check agreement against human labels before the judge gates anything.
  4. Measuring only the happy path. Without unanswerable questions you cannot detect confabulation — the failure mode users punish hardest. See reducing LLM hallucinations.
  5. Running evals offline only. Production distribution drifts away from your golden set; sample live traffic monthly and fold new failures back in, the way LLMOps practice treats any production feedback loop.

Where to start this week

Label fifty questions with the document IDs that answer them, compute recall@5, and you will know within a day whether your problem is retrieval or generation. That single number reroutes more RAG projects than any model upgrade — and it is the cheapest of all the RAG evaluation metrics to produce. Only once retrieval is solid, and faithfulness is still high while the output shape is wrong, does fine-tuning become the rational next spend.

If you would rather have an evaluation harness and retrieval pipeline built against your own corpus than assembled from blog posts, my consulting services cover exactly this — designing, measuring and operating RAG systems that hold up in production.

Frequently Asked Questions

They split into retrieval metrics — recall@k, precision@k, MRR, nDCG@k and context recall — which score whether the right passage was found, and generation metrics — faithfulness, answer relevancy and answer correctness — which score what the model produced from that passage.

Because a RAG pipeline has at least two independent failure modes and a single end-to-end score cannot distinguish them. Separate scores tell you whether to fix chunking and embeddings or to work on the prompt and model, which are completely different pieces of work.

recall@k. It sets the hard ceiling on everything downstream — if the answering passage is not in the top k, no model can produce a correct answer from it.

Most teams target 0.85 or better at k=5 on their golden set before worrying about generation quality. The right floor depends on the cost of a wrong answer in your domain, so set it from that rather than from a published benchmark.

The fraction of claims in the generated answer that are actually supported by the retrieved context. It is the direct measure of hallucination — a faithfulness of 0.72 means roughly a quarter of the answer's assertions were not grounded in anything retrieved.

Context recall asks whether everything needed to answer the question was retrieved. Context precision asks whether the relevant passages ranked above the irrelevant ones. Low recall is a retrieval coverage problem; low precision is usually a ranking problem a reranker can fix.

When your evals show high retrieval recall and high faithfulness but the output shape is still wrong — ignored citation formats, missing disclaimers, off-brand tone, or a need to run a smaller cheaper model. Fine-tuning fixes behaviour, not missing knowledge.

No. If the passage containing the answer was never retrieved, the model never saw the information, and no amount of training changes that. Fix recall first, then re-measure before considering a fine-tune.

A hundred to two hundred carefully labelled items beats a thousand scraped ones. What matters more than size is that each item carries document-level labels and that the set is stratified across single-fact, multi-hop, comparison and time-sensitive questions.

They are the only way to measure whether the system refuses gracefully or confabulates. Aim for 10 to 15% of the set, and watch that slice closely — it regresses most often when someone raises k or loosens a similarity threshold.

It is usable once calibrated. Judges show position bias, self-preference bias and length bias, so hand-label 50 to 100 examples and check agreement before trusting the judge, randomise candidate order, and pin the judge model version so upstream upgrades do not silently move your baseline.

Yes. RAGAS provides faithfulness, answer relevancy, context precision and context recall out of the box over a dataset of questions, answers, contexts and ground truths. Keep the deterministic retrieval metrics in your own code so you can slice them by query type.

Gate on fixed thresholds per metric rather than on exact scores, pin the judge and embedding model versions, version the golden set alongside the code, and set temperature to zero where the API allows it. Assert per-slice floors as well as the overall mean.

More passages usually lifts recall while lowering precision, so the prompt fills with near-miss context that distracts the model and drags faithfulness down. It also raises token cost. Measure both metrics before and after any change to k.

On every change to the prompt, chunking, embedding model, retriever or generation model — that is what the CI gate is for — plus a monthly refresh where you sample real production traffic and fold new failure cases back into the golden set as distribution drifts.