RAG 评估体系:召回、忠实度与自动化指标

构建可落地的 RAG 评估体系:检索侧 Recall@k / MRR / NDCG、生成侧 Faithfulness / Answer Relevance / Context Precision,Ragas 实战代码、LLM-as-Judge 的偏差与校准、评估集构建与在线 A/B 方法。

1. 为什么 RAG 必须单独评估

RAG 系统由检索器与生成器串联,端到端效果差时无法定位问题在哪一段。常见的两种误判:

  • 检索对了但生成幻觉 → 误以为是检索问题,去调 embedding 模型,白费力气。
  • 检索错了但答案蒙对 → 端到端准确率看起来不错,掩盖了检索缺陷。

所以评估必须分层:先测检索,再测生成,最后测端到端。这与传统 ML 里"分开评估特征与模型"的思路一致,可参考 模型评估方法 。

另一个现实动机:RAG 调优的参数极多(chunk size、top-k、reranker 阈值、embedding 模型、混合检索权重)。没有量化指标,调优就是玄学。

2. 评估的四个对象与数据三元组

RAG 评估的最小数据单元是一个四元组:

(question, retrieved_contexts, answer, ground_truth)
字段说明是否必需
question用户问题必需
retrieved_contexts系统检索到的上下文列表必需
answer系统生成的回答必需
ground_truth标准答案可选(有则能测召回与正确性)

依据是否有 ground_truth,指标分成两类:

类别需要标准答案指标
有参考(reference-based)是Context Recall、Answer Correctness、EM/F1
无参考(reference-free)否Faithfulness、Answer Relevance、Context Precision

无参考指标的价值在于可以用真实线上流量评估,不需要人工标注。

3. 检索质量指标

检索是 RAG 的地基。先测检索,再谈生成。

3.1 核心指标

指标含义关注点
Recall@kTop-k 里包含相关文档的比例有没有漏
Precision@kTop-k 里相关文档的占比有没有噪声
MRR第一个相关文档排名的倒数均值排得够不够前
NDCG@k考虑位置权重的排序质量整体排序
Hit Rate@k至少命中一个相关文档的查询比例覆盖率
import numpy as np

def recall_at_k(retrieved: list[str], relevant: set[str], k: int) -> float:
    if not relevant:
        return 0.0
    return len(set(retrieved[:k]) & relevant) / len(relevant)

def precision_at_k(retrieved: list[str], relevant: set[str], k: int) -> float:
    top = retrieved[:k]
    return len(set(top) & relevant) / k if k else 0.0

def mrr(retrieved: list[str], relevant: set[str]) -> float:
    for rank, doc_id in enumerate(retrieved, start=1):
        if doc_id in relevant:
            return 1.0 / rank
    return 0.0

def ndcg_at_k(retrieved: list[str], relevant: set[str], k: int) -> float:
    dcg = sum(
        1.0 / np.log2(rank + 1)
        for rank, doc_id in enumerate(retrieved[:k], start=1)
        if doc_id in relevant
    )
    idcg = sum(1.0 / np.log2(rank + 1) for rank in range(1, min(len(relevant), k) + 1))
    return dcg / idcg if idcg else 0.0

3.2 怎么选 k

kRecallPrecision延迟适用
3低高低简单问答
5中中中通用默认
10高中低中配 Reranker
20+很高低高仅用于召回阶段,必须重排

标准做法:召回阶段用 k=2050 保证 Recall,再用 Reranker 精排到 k=35 保证 Precision。重排序的机制见 /llm-embedding-reranker/。

3.3 检索指标的常见陷阱

  • 用 chunk 级还是文档级标注:用户问"退款政策",相关的是"某个文档的某一节"。若标注是文档级,而系统返回 chunk 级,需先做 chunk→doc 映射再算指标。
  • 相关性非二值:很多场景是分级相关(完全相关 / 部分相关 / 不相关),此时应算 NDCG 而非 Recall。
  • 同一问题多个正确来源:相关文档集要覆盖全部正确来源,否则会低估 Recall。

4. 生成质量指标

4.1 Faithfulness(忠实度)

回答是否完全由检索到的上下文支撑,即有没有幻觉。这是 RAG 最重要的生成指标。

计算方式:把回答拆成若干断言(claim),逐条判断能否从上下文推出。

Faithfulness = 能被上下文支撑的断言数 / 总断言数

示例:

上下文:本产品支持 7 天无理由退货,运费由买家承担。
回答:本产品支持 7 天无理由退货,运费由卖家承担。

断言 1「支持 7 天无理由退货」→ 被支撑 ✓
断言 2「运费由卖家承担」    → 与上下文矛盾 ✗
Faithfulness = 1/2 = 0.5

4.2 Answer Relevance(答案相关性)

回答是否切题。注意它不检查正确性,只检查是否在回答所问。

Answer Relevance = 用回答反向生成问题的相似度均值

做法:让 LLM 根据回答反推若干个"这答案可能在回答的问题",再与原问题算 embedding 相似度取均值。如果回答跑题,反推的问题会与原问题不相似。

