RAG评测指标实战:忠实度、上下文精确率/召回率从原理到CI落地

RAG系统上线三个月后最常见的困境:用户反馈"答得还行",但你不知道是检索对了还是模型硬编的。传统QA只管功能跑通,RAG的质量问题出在两层------检索层拿没拿到对的资料,生成层有没有照着资料说。评测必须拆开打:检索层看上下文精确率(Context Precision)和上下文召回率(Context Recall),生成层看忠实度(Faithfulness)和答案相关性(Answer Relevancy)。工具直接用 Ragas 或 DeepEval,别自己造轮子,但指标的计算逻辑必须吃透,否则阈值怎么定、分数怎么解读都是黑盒。跑通之后把评测脚本接进 CI,每次改 prompt、换 embedding、调 chunk 策略都能拿到回归数据。

指标先分层:检索归检索,生成归生成

RAG 评测最常见的错误是把所有问题都归到"模型不行"。实际上不同层的问题要用不同指标暴露:

指标 所属层 回答的问题 计算方式
上下文精确率 Context Precision 检索 检索结果里有多少是真正相关的?相关文档排前面了吗? 逐位置看命中情况,按位置加权
上下文召回率 Context Recall 检索 该拿到的资料拿全了吗? 标注的相关文档中被检索到的比例
忠实度 Faithfulness 生成 回答有没有超出上下文瞎编? 回答拆成声明,逐条核对上下文支持
答案相关性 Answer Relevancy 生成 答的是用户问的吗? 用 LLM 打分或生成反向问题比对

检索层和生成层的分界线很明确:检索层指标不碰 LLM 的输出,只看"上下文窗口里有什么";生成层指标才看"模型基于这些上下文说出了什么"。定位问题的时候,先看检索层,再看生成层------检索层挂了,生成层分数再高也是空中楼阁。

忠实度的实现原理:声明拆解 + 逐条核验

忠实度是整个 RAG 评测里最值得吃透的指标,因为它直接对应"幻觉"检测。实现思路分三步:

  1. 让 judge 模型把回答拆成独立的原子声明(claim),每条只表达一个事实;
  2. 对每条声明,判断它是否被给定的上下文支持(支持 / 不支持 / 上下文无相关信息);
  3. 忠实度 = 被支持的声明数 / 总声明数。

伪代码长这样:

复制代码
def faithfulness(answer: str, contexts: list[str]) -> float:
    claims = judge_llm(
        f"把下面的回答拆成原子声明,每条一行,只输出声明本身:\n{answer}"
    ).splitlines()

    supported = 0
    for claim in claims:
        verdict = judge_llm(
            f"判断声明是否被上下文支持。只输出 SUPPORTED / NOT_SUPPORTED / "
            f"NO_INFO。\n上下文:{contexts}\n声明:{claim}"
        )
        if "SUPPORTED" in verdict:
            supported += 1
    return supported / len(claims) if claims else 1.0

这个设计的巧妙之处在于:把"这段回答对不对"这种模糊问题,拆成"N 个小判断题"。判断题比整体打分稳定得多,而且声明级别的结果可以直接喂给调试流程------哪条声明挂了,就知道是检索漏了资料还是模型在编,定位成本大幅下降。

注意 NO_INFO 这个第三分类不能省。上下文里根本没提这件事,和上下文明确矛盾,是两种完全不同的 failure mode:前者是检索召回不足,后者是模型无视上下文。混在一起算,你会分不清该优化检索还是换模型。

检索层指标:命中位置和覆盖度

上下文精确率看的是排序质量。假设一个 query 检索出 5 个 chunk,标注其中 chunk 2 和 chunk 4 相关,那么:

复制代码
def context_precision(relevant_positions: list[int], k: int = 5) -> float:
    # relevant_positions: 相关文档在检索结果中的位置(从1开始)
    score = 0.0
    for i in range(1, k + 1):
        if i in relevant_positions:
            # 位置越靠前,分母越小,贡献越大
            score += len(relevant_positions) / i
    return score / len(relevant_positions)

位置 2 的命中贡献是 2/2=1.0,位置 4 的命中贡献是 2/4=0.5,总分 1.5/2 = 0.75。也就是说,相关文档排得越靠前,分数越高------这个指标直接反映 reranker 和混合检索的调优效果。

上下文召回率则简单粗暴:标注的相关文档里,检索到了多少。它暴露的是切分策略和 embedding 的问题------chunk 切太碎,语义被截断,相关文档就召回不全。实践中这两个指标要一起看:精确率低召回率高,说明检索结果里噪声多,该上 reranker;精确率高召回率低,说明该调 chunk size 或换 embedding 模型。

用 Ragas 落地 + 接进 CI

原理清楚之后,落地直接用 Ragas 的 evaluate 接口,把 metrics 显式传进去:

复制代码
from ragas import EvaluationDataset, evaluate
from ragas.metrics import (
    faithfulness, answer_relevancy,
    context_precision, context_recall,
)

