TL;DR
Production RAG evaluation must identify where a system failed. Measure retrieval, evidence use, answer correctness and relevance, noise sensitivity, refusal behavior, citation quality, task outcome, safety, and operations separately. Use a versioned test set with realistic slices, calibrate LLM-as-judge metrics against human labels, and gate releases with both aggregate and critical-slice rules. A high average must never hide missing evidence, unsupported claims, unsafe over-response, or cross-tenant retrieval.
Why one RAG score is not enough
A RAG system can fail before or after the model receives context. A single satisfaction score says that something went wrong but cannot tell the team what to change.
| Layer | Core question | Example signals |
|---|---|---|
| Retrieval | Did the system find and rank needed evidence? | recall@k, precision@k, MRR, nDCG, context recall |
| Evidence use | Did the answer use supplied evidence without copying its noise? | answer faithfulness, noise sensitivity, citation support |
| Answer | Is the result correct, relevant, and complete for the request? | answer correctness, answer relevance, required-aspect coverage |
| Answerability | Did the system answer or refuse at the right time? | correct refusal, false refusal, over-response |
| End to end | Did the user receive a useful result and complete the task? | task success, escalation outcome, user correction |
| Safety | Did policy and data boundaries hold? | unauthorized retrieval, sensitive leakage, unsafe action |
| Operations | Is the quality affordable and reliable? | p95 latency, cost, error rate, token and context budget |
The RAGAS paper formalized reference-light evaluation across faithfulness, answer relevance, and context relevance. ARES and RAGChecker show why evaluator calibration and claim-level diagnosis matter. These are useful protocols, not interchangeable definitions or a universal production scorecard.
Record metric identity as part of every result:
metric name + implementation version + judge model/prompt
+ claim or evidence unit + reference source/version
+ aggregation mode + empty-case policy
Two dashboards that display “answer correctness” can still measure different things. One may compare claim sets with precision, recall, or F1; another may blend semantic similarity and factual overlap; a third may use a binary rubric. Compare scores only after those contracts match.
Build a test set around failure slices
A useful test set represents decisions and risks, not just random questions. Each row should record the expected evidence and outcome at the level needed for its slice.
{
"case_id": "returns-policy-014",
"slice": ["policy", "multi_document", "must_cite"],
"question": "Can a damaged final-sale item be returned?",
"expected_evidence": ["policy-v7#damaged-items", "policy-v7#final-sale"],
"reference_answer": "Damaged items follow the defect process even when marked final sale.",
"expected_behavior": "answer",
"forbidden_sources": ["tenant-b/*"]
}
Include:
- common questions weighted by real traffic;
- rare but costly or regulated cases;
- single-hop, multi-hop, ambiguous, and unanswerable questions;
- recent, changed, deleted, and access-denied content;
- typos, short queries, long conversations, and multiple languages;
- known incidents and adversarial retrieval cases.
Keep a frozen core set for trend comparison and a rotating set for new traffic. Version documents, chunking, labels, and evaluator prompts so score changes remain explainable.
Evaluate retrieval before generation
Retrieval labels should answer whether the evidence needed for the question appears in the ranked results.
Context recall checks coverage: did the retriever find all required evidence? Context precision checks noise and ordering: did relevant evidence appear before irrelevant chunks?
| Symptom | Likely retrieval problem | First experiments |
|---|---|---|
| Required source absent | low recall | query rewriting, hybrid search, filters, chunk boundaries |
| Correct source ranked late | low precision/ranking | reranker, scoring features, duplicate removal |
| Correct source denied | authorization/index mismatch | ACL propagation and policy tests |
| Old source returned | freshness failure | version filters, deletion and re-index checks |
| Too many redundant chunks | diversity failure | deduplication and source caps |
Evaluate raw candidates and post-filter results separately. A retriever can have good offline recall while an authorization filter removes the only useful passage, or it can appear accurate because an evaluation corpus accidentally includes inaccessible data.
Evaluate generation against evidence
Answer faithfulness asks whether generated claims are supported by retrieved context. A common method extracts atomic claims and checks each one against evidence:
faithfulness = supported_answer_claims / all_answer_claims
This score does not prove correctness. If a source is wrong, a faithful answer can repeat the error. If the model answers correctly from memory but ignores supplied evidence, it can be correct but unfaithful.
Keep four additional properties separate:
| Property | Comparison boundary | Typical failure |
|---|---|---|
| Answer correctness | response claims versus a trusted reference or executable oracle | a faithful answer repeats an incorrect source |
| Answer relevance | response versus the user's question and required aspects | a correct paragraph answers only half the request or adds distracting material |
| Noise sensitivity | paired clean/noisy contexts or claim attribution to noisy chunks | a distractor introduces a new incorrect claim |
| Refusal correctness | answerability label versus answer/refusal behavior | the system answers without evidence or refuses an answerable query |
Citation support and coverage form another independent layer. A correct answer can cite the wrong passage; a well-supported answer can omit a required fact. Do not reward verbosity: more claims create more opportunities for unsupported details and can hide a concise correct answer inside irrelevant prose.
Test answerability before scoring the answer
Every evaluation case should declare whether the authorized corpus contains sufficient evidence. Only then can the evaluator distinguish:
- a correct answer to an answerable question;
- an incorrect answer despite sufficient evidence;
- a correct refusal when evidence is absent;
- a false refusal despite sufficient evidence;
- an over-response that answers an unanswerable question.
Trust-Align separates over-responsiveness from excessive refusal, while UAEval4RAG builds six categories of unanswerable requests. Their reported results belong to their datasets and protocols; the reusable lesson is to preserve both sides of the answer/refuse confusion matrix instead of reporting one refusal rate.
Calibrate LLM judges
An LLM judge is a measurement instrument and needs calibration.
- Create a human-labeled calibration set with clear positive, negative, and borderline examples.
- Write a rubric that defines the evidence unit and allowed uncertainty.
- Blind the judge to system names and candidate ordering where possible.
- Compare judge labels with human labels by slice, not only overall.
- Review disagreements and update the rubric before changing thresholds.
- Re-run calibration when the judge model or prompt changes.
Use deterministic checks whenever possible:
package main
import (
"errors"
"fmt"
)
type Result struct {
CaseID string
Answerable bool
Answered bool
AnswerCorrect bool
SchemaValid bool
CitationIDs []string
VisibleCitationIDs map[string]bool
UnauthorizedRetrieval int
}
func deterministicChecks(result Result) error {
if result.CaseID == "" || !result.SchemaValid {
return errors.New("invalid_result_contract")
}
if result.UnauthorizedRetrieval != 0 {
return errors.New("unauthorized_retrieval")
}
for _, id := range result.CitationIDs {
if !result.VisibleCitationIDs[id] {
return fmt.Errorf("citation_not_visible:%s", id)
}
}
if !result.Answerable && result.Answered {
return errors.New("over_response")
}
if result.Answerable && !result.Answered {
return errors.New("false_refusal")
}
if result.Answered && !result.AnswerCorrect {
return errors.New("incorrect_answer")
}
return nil
}
func main() {
result := Result{
CaseID: "returns-policy-014",
Answerable: true,
Answered: true,
AnswerCorrect: true,
SchemaValid: true,
CitationIDs: []string{"policy-v7#damaged-items"},
VisibleCitationIDs: map[string]bool{"policy-v7#damaged-items": true},
}
if err := deterministicChecks(result); err != nil {
panic(err)
}
fmt.Println("release_candidate=true")
}
This program does not judge natural-language correctness. It demonstrates the deterministic envelope around a separately calibrated semantic label. Do not ask a judge to infer authorization, source identity, schema validity, or exact string constraints when code can check them directly.
Design release gates
Release gates should combine hard invariants, slice minimums, and change limits.
hard_fail:
unauthorized_retrieval: 0
invalid_citation_ids: 0
critical_over_response: 0
critical_cases_pass_rate: 1.0
slice_minimums:
faithfulness:
policy: 0.95
general: 0.88
context_recall:
multi_hop: 0.85
answer_correctness:
policy: 0.95
regression_limits:
task_success: -0.01
false_refusal_rate: 0.01
p95_latency_ms: 500
cost_per_success: 0.05
These numbers are illustrative, not universal. Set thresholds from labeled data, measurement variance, business impact, and the cost of false acceptance versus false rejection.
Use paired comparisons on the same cases. Report confidence intervals or repeated-run variation when model sampling or judge behavior is nondeterministic. A 0.01 average change is meaningless if run-to-run variation is 0.04.
Online evaluation and observability
Offline tests control inputs and enable release comparison. Online evaluation detects traffic drift, new documents, provider changes, and long-tail failures.
Sample by risk and novelty rather than uniformly:
- always evaluate sensitive or consequential outcomes;
- oversample new query clusters and changed sources;
- inspect low-confidence retrieval and abstentions;
- retain bounded, redacted evidence needed for review;
- route user corrections and incidents into the regression set.
Keep evaluation separate from tracing. Traces explain what happened; evaluations judge whether it met a contract. The agent observability guide explains how to join those planes without collecting unnecessary private content.
A practical implementation sequence
- Define the decisions the RAG system is allowed to make.
- Build a versioned test schema with meaningful traffic, risk, language, and answerability slices.
- Label expected evidence before optimizing generation.
- Establish lexical/vector or hybrid retrieval baselines.
- Add deterministic policy, citation, and schema checks.
- Calibrate correctness, faithfulness, relevance, noise, and refusal judges.
- Set hard gates and per-slice regression limits, including both false refusals and over-responses.
- Run the suite in CI and before index or model changes.
- Sample production outcomes and add incidents to the suite.
- Review metrics quarterly for gaming and drift.
For retrieval tuning after diagnosis, see the hybrid search and reranking guide. For architecture fundamentals, use the RAG guide.
FAQ
Should retrieval and generation use the same test set?
They can share cases, but labels differ. Retrieval needs evidence relevance and ranking labels; generation needs claim support, reference outcomes, and abstention labels. Keep those fields separate.
Is context precision more important than context recall?
Neither dominates universally. Missing required evidence caps answer quality, while noisy context raises cost and can distract the generator. Optimize them under a context budget and by query slice.
Can user thumbs-up data replace evaluation labels?
No. Feedback is useful but affected by selection bias, presentation, user expertise, and delayed discovery of errors. Treat it as one online signal and investigate it with evidence.
Should production answers be sent to a judge synchronously?
Usually not for every low-risk request. Synchronous judging adds cost and latency and can create another dependency. Use deterministic inline checks, risk-based asynchronous sampling, and synchronous review only where the decision justifies it.
What should happen when metrics disagree?
Inspect the layer-specific evidence. High retrieval quality with low faithfulness points to generation. High faithfulness with low correctness points to bad sources or an incomplete reference. Good averages with a failed critical slice should block release.
Is a high refusal rate safer?
No. A system can improve refusal rate by refusing useful answerable requests. Report correct refusals, false refusals, over-responses, and correct answers on a declared answerability set, then weight the error classes by business impact.
Sources
- RAGAS: Automated Evaluation of Retrieval Augmented Generation
- RAGAS evaluation metrics
- ARES: An Automated Evaluation Framework for Retrieval-Augmented Generation Systems
- RAGChecker: A Fine-grained Framework for Diagnosing Retrieval-Augmented Generation
- Trust-Align: Grounded Attributions and Learning to Refuse
- UAEval4RAG: Unanswerability Evaluation for Retrieval Augmented Generation
- OpenTelemetry GenAI semantic conventions