CoderBlog
AI Tech

RAG Evaluation in 2026: Stop Guessing, Start Measuring

Six months into production, RAG failure is silent. Here is what to measure, how to measure it, and the LLM-as-judge tradeoffs nobody tells you about in 2026.

Most RAG systems I have seen in production look roughly the same on a demo. You chunk a few hundred PDFs, embed them, store them in a vector DB, wire up a retriever, and ask a question. The answer comes back, the LLM has done some sort of synthesis, the demo works. Everybody nods.

Six months later, the customer success team starts getting tickets. "The chatbot gave me a wrong answer." "It said the refund policy was 60 days but it is 30." "It cited a section that does not exist." The engineering team looks at the logs. There are no errors. Latency is fine. Token spend is fine. Nothing has broken. The system has just slowly drifted into mediocrity, and there is no number you can point to that says how bad it is or how much worse it got last week.

That is the problem RAG evaluation is supposed to solve. And in 2026, after a year of frameworks maturing and a lot of production pain, I think we finally know what actually works and what is mostly theater. This post is the field guide I wish I had when I started.

Why RAG is uniquely hard to evaluate

A regular API endpoint returns either the right thing or it does not. A RAG pipeline returns a probabilistic blob of text, generated by a model you do not fully control, conditioned on a document set that itself keeps changing. There are three places it can go wrong, and you need to measure all of them:

  1. The retriever pulled the wrong documents (or the right ones, but in the wrong order).
  2. The LLM hallucinated facts that were not in the retrieved context.
  3. The LLM ignored the retrieved context entirely and answered from its own training data.

The classic "eyeball" test catches some of these. A human reads 20 answers and goes "yeah, looks good." That is fine for a demo. It is catastrophic for production, because the failure modes are sparse and non-uniform. Last Tuesday your retrieval was bad because someone added a new document with the same name as an old one. Yesterday it was bad because your embedding model was retrained. Today it is fine again. You need numbers, not vibes.

The good news: the tooling has caught up. The bad news: most of it has sharp edges that the docs do not warn you about. Let me walk through the layers, bottom up.

Layer 1: Retrieval quality

This is the part you control the most, and the part most teams under-invest in measuring. Retrieval has a small set of well-defined metrics that predate LLMs by decades. They are not glamorous, but they are honest.

The two I lean on are context recall and context precision. Recall answers: "Of the documents that contain the ground-truth answer, did the retriever actually pull them?" Precision answers: "Of the documents the retriever pulled, how many of them were actually useful?" The F1 between them is what you usually want to optimize.

The math is not complicated. You compute it over a labeled set of (question, relevant_doc_ids) pairs. The annoying part is getting those labels. There are three ways, and you should plan to use all of them.

Human labels. Slow, expensive, and you only get maybe 200 to 500 of them per evaluation cycle. They are still the gold standard, because humans notice things the LLM-judge misses. I have a 300-question set for our support docs that took an intern two weeks to build. It has caught regressions the synthetic set never saw.

Synthetic labels. You use an LLM to generate questions from chunks of your corpus, then assume the source chunk is the ground-truth relevant document. Ragas, DeepEval, and most modern frameworks do this out of the box. It is fast, cheap, and biased. The bias is important: a synthetic question is generated from a chunk, so the chunk is by construction relevant, which means the "irrelevant" documents in the eval set are not really hard negatives. They are easy negatives, the random chunks that any retriever would correctly deprioritize. Your recall numbers will look great, and they will be wrong about the actual hard cases. Use synthetic labels for breadth, not for proof.

Mining from logs. If your RAG system has been running in production for a while, you have a goldmine: user queries, the documents that got retrieved, and (sometimes) downstream signals like thumbs up/down, copy-to-clipboard, or "was this helpful." You can mine these for naturally hard cases — the queries where the user clicked no, or where the LLM answer was very short. These are the cases where your retrieval is most likely to have failed, and they are free.

In our setup, the breakdown ends up being roughly 60 percent synthetic, 30 percent mined, 10 percent human. That is a healthy mix. If you are at 100 percent synthetic, you are fooling yourself.

A subtle but important point: metric stability across embedding model changes. When we switched from text-embedding-3-small to text-embedding-3-large last quarter, our context recall went up by 4 points. That was real. But our context precision went down by 2 points, because the larger embedding model started pulling in semantically related but off-topic chunks. The aggregate F1 barely moved. The numbers, taken individually, were misleading. Always look at precision and recall together, and never trust a single number.