4.3 Context Precision / Recall

指标含义依赖
Context Precision检索到的上下文中,真正有用的占比,且按排名加权ground_truth
Context Recall标准答案中的信息,有多少能在上下文中找到ground_truth

Context Recall 的直观算法:把标准答案拆成句子,逐句判断"能否在检索上下文中找到依据"。

def context_recall(contexts: list[str], ground_truth: str, judge) -> float:
    sentences = split_sentences(ground_truth)
    if not sentences:
        return 0.0
    hits = sum(
        1 for s in sentences
        if judge.binary(f"下面的上下文能否支撑这句话?\n上下文:{contexts}\n句子:{s}")
    )
    return hits / len(sentences)

4.4 指标总览与定位

症状可能原因该看的指标
答非所问检索没找到相关内容Context Recall ↓
回答有幻觉生成器没约束好Faithfulness ↓
回答太啰嗦/偏题Prompt 问题Answer Relevance ↓
上下文噪声大top-k 太大Context Precision ↓

定位流程:端到端差 → 先看 Context Recall(检索是否兜住)→ 再看 Faithfulness(生成是否忠实)→ 最后看 Answer Relevance。

5. Ragas 实战

Ragas 是目前最成熟的 RAG 评估框架,内置上述指标并支持自定义。

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

data = Dataset.from_dict({
    "question": [
        "退款需要几天到账?",
        "支持哪些支付方式?",
    ],
    "answer": [
        "退款一般在 3~5 个工作日到账。",
        "支持微信、支付宝和银行卡。",
    ],
    "contexts": [
        ["退款将在审核通过后 3~5 个工作日内退回原支付渠道。"],
        ["支持微信支付、支付宝、银行卡三种方式。"],
    ],
    "ground_truth": [
        "审核通过后 3~5 个工作日退回原渠道。",
        "支持微信、支付宝和银行卡。",
    ],
})

result = evaluate(
    dataset=data,
    metrics=[
        faithfulness,
        answer_relevancy,
        context_precision,
        context_recall,
        answer_correctness,
    ],
)
print(result)
# {'faithfulness': 0.95, 'answer_relevancy': 0.91,
#  'context_precision': 0.88, 'context_recall': 0.90, 'answer_correctness': 0.84}
df = result.to_pandas()
df.to_csv("rag_eval_report.csv", index=False)

5.1 用 LangSmith / 自建管线跑批量评估

import asyncio

async def evaluate_rag_system(rag, golden: list[dict]) -> dict:
    rows = []
    for case in golden:
        retrieved = await rag.retrieve(case["question"])
        answer = await rag.generate(case["question"], retrieved)
        rows.append({
            "question": case["question"],
            "answer": answer,
            "contexts": [c["text"] for c in retrieved],
            "ground_truth": case["ground_truth"],
        })
    ds = Dataset.from_list(rows)
    return evaluate(ds, metrics=[faithfulness, answer_relevancy,
                                 context_precision, context_recall])

5.2 报告要按维度切片

整体平均分会掩盖问题。必须按问题类型切片:

切片FaithfulnessContext Recall结论
事实型0.960.94健康
多跳推理0.820.65检索漏链,需多跳检索
对比型0.880.71需增加召回数
否定型0.700.60上下文约束不足

多跳场景的改进方案(查询分解、迭代检索)见 /llm-rag-advanced-optimization/。

6. LLM-as-Judge 的偏差与校准

多数指标依赖 LLM 打分,因此 Judge 的质量决定了评估的可信度。

6.1 已知偏差

偏差现象缓解
位置偏差偏好第一个候选交换顺序跑两次取平均
长度偏差偏好更长的回答显式要求"长度不影响评分"
自我偏好偏好同族模型的输出换不同家族的 Judge
分数聚集大量 4/5 分,区分度低用 1~5 分并给锚点定义

6.2 提高可靠性的做法

JUDGE_PROMPT = """你是一个严格的事实核查员。

任务:判断「回答」中的每一条事实性断言是否被「上下文」支持。

规则:
1. 只依据上下文判断,不使用你的先验知识。
2. 上下文未提及的内容,一律判为「不支持」。
3. 回答长度与评分无关。

上下文:
{context}

回答:
{answer}

以 JSON 输出:{"claims": [{"claim": "...", "supported": true|false}], "score": 0.0~1.0}
"""

三个要点:

  1. 给锚点定义(1 分是什么、5 分是什么),避免分数聚集。
  2. 要求先给理由再给分(CoT),一致性显著提升。
  3. 用 JSON 结构化输出约束格式,便于程序解析——这正是受限解码的用武之地。

6.3 与人工标注对齐

Judge 上线前必须做一次对齐验证:抽 50~100 条人工标注,算 Judge 与人的一致率(Cohen’s Kappa)。

Kappa一致性
< 0.4差,不可用
0.4~0.6中,仅供参考
0.6~0.8好,可用于迭代
> 0.8优,可用于上线门禁