dataset = EvaluationDataset.from_dict({
    "user_input": ["我们的退款政策是几天?"],
    "response": ["根据文档,退款申请需在购买后7天内提出。"],
    "retrieved_contexts": [["退款政策:购买后7天内可申请退款..."]],
    "reference": ["退款申请应在购买后7天内提出。"],  # context_recall 需要
})

result = evaluate(
    dataset=dataset,
    metrics=[faithfulness, answer_relevancy,
             context_precision, context_recall],
)
print(result)

几个落地细节:

  • reference 列只有 context_recall 需要。如果只关心生成质量,可以只跑 faithfulness + answer_relevancy,减少标注成本;
  • judge 模型用比你线上模型强的。线上用 7B 小模型,judge 就用 GPT-4o / Claude 级别的,judge 太弱会把噪声带进指标里;
  • 结果带个 seed 重跑三次看方差。LLM-as-judge 天然有抖动,方差超过 0.05 说明测试集或 judge prompt 有问题。

测试集怎么来?三条路:线上真实 query 采样(最靠谱,但量少)、用 LLM 基于知识库文档合成(量大,注意别让合成 query 和文档措辞太像,否则指标虚高)、人工构造对抗样本(专门测边界情况)。比例建议 5:3:2。

CI 集成是这套东西价值最大化的地方,每次 PR 自动跑:

复制代码
# .github/workflows/rag-eval.yml
name: RAG Evaluation
on: [pull_request]
jobs:
  evaluate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }
      - run: pip install ragas
      - run: python scripts/eval_rag.py --dataset tests/rag_cases.jsonl
      - name: 分数门槛检查
        run: python scripts/check_threshold.py \
             --metric faithfulness --min 0.85 \
             --metric context_precision --min 0.7

check_threshold.py 里就是读评测结果 JSON,低于阈值就 sys.exit(1) 把 PR 挡下来。阈值建议从宽松开始:忠实度 0.8、上下文精确率 0.6,跑两周拿到基线再收紧,一上来定死高标准只会让团队天天跟 CI 搏斗。

踩坑记录与对比数据

  • 噪声敏感度是隐性杀手:把 3 段不相关的噪声 chunk 塞进上下文,忠实度从 0.92 掉到 0.61,但 answer_relevancy 几乎没变。所以别只看生成层指标------构造"相关文档 + N 段噪声"的测试样本,看忠实度随 N 的下降曲线;
  • Ragas 默认 judge 走 OpenAI,国内网络环境要配 base_url 走代理或换成国产模型兼容端点,否则 CI 一跑就超时;
  • DeepEval 的 metric 用 pytest 风格写断言,适合已经用 pytest 的团队;Ragas 的 dataset 驱动方式适合批量回归。两者指标定义有细微差别,同一条数据跑出来分数会差 0.05 左右,换工具后别直接拿旧阈值对比,先重新标定;
  • chunk size 从 512 调到 1024,context_recall 涨了 0.11,但 context_precision 掉了 0.04------大 chunk 覆盖全但噪声多,这就是为什么要同时盯两个指标而不是只看总分。

进阶方向

先把离线评测(golden dataset + CI 门槛)跑稳,再往两个方向走:一是线上评测,对真实流量抽样用轻量 judge 打分,接告警;二是从单轮问答走向 agent 评测,轨迹级别(trajectory-level)的指标------工具调用是否正确、规划是否合理------这已经是 Agent 测试的范畴,Ragas 和 DeepEval 都在往这个方向补能力。评测体系的建设顺序永远是:先分层指标,再建测试集,最后自动化,别跳步。


如果你也在做 AI 应用(RAG / Agent / LLM),不知道质量怎么测------我最近在给 AI 应用做免费质量体检,出一份可执行的测评报告(检索命中率、回答忠实度、噪声敏感度等维度),感兴趣可以直接私信我。

相关推荐
Easy_API16 分钟前
OpenAI三周内第二次降价,GPT-5.6 Sol砍了20%到33
大数据·前端·人工智能·gpt·深度学习
小K讲AI营销18 分钟前
智谱配售314亿港元:融资通道切换
大数据·人工智能
摇滚侠21 分钟前
《SpringBoot 3:入门与应用实战》第 9 章 使用 WebMvc 开发应用 阅读笔记 20
spring boot·笔记·后端
陕西企来客23 分钟前
2026年8月木铝门窗适合哪些住宅?材质性能与维护解析
人工智能·材质·木铝门窗
凤山老林23 分钟前
Spring Boot 3.x 集成 Spring Cloud Kubernetes:原生服务发现、配置注入与优雅下线
spring boot·spring cloud·kubernetes
深小乐24 分钟前
读李开复《AI 未来已来》,我三月份写的那些判断,被补全了
人工智能
Csvn27 分钟前
第 9 章 记忆系统
人工智能·aigc·agent
李洱30 分钟前
Reranker(重排器)
人工智能
rui050132 分钟前
加载dsh第三方插件
人工智能