Layer 2: Answer quality

This is where things get controversial. The community has settled on LLM-as-judge: you take the question, the retrieved context, the generated answer, and a reference answer (when you have one), and you ask a strong LLM to score the generated answer on a 1-to-5 scale across a few dimensions.

The dimensions I use, and the rubric prompts, are below. The key insight is that "answer quality" is not one thing. It is at least three:

  • Faithfulness — does every claim in the answer appear in the retrieved context? This is your hallucination detector.
  • Answer relevance — does the answer actually address the question, or is the LLM talking about something adjacent?
  • Completeness — did the answer cover all parts of a multi-part question?

Here is a minimal LLM-as-judge prompt that I have found works well in production. Strip the markdown if you are not using one, but the structure matters:

You are evaluating a RAG system's answer to a user question.

QUESTION:
{question}

RETRIEVED CONTEXT:
{context}

GENERATED ANSWER:
{answer}

REFERENCE ANSWER (if available):
{reference}

Score the generated answer on three dimensions, each 1-5:

1. FAITHFULNESS: Are all factual claims in the answer
   supported by the retrieved context? Score 5 if every
   claim is directly traceable. Score 1 if the answer
   contains claims that contradict the context or have
   no basis in it.

2. RELEVANCE: Does the answer directly address the
   question, or does it go off on tangents? Score 5 if
   every sentence is on-topic. Score 1 if the answer
   is mostly about something else.

3. COMPLETENESS: Does the answer cover all parts of
   the question? For multi-part questions, every part
   should be addressed. Score 5 if nothing is missing.
   Score 1 if major parts of the question are ignored.

Output JSON only:
{
  "faithfulness": ,
  "relevance": ,
  "completeness": ,
  "reasoning": ""
}

Three things the prompt enforces that the defaults do not:

  1. It explicitly asks for JSON output, which makes downstream parsing robust. Most "JSON mode" features in 2026 still occasionally leak markdown fences or trailing commentary.
  2. It anchors each dimension to a specific, observable property. "Faithfulness" is not a vibe; it is "every claim traceable to the context."
  3. It asks for a reasoning field. The reasoning is more useful than the score, honestly, because it tells you why the judge scored what it did. I almost always sample 10 to 20 reasoning traces per evaluation cycle and read them by hand.

Now the sharp edges. LLM-as-judge bias is real. In my experience, Claude and GPT-5 have different biases. Claude tends to be stricter on faithfulness — it will flag a paraphrase as "not directly supported" even when the meaning is preserved. GPT-5 is more lenient on relevance but stricter on completeness. If you switch judges between experiments, you can see 5 to 10 percent swings in scores that have nothing to do with your retrieval or prompt changes. Pick a judge and stick with it for a quarter. Document the choice. Review it annually.

Cost matters. Running a 200-question eval set through a frontier LLM as a judge costs about $2 to $5 per run, depending on the model and context length. That is not nothing, especially if you are running it on every PR. We cache judge outputs by (question, context_hash, answer_hash) and we only re-judge when one of those three changes. This brought our eval cost from $400 a month to about $60.

Inter-rater agreement is lower than you think. A/B testing two answer phrasings on the same judge, on the same question, on two consecutive days, gives different scores about 8 percent of the time. That is not because the judge is broken. It is because the questions are genuinely ambiguous, and the judge's temperature is not zero. When you see a 2-point movement in your eval metrics, do not panic. When you see a 5-point movement, dig in. When you see a 10-point movement, something real happened.

Layer 3: System quality

Retrieval and answer quality are about whether the system is right. System quality is about whether the system is operationally healthy. You need both.

The numbers I track on a dashboard, in order of how much they correlate with customer pain:

  • p50 and p95 retrieval latency. If retrieval is slow, answer latency is slow. End of story.
  • p95 answer latency. Customers will wait 3 seconds for a chat answer. They will not wait 8.
  • Cost per query. Token spend times retrieval calls. If this drifts up by 20 percent, either your chunks are too big, your top-k is too high, or someone changed a model without telling you.
  • Retrieval-empty rate. The percent of queries where the retriever returned zero documents, or documents with similarity below a threshold. A spike here means your corpus has a gap, or your embedding model is out of sync with your chunking strategy.
  • Refusal rate. The percent of queries where the LLM says "I don't know" or similar. A healthy RAG system refuses on a small slice of questions, and that slice should be stable. If your refusal rate jumps from 4 percent to 12 percent overnight, either your retrieval broke or the questions coming in shifted.