7. 组件级 vs 端到端

层次测什么优点缺点
组件级检索 / 重排 / 生成各自指标定位精准忽略误差累积
端到端最终答案质量贴近真实体验无法定位

两者都要有。日常迭代看组件级(快、便宜),上线门禁看端到端(准、慢)。

端到端还要关注非质量指标:

P50/P95 延迟、Token 成本/次、检索命中缓存率、拒答率、用户追问率

“用户追问率"是一个极好的隐式反馈信号——用户追问通常意味着上一轮没答好。

8. 评估集构建

评估集的质量决定了评估的上限。三种来源:

来源规模成本特点
人工标注100~500高最可靠,作为黄金集
线上真实问题1000+低分布真实,需补标准答案
LLM 合成数千极低覆盖广,需过滤

8.1 LLM 合成评估集

GEN_PROMPT = """基于下面的文档片段,生成 {n} 个用户可能提出的问题及其标准答案。

要求:
1. 问题必须能仅凭该片段回答。
2. 问题要口语化,模拟真实用户。
3. 覆盖不同类型的提问:事实查询、对比、条件判断。

文档片段:
{chunk}

以 JSON 输出:[{{"question": "...", "ground_truth": "..."}}]
"""

def build_eval_set(chunks: list[str], n_per_chunk: int = 3) -> list[dict]:
    cases = []
    for chunk in chunks:
        raw = llm.complete(GEN_PROMPT.format(n=n_per_chunk, chunk=chunk))
        cases.extend(json.loads(raw))
    return dedup(cases)

合成后必须人工抽检 10%:LLM 生成的问题常有"答案已在问题里"或"文档其实答不了"的缺陷。

8.2 评估集要覆盖的四类问题

  • 事实型:单一事实查询(“退款几天到账”)。
  • 多跳型:需要串联多个片段(“A 产品的退货政策和 B 产品有什么区别”)。
  • 否定/边界型:答案应该是"文档里没说”(考验拒答能力)。
  • 对抗型:问题措辞与文档用词不匹配,考验语义检索。

9. 在线评估与 A/B

离线指标涨了,线上不一定涨。最终要靠 A/B 验证。

9.1 指标设计

类型指标说明
质量点赞/点踩率、追问率直接反馈
行为会话时长、任务完成率间接反馈
成本平均 Token/次、检索次数成本控制
性能P95 延迟体验

9.2 分流与显著性

import hashlib

def assign_bucket(user_id: str, salt: str = "rag-v2") -> str:
    """稳定分流:同一用户始终进同一组。"""
    h = hashlib.md5(f"{user_id}:{salt}".encode()).hexdigest()
    return "treatment" if int(h[:8], 16) % 100 < 50 else "control"

注意:RAG 的 A/B 必须按用户分流而非按请求,否则同一用户的会话会在两组间跳变,污染体验。

样本量估算:点踩率这类低基率指标(如 3%)要检测 10% 的相对提升,通常需要每组数千次会话。小流量场景建议先用离线评估与灰度小样本的定性反馈。

9.3 回归门禁

把评估接进 CI,每次改动检索参数或 Prompt 都自动跑黄金集:

# .github/workflows/rag-eval.yml
name: RAG Evaluation
on:
  pull_request:
    paths: ["src/rag/**", "configs/retrieval.yaml"]
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pip install -r requirements.txt
      - run: python scripts/eval_rag.py --golden data/golden.jsonl --out report.json
      - run: python scripts/check_gate.py --report report.json --max-drop 0.02

门禁阈值建议:Faithfulness 下降超过 0.02 或 Context Recall 下降超过 0.03 即 fail。阈值太严会被噪声频繁误伤,太松则形同虚设。

10. 落地检查清单

  • 有分层指标:检索侧 + 生成侧 + 端到端
  • 有版本化的黄金评估集(含四类问题)
  • Judge 与人工标注做过一致性校验(Kappa > 0.6)
  • 评估报告按问题类型切片,而非只看平均分
  • CI 中跑回归,指标下降超阈值即阻断
  • 线上有隐式反馈指标(追问率)作为兜底信号

小结

RAG 评估的核心是分层:先用 Recall@k / NDCG 确认检索兜住了相关信息,再用 Faithfulness 确认生成没有幻觉,最后用端到端指标与 A/B 确认用户体验真的变好。

工具上首选 Ragas 覆盖标准指标,但评估集的质量比框架更重要——一个覆盖四类问题、经过人工校验的 200 条黄金集,价值远超一万条自动合成的低质样本。同时记住 LLM-as-Judge 有位置、长度与自我偏好三类偏差,必须做顺序交换、锚点定义与人工对齐。

指标体系的持续运营属于 MLOps 范畴,与模型上线流程的配合见 /llm-evaluation-llmops/。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「llm」更多文章

  1. 端侧推理:移动端与浏览器部署
  2. 长上下文优化:注意力稀疏化与 KV Cache 管理
  3. 实时语音 Agent:全双工对话与低延迟链路