All articles
    AI Engineering

    How to Evaluate a RAG System Before It Ships

    To evaluate a RAG system, measure retrieval and generation separately. Score retrieval with recall@k, precision and MRR against a golden test set of question-and-source pairs. Score generation with faithfulness, answer relevance and context precision. Automate the whole set as a regression gate in CI, and fail the build when faithfulness drops.

    Syed Husnain Haider Bukhari
    12 min read

    Most RAG demos look flawless and most RAG products quietly lie to users. The gap between those two states is evaluation, and it is the step teams skip because a chatbot that answers three questions in a meeting feels like proof. It is not. Knowing how to evaluate a RAG system is what separates a demo you cheer at from a system you can put in front of a client and defend.

    I have shipped retrieval-augmented systems over legal documents, product catalogues and internal knowledge bases, and every one of them passed the eyeball test long before it was safe. This post is the eval process I now run before any RAG build reaches a sign-off. If you are still assembling the pipeline itself, start with how to build a RAG chatbot on your knowledge base and come back here to prove it works.

    Why does RAG need its own evaluation, separate from the LLM?

    Because a RAG system can fail in two completely different places, and a single quality score hides which one broke. A generic LLM eval asks whether the model wrote a good answer. A RAG eval has to ask two questions in sequence: did the retriever fetch the right context, and then did the generator stay faithful to it? Those are independent failure modes with independent fixes.

    Consider what a wrong answer actually tells you. If the retriever never surfaced the relevant chunk, the model was doomed regardless of how good it is, and no prompt tuning saves you. If the retriever did surface the right chunk and the model ignored it or invented a detail, your retriever is fine and your generation step is hallucinating. Collapse both into one number and you will spend a week tuning the wrong half.

    "A single accuracy score on a RAG system tells you the patient is sick. It never tells you which organ is failing."

    This is also why swapping frameworks rarely fixes a bad RAG system. The choice between LangChain and LlamaIndex changes ergonomics, not retrieval quality. Evaluation, not framework selection, is what surfaces the real defect.

    The two halves: retrieval quality versus generation quality

    Split every RAG evaluation into a retrieval stage and a generation stage, and measure each against its own ground truth. Retrieval is an information-retrieval problem with decades of established metrics. Generation is a grounding problem that needs newer, LLM-scored metrics. Treat them as one and you lose all diagnostic power.

    The retrieval stage takes a query and returns a ranked list of chunks. You judge it on whether the chunks that should have been retrieved actually were, and how highly they ranked. The generation stage takes those chunks plus the query and produces an answer. You judge it on whether the answer is supported by the retrieved chunks and whether it actually addresses the question. Keep the two ledgers separate and every regression points at a specific component.

    What retrieval metrics should you actually track?

    Track recall@k first, precision second, and a rank-aware metric like MRR or nDCG third. Recall@k answers the only question that can outright kill an answer: was the correct document anywhere in the top k results the model got to see? If recall@k is low, nothing downstream can recover.

    The retrieval metrics I put on every dashboard, and what each one catches:

    • Recall@k: the fraction of queries where at least one relevant chunk appears in the top k. Low recall means the model never even had a chance. This is the metric to maximise first.
    • Context precision: of the chunks you retrieved, how many were actually relevant. Low precision floods the context window with noise, raises cost, and gives the model room to latch onto the wrong passage.
    • MRR (mean reciprocal rank): rewards putting the first relevant chunk near the top of the list. Matters when you only feed the model the top two or three chunks.
    • nDCG: a graded, position-discounted score for when relevance is not binary and some sources are more authoritative than others.
    • Hit rate at k: the blunt yes-or-no version of recall, useful as a single headline number for non-technical stakeholders.

    The practical move is to tune k with these numbers rather than guessing. Raise k and recall climbs but precision usually falls and cost rises. The right k is the smallest one where recall@k plateaus, because past that point you are paying tokens to feed the model distractions.

    What generation metrics catch hallucinations?

    The generation metric that matters most is faithfulness, also called groundedness: every factual claim in the answer must be supported by the retrieved context. Faithfulness is your direct hallucination detector. An answer can be fluent, confident and completely fabricated, and faithfulness is the score that flags it.

    The mechanics are simple and worth knowing. A judge decomposes the answer into individual claims, then checks each claim against the retrieved chunks. Faithfulness is the proportion of claims that are entailed by the context. An answer with three claims where only two are supported scores 0.67, and that is a number you can gate a release on.

    The three generation metrics I score on every eval run:

    • Faithfulness / groundedness: the share of answer claims supported by retrieved context. This is the anti-hallucination metric. I gate releases on it.
    • Answer relevance: whether the answer actually addresses the user's question rather than drifting into related-but-useless territory. A grounded answer to the wrong question still fails the user.
    • Context recall: whether the retrieved context contained everything needed to produce the ground-truth answer. It links generation quality back to retrieval, so a low score tells you to fix the retriever, not the prompt.

    Faithfulness and answer relevance together catch the two most damaging failure modes: confidently making things up, and confidently answering a question nobody asked. This is exactly the class of failure that turns into an incident once real users arrive, which is why I treat it with the same seriousness as deploying agents to production safely.

    The metrics I track on a RAG system, what each measures, and the failure it catches:

    MetricStageWhat it measuresFailure it catches
    Recall@kRetrievalRelevant chunk present in top k resultsThe answer was doomed before generation
    Context precisionRetrievalShare of retrieved chunks that are relevantNoisy context, wasted tokens, wrong-passage answers
    MRR / nDCGRetrievalHow highly the relevant chunk is rankedRight chunk buried below the cutoff
    FaithfulnessGenerationAnswer claims supported by contextHallucination and fabricated detail
    Answer relevanceGenerationAnswer addresses the actual questionGrounded but off-topic responses
    Context recallBothContext held everything the answer neededRetriever gaps disguised as model errors

    How do you build a golden test set for RAG?

    Before any metric means anything, you need a golden test set: a fixed collection of questions, each paired with the source chunks that should answer it and, ideally, a reference answer. Without ground truth, every metric above is unanchored. The golden set is the single most valuable and most skipped artefact in RAG evaluation.

    How I build a golden test set that is worth trusting:

    1. 1Collect real questions, not invented ones. Pull them from support tickets, search logs or the client's own FAQ. Synthetic questions test a world your users do not live in.
    2. 2For each question, label the source chunks that genuinely answer it. This mapping is what makes recall and precision computable. It is tedious and there is no shortcut.
    3. 3Write or approve a reference answer per question, so generation metrics have something to compare against. A subject-matter expert should sign these off, not the engineer.
    4. 4Cover the hard cases deliberately: multi-hop questions needing two sources, questions the corpus cannot answer (the correct response is 'I don't know'), and near-duplicate topics that tempt the retriever into the wrong chunk.
    5. 5Aim for a few hundred items across those categories. Fifty is enough to catch gross regressions; a few hundred is enough to trust a sign-off.
    6. 6Version the set and freeze it. When the corpus changes, update deliberately and record it, so a score change reflects the system, not a moved goalpost.

    The unanswerable questions are the ones people forget, and they are where hallucination hides. A RAG system that never says 'I don't know' is not confident, it is dangerous, and only a test set that includes questions with no valid answer will ever expose it.

    LLM-as-judge and where it quietly lies to you

    You cannot hand-grade faithfulness across hundreds of questions on every commit, so you use a strong model as an automated judge. LLM-as-judge is how metrics like faithfulness and answer relevance get computed at scale: the judge model reads the question, context and answer, and returns a structured verdict. It works, and it also introduces its own biases you have to manage.

    The LLM-as-judge failure modes I have hit in production:

    • Self-preference: a judge tends to rate answers from its own model family more highly. Where you can, judge with a different model than the one that generated the answer.
    • Position and verbosity bias: judges over-reward longer, more assertive answers and are swayed by option ordering in pairwise comparisons. Randomise order and ask for claim-level verdicts, not a single vibe score.
    • Drift across model versions: when the judge model is silently upgraded by the vendor, your scores shift with no code change. Pin the judge model version and treat an upgrade as a re-baselining event.
    • Silent miscalibration: a judge can be confidently wrong. The only defence is checking it against humans.

    The discipline that keeps LLM-as-judge honest is calibration. Take a sample of a hundred items, grade them by hand, and measure how well the judge agrees with your human labels. If agreement is high, trust the judge on the full set. If it is low, fix the judge prompt before you trust a single automated number. A judge you have never calibrated is a random number generator with good manners.

    Ask the judge for structured output with a per-claim breakdown and a short rationale, not a bare score. The rationale is what lets you audit a disagreement, and structured verdicts are what let you compute faithfulness as a real proportion instead of guessing at a five-point scale.

    from typing import Literal
    from pydantic import BaseModel, Field
    
    # The schema a faithfulness judge must return per answer.
    class ClaimVerdict(BaseModel):
        claim: str
        supported: bool
        evidence: str = Field(description="Quote from context, or empty if unsupported.")
    
    class FaithfulnessResult(BaseModel):
        claims: list[ClaimVerdict]
        verdict_rationale: str
    
        @property
        def faithfulness(self) -> float:
            if not self.claims:
                return 0.0
            supported = sum(c.supported for c in self.claims)
            return supported / len(self.claims)
    
    
    def evaluate_case(case, rag_pipeline, judge) -> dict:
        result = rag_pipeline(case.question)
        retrieved_ids = {c.id for c in result.contexts}
        gold_ids = set(case.gold_chunk_ids)
    
        recall_at_k = len(retrieved_ids & gold_ids) / len(gold_ids)
        precision = len(retrieved_ids & gold_ids) / max(len(retrieved_ids), 1)
        faith = judge.score_faithfulness(
            question=case.question,
            answer=result.answer,
            contexts=result.contexts,
        )  # returns a FaithfulnessResult
    
        return {
            "id": case.id,
            "recall_at_k": recall_at_k,
            "context_precision": precision,
            "faithfulness": faith.faithfulness,
        }
    
    
    # CI gate: fail the build if the suite regresses on the metric that matters.
    MIN_FAITHFULNESS = 0.90
    MIN_RECALL_AT_K = 0.85
    
    def assert_release_ready(scores: list[dict]) -> None:
        n = len(scores)
        mean_faith = sum(s["faithfulness"] for s in scores) / n
        mean_recall = sum(s["recall_at_k"] for s in scores) / n
        assert mean_faith >= MIN_FAITHFULNESS, f"Faithfulness {mean_faith:.3f} below gate"
        assert mean_recall >= MIN_RECALL_AT_K, f"Recall@k {mean_recall:.3f} below gate"

    Regression testing RAG in CI

    Run the golden set as an automated test suite on every change that can move quality: prompt edits, chunk-size changes, embedding-model swaps, retriever settings and reranker tweaks. RAG systems have an enormous surface of silent regressions, because a change that improves one query class often degrades another, and only a fixed suite catches the trade.

    The pattern is the same one you already use for code. Compute mean faithfulness, recall@k and answer relevance across the golden set, compare against a stored baseline, and fail the build if any headline metric drops beyond a tolerance. This turns 'the chatbot feels worse since Tuesday' into a red pipeline on the exact commit that caused it. The same instinct for guardrails and rollback that I described in nine lessons from running agents in production applies directly here.

    How I wire RAG evals into a pipeline that actually gets run:

    • Keep a small, fast smoke set (twenty to fifty items) that runs on every pull request in a couple of minutes, plus the full set nightly or before release.
    • Store baselines as versioned artefacts so a regression is measured against the last known-good build, not against nothing.
    • Cache LLM-judge calls keyed on question, answer and context hash, so a re-run over unchanged cases costs nothing.
    • Report per-category breakdowns, so you can see that recall held overall but collapsed on multi-hop questions specifically.
    • Alert on the unanswerable-question subset separately; a rising rate of confident answers there is your earliest hallucination warning.

    How this maps to a POC sign-off

    When a client asks whether the RAG proof-of-concept is ready to ship, 'it seemed good in the demo' is not an answer you can stand behind. The eval suite is. A sign-off becomes a table of numbers against thresholds you agreed up front, and the conversation shifts from opinion to evidence.

    The sign-off checklist I hand a client at the end of a RAG POC:

    1. 1Retrieval clears the bar: recall@k at or above the agreed floor, and context precision high enough that the model is not drowning in noise.
    2. 2Generation is grounded: mean faithfulness at or above the threshold, with the low-scoring cases individually reviewed rather than averaged away.
    3. 3Answer relevance holds across every question category in the golden set, not just the easy ones.
    4. 4The unanswerable set behaves: the system abstains instead of inventing, and that abstention rate is measured.
    5. 5The whole suite runs in CI, so quality after handover is a monitored property, not a hope.
    6. 6The judge is calibrated against human labels, and the calibration number is in the report.

    That checklist is deliberately boring, and that is the point. Boring, numeric and repeatable is what lets you charge for a system rather than a demo, and it is how I run every retrieval build inside my AI engineering services. For B2B SaaS teams shipping a knowledge assistant to their own customers, this eval discipline is the difference between a feature and a liability.

    Knowing how to evaluate a RAG system is not a phase you finish, it is the harness you keep. The corpus grows, the models change under you, and the only thing that tells you whether last week's edit helped or hurt is a golden set, two ledgers of metrics, and a CI gate that is willing to say no.

    Key takeaways

    • Evaluate retrieval and generation separately; one blended accuracy score hides which half of the RAG system is failing.
    • Retrieval quality is recall@k, context precision and a rank-aware metric like MRR or nDCG; maximise recall first.
    • Faithfulness is your hallucination detector: the share of answer claims supported by the retrieved context.
    • A versioned golden test set of question-to-source pairs is the prerequisite for every meaningful RAG metric.
    • LLM-as-judge scales grading but carries self-preference, verbosity and drift biases, so calibrate it against human labels.
    • Run the golden set as a CI regression gate that fails the build when faithfulness or recall drops below threshold.

    Frequently asked questions

    What is the difference between RAG evaluation and LLM evaluation?
    LLM evaluation judges whether a model wrote a good answer. RAG evaluation adds a retrieval stage, so it must first check whether the right context was fetched, then whether the answer stayed faithful to it. Those are independent failure modes, and measuring them separately is what makes a RAG eval diagnostic rather than just a pass or fail verdict.
    What is faithfulness in RAG, and how is it measured?
    Faithfulness, or groundedness, is the share of factual claims in an answer that are supported by the retrieved context. A judge decomposes the answer into individual claims and checks each against the context. If two of three claims are supported, faithfulness is 0.67. It is the most direct metric for catching hallucinations in a RAG system.
    What are the most important retrieval metrics for RAG?
    Recall@k comes first: it measures whether a relevant chunk appears in the top k results, and if it does not, no downstream tuning can produce a correct answer. Then context precision measures how much of the retrieved context is actually relevant, and MRR or nDCG measure how highly the correct chunk is ranked within the returned list.
    How big should a RAG golden test set be?
    Around fifty items is enough to catch gross regressions in a smoke test, while a few hundred is enough to trust a client sign-off. What matters more than raw count is coverage: include multi-hop questions, near-duplicate topics, and unanswerable questions where the correct behaviour is to abstain rather than invent an answer.
    Can I trust an LLM as a judge for RAG evaluation?
    Only after you calibrate it. LLM judges carry self-preference bias, over-reward long assertive answers, and drift when the vendor upgrades the model. Grade a sample of a hundred cases by hand, measure agreement with the judge, and pin the judge's model version. A judge you have never checked against human labels is not a metric you can defend.
    How do I stop my RAG system from hallucinating?
    Measure faithfulness on every release and gate on it, so an ungrounded change fails the build rather than reaching users. Include unanswerable questions in your test set to confirm the system abstains instead of inventing. Hallucination is usually a symptom of either a retriever miss or a generator ignoring context, and separate metrics tell you which.
    How do I put RAG evaluation into CI?
    Run a small smoke set on every pull request and the full golden set nightly or before release. Compute mean faithfulness, recall@k and answer relevance, compare against a stored baseline, and fail the build when any headline metric drops beyond tolerance. Cache judge calls by content hash so unchanged cases cost nothing on re-runs.
    What retrieval and generation scores are good enough to ship?
    There is no universal number, so agree thresholds with stakeholders before the build. As a working default I aim for recall@k above roughly 0.85 and mean faithfulness above 0.90, with every low-faithfulness case reviewed individually rather than hidden in an average. The right bar depends on how costly a wrong answer is in your domain.

    Sources

    Tags:
    RAGEvaluationAI EngineeringTestingLLM
    HB

    Written by Syed Husnain Haider Bukhari

    AI engineer, data scientist, and founder of Revolutionary Technologies LLC. Ships production AI agents, automations, and data platforms for teams in the US, UK, and UAE — including AgentFlow, AI Walay, and ProLeads.

    Get in touch →

    Related pages

    Want this built instead of researched?

    I build these systems for teams in the US, UK, and UAE. Book a free 30-minute consultation and you get a one-page plan and a fixed-scope quote within 48 hours — or message me directly, whichever is faster for you.

    Prefer a form? Send a project brief →

    Let's Create a Revolution