A pattern I have seen over and over: the system-level metrics catch problems before the answer-quality metrics do. Latency spikes, cost spikes, refusal spikes — these all show up at the operations layer within hours. Answer-quality regressions from prompt or model changes take days to surface in user feedback, and longer to localize.

Layer 4: The frameworks

You have options here, and the choice matters more than people admit. I have used four in production. Honest summary:

Ragas is the most established. Good metric coverage, decent LLM-as-judge defaults, decent synthetic data generation. The downside is that the API is opinionated in ways that are hard to customize, and the docs assume you are running a single-tenant vanilla setup. We had to fork it to add custom metrics for our domain. The fork is now 600 lines of glue code and I regret it sometimes.

DeepEval is more developer-friendly. Pytest-style test cases, a clean metric API, and the LLM-as-judge implementation is solid. The community is smaller than Ragas, but the code is more readable. We use DeepEval for unit tests in CI and Ragas for offline batch evaluation. It is not a great answer, but it is what works.

Phoenix (Arize) is what you reach for when you care about observability, not just batch evaluation. Trace every span, see retrieval hits and misses per query, replay bad answers with the original context. The UI is genuinely good. The pricing is genuinely bad once you exceed the free tier. We use Phoenix in development and a self-hosted OpenTelemetry collector in production because the bill was on track to be $4K a month.

Langfuse sits in the middle. It is a hosted observability tool with a generous free tier and a self-host option. The eval features are improving, and the team is responsive. If you are starting from scratch in 2026, I would pick Langfuse as the default and only graduate to Phoenix or a custom setup if you outgrow it.

Braintrust is the one to watch. Their eval primitives are the cleanest I have seen, and the LLM-as-judge prompts are noticeably better than what I wrote myself. It is also the most expensive. I am waiting for their pricing to come down before I move our production workload.

Pick one. The second-most-important thing is consistency, after correctness. Mixing two frameworks will silently double-count some metrics and under-count others.

Layer 5: Continuous evaluation in CI

This is the part nobody does well, and it is the most important thing in this entire post.

A batch evaluation run on a Friday afternoon tells you your system was good on Friday. It does not tell you whether the change you shipped Tuesday made it worse. You need RAG evaluation in your CI pipeline, on every PR that touches the retriever, the chunker, the prompt, or the model.

Here is what ours looks like, in pseudo-code. The actual implementation is in Python, but the shape is the same:

def evaluate_rag_change(change):
    eval_set = load_eval_set(mined=True, human=True)
    # Synthetic set is too easy, we only use it weekly
    for question in eval_set:
        context = retrieve(question, top_k=10)
        answer = generate(question, context)
        yield {
            "question": question,
            "context_precision": precision_at_k(context, question.relevant_docs, k=10),
            "context_recall": recall_at_k(context, question.relevant_docs, k=10),
            "faithfulness": judge(question, context, answer).faithfulness,
            "relevance": judge(question, context, answer).relevance,
            "latency_ms": last_query_latency(),
        }

def check_regression(report):
    baseline = load_baseline_metrics()
    for metric in ["context_precision", "context_recall", "faithfulness", "relevance"]:
        delta = report.aggregate(metric) - baseline.aggregate(metric)
        if delta < -0.03:  # 3 percentage points
            raise RegressionError(f"{metric} dropped by {abs(delta):.2%}")

    if report.p95_latency_ms > baseline.p95_latency_ms * 1.20:
        raise RegressionError("p95 latency regressed by more than 20%")

A few decisions worth calling out:

  • The eval set is versioned and reviewed. We do not let eval set questions drift. If a question becomes stale because the source document changed, we either update the question or retire it. The set is a measurement instrument, and like any instrument, it has to be calibrated.
  • The thresholds are explicit, not magic. A 3 percentage point drop on context recall is a regression. A 1.5 percentage point drop is noise. We document the thresholds in the repo so anyone touching the pipeline knows what they are agreeing to.
  • The latency budget is checked per query, not just on average. A regression where p50 is fine but p95 doubles is real, and aggregate latency would not catch it.
  • The CI run is capped at 10 minutes. If a PR author has to wait 30 minutes for eval, they will skip the eval. The eval set is sized so the test runs in under 10 minutes on a single worker. When the set grows, we sample.

One more thing. Cache aggressively, version explicitly. I have seen teams lose a week of debugging because they were comparing metrics from judge-v1 against metrics from judge-v2, with the same questions, and getting confused by a 6 percent delta that had nothing to do with the RAG system. Version the judge prompt, the chunker, the embedder, the retriever, and the generator. Tag every evaluation run with all five. The storage cost is nothing. The debugging cost of not doing this is enormous.

Layer 6: Online evaluation, the hard part

CI catches changes before they ship. It does not catch drift. In production, your user base shifts, your document corpus shifts, your embedding model degrades as the world changes around it. CI is a static test against a static instrument. Real production RAG is a moving target.

The thing I have not figured out is online evaluation at scale. Here is what I have tried, in order of how much I trust them:

  1. Implicit feedback. Thumbs up/down, "was this helpful," copy-to-clipboard. Easy to instrument, biased toward users who bother to click, and the negative class is sparse.
  2. Spot-check sampling. Take 1 percent of production traffic, run it through the offline eval pipeline, alert on regressions. Cheap, slow to alert (you need a few hundred samples to see anything), but honest.
  3. RAG triad in real time. The "triad" is context relevance, groundedness, and answer relevance, all measured by an LLM judge, on every query. This is what every vendor blog post tells you to do. It is also the most expensive, and in my experience the judge scores in production do not correlate as tightly with user satisfaction as you would hope, because the questions users actually ask are messier than the questions in your eval set.
  4. Holdout evaluation. A small fraction of production traffic gets routed to a "challenger" version of the system, and you compare the two on a downstream metric. The cleanest of all the methods, and the slowest. We run a permanent 5 percent challenger for our high-stakes product pages, and it is the only number I really trust.

The honest truth is that I do not think anyone has cracked this. The closest I have seen is the Braintrust team's writeups, and even they admit the gap. For now, my recommendation is to do CI evaluation religiously, sample 1 percent of production traffic for offline eval, and use holdouts for the parts of the product where the cost of a wrong answer is high.

The 2026 RAG eval checklist

If you only read this section, here is what I would actually do, in order:

  1. Build a labeled set of 200 to 500 questions. Mix human labels, mined-from-logs, and synthetic.
  2. Track context precision, context recall, faithfulness, and answer relevance from day one. Pick one judge, document it, stick with it.
  3. Put the eval in CI with explicit regression thresholds. Block merges on a 3 percentage point drop on any metric.
  4. Add system metrics — p95 latency, cost per query, retrieval-empty rate, refusal rate — to a dashboard, with alerts.
  5. Sample 1 percent of production traffic for offline eval. Re-run weekly, diff against the last run, investigate any metric movement greater than 5 percent.
  6. Run a 5 percent holdout for the parts of the product that matter most. Treat that holdout's metric as the source of truth.
  7. Re-label and re-evaluate when you change any model, embedder, chunker, or retriever. Version everything.

If you do all seven of those, you will be ahead of 90 percent of the RAG systems I have seen. Most teams stop at step 1 or 2 and call it done. The teams that ship a real production RAG product do all of it, and they iterate on the eval set as much as they iterate on the system.

A few things I want to be honest about

I have not solved online evaluation well. The judge scores in production do not match user satisfaction as tightly as I would like, and I do not have a great answer for why.

Synthetic data is necessary but dangerously easy to over-trust. If you only ever evaluate on synthetic questions, you will ship a system that is great at questions you generated and bad at questions your users actually ask.

The LLM-as-judge tradeoffs are real, but they are also the best tool we have. Pretending we can do without it is a worse decision than using it carefully.

And finally: the biggest improvement I have made to our RAG system in the last six months was not a new embedding model, or a smarter chunker, or a fancier retriever. It was spending a week improving the eval set. The eval set is the bottleneck. The system is downstream of it.

If you take one thing from this post, take that. The metric is the moat. The eval set is the metric. The rest is plumbing.

Winson Yau

Engineer, writer, and founder of CoderBlog. Building tools and writing about the craft of software from Hong Kong.

Comments

Discuss the article below. Markdown is supported. Sign in with email or GitHub to leave a comment.