RAG应用评测:从指标体系到LLM-as-a-Judge的自动化落地

RAG 上线后最常见的困境是"感觉答得还行",但产品、测试、老板问"到底行不行"时拿不出数字。这套评测方案把 RAG 质量拆成两层可量化指标:检索层 (hit rate、MRR、context precision/recall)衡量"有没有把对的东西捞上来",生成层(faithfulness 忠实度、answer relevancy、噪声敏感度)衡量"有没有基于捞上来的东西好好回答",统一交给 LLM-as-a-Judge 打分,配一个 200 条左右的 golden set 做回归。选型上指标计算用 ragas(业界通用、指标全),回归门禁用 promptfoo(YAML 配置、接 CI 成本低),judge 模型固定用 GPT-4o 且 temperature=0。跑了两个月,线上 52 个 query 的 faithfulness 从 0.71 提到 0.86,人工抽检一致率 84%。下面讲清楚指标怎么定义、judge 怎么校准、以及踩过的坑。

指标分层:先分清楚"没检索到"还是"没答好"

RAG 的错误一半以上出在检索层,但很多人只盯着最终回答看,导致定位问题全靠猜。把指标拆两层,出问题先看检索层指标,再看生成层指标,定位成本低一个数量级。

层级 指标 衡量什么 怎么算
检索层 Hit Rate@k 正确答案是否在 top-k 里 每个 query 有 golden chunk 标注,命中即 1
检索层 MRR 正确答案排得多靠前 1/rank 取平均
检索层 Context Precision@k 捞上来的 chunk 里有用的比例 有用 chunk 的 precision 按位置加权
检索层 Context Recall@k 该捞的 chunk 捞上来多少 命中 golden chunk 数 / golden 总数
生成层 Faithfulness 回答里的 claim 有多少能被检索上下文支撑 逐 claim 判定,支撑数 / 总数
生成层 Answer Relevancy 回答是否切题、没答非所问 judge 对 question↔answer 相关性打分
生成层 Noise Sensitivity 混入无关上下文后回答漂移多少 注入 top-k 外的噪声 chunk,对比回答变化

实际经验:先修检索层再谈生成层。context recall 低于 0.8 时,生成层指标再好看也是假象------答案可能来自模型参数记忆而非检索结果,上线后换知识库就翻车。检索层达标后,生成层的 faithfulnes 才是真正的"有没有忠实于资料"。

LLM-as-a-Judge:别整体打分,拆 claim 再判

judge 最容易犯的错是让 LLM 对"整个回答"打一个分。整体打分有两个毛病:一是 LLM 对长回答的局部错误不敏感,二是容易受"看起来专业"的措辞影响。正确做法是两步走:先让 judge 把回答拆成原子 claim,再逐条判定"该 claim 是否能被给定上下文支撑",最后算支撑比例。

judge prompt 模板(faithfulness 判定):

复制代码
你是一个严谨的评测员。任务:判断回答中的每个事实性陈述(claim)是否能被"给定上下文"支撑。

第一步:把回答拆成原子 claim,每个 claim 只包含一个可验证的事实。
第二步:对每个 claim 输出 JSON:{"claim": "...", "supported": true/false, "reason": "..."}
判定规则:
- 只有上下文明确包含的信息才算 supported,模型自身知识不算。
- 上下文没有提及的 claim 判 false(哪怕它是对的)。
- 上下文与 claim 矛盾判 false。

只输出 JSON 数组,不要额外解释。

代码侧用 ragas 跑全套指标:

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

judge = LangchainLLMWrapper(ChatOpenAI(model="gpt-4o", temperature=0))

dataset = EvaluationDataset.from_dict({
    "user_input": ["合同里违约金比例是多少?", "公司2025年营收多少?"],
    "retrieved_contexts": [["<chunk 1>", "<chunk 2>"], ["<chunk 1>"]],
    "response": ["合同约定违约金为合同总额的10%......", "根据检索资料,未找到相关数据。"],
    "reference": ["违约金为合同总额的10%。", "资料中未披露2025年营收。"],
})
result = evaluate(dataset, metrics=[
    faithfulness, answer_relevancy, context_precision, context_recall,
], llm=judge)

df = result.to_pandas()
print(df[["faithfulness", "answer_relevancy",
          "context_precision", "context_recall"]].describe())

三个关键点:temperature=0 必须写死,否则同一条数据两次跑分能差 0.1;retrieved_contextsresponse 必须来自同一次运行快照 ,分开采集会导致指标对不上;golden set 的 reference 要人工反复校对,因为 faithfulness 的判定基准是检索上下文,reference 本身有幻觉会带偏整个分数。

持续回归:promptfoo 接 CI,改完就测

离线批跑指标解决"这个版本行不行",解决不了"这次改动有没有把之前修好的问题弄回去"。用 promptfoo 把评测固化进 CI,每次改 prompt、改切分策略、改重排都跑一遍 golden set。

复制代码
# promptfooconfig.yaml
prompts:
  - file://prompts/rag_answer.txt

providers:
  - id: openai:gpt-4o
    config:
      temperature: 0

defaultTest:
  options:
    # 每个 test 都会先跑检索器取上下文,保证上下文是当前版本的真实输出
    setup: file://setup/retrieve.py

tests:
  - vars:
      question: "合同里违约金比例是多少?"
    assert:
      - type: llm-rubric
        value: "回答必须只基于给定上下文;上下文不含该信息时必须明确说不知道"
      - type: contains-all
        value: ["10%"]
  - vars:
      question: "公司2025年营收是多少?"
    assert:
      - type: llm-rubric
        value: "若上下文无营收数据,回答必须明确拒绝,禁止编造"
  - vars:
      question: "合同有效期多长?"
    assert:
      - type: llm-rubric
        value: "回答必须与上下文一致,不得引入上下文之外的事实"

llm-rubric 类型的断言就是 LLM-as-a-Judge 的工程化封装,语义化断言比字符串匹配抗"说法变了但意思没变"的情况。CI 里 promptfoo eval --config promptfooconfig.yaml 输出 JSON 报告,配合 promptfoo assertions 的阈值参数,分数跌破阈值直接让流水线失败。

踩坑记录

坑 1:judge 的位置偏差和自褒偏差。 同一份回答,把上下文顺序调换,faithfulness 能差 0.05~0.1;judge 还倾向给"结构完整、语气自信"的回答加分。对策:关键版本跑两遍(上下文顺序打乱各一次)取均值;rubric 里明确禁止"well-structured"之类的评价维度,只许判事实支撑关系。

坑 2:reference 污染。 早期 golden set 的参考答案是让另一个 LLM 生成的,没人工核对,结果 faithfulness 虚高------judge 看到参考答案里也有该 claim,倾向判 supported。后来对 200 条 reference 做了两轮人工校对,faithfulness 整体掉了 0.06,但和人工抽检的一致率从 71% 涨到 84%。指标的可信度比数值好看更重要。

坑 3:噪声敏感度必须主动构造。 真实用户 query 里"上下文混入无关内容"是小概率事件,被动采样测不出来。做法:评测时把检索结果第 k+1 到 k+3 的 chunk 强制塞进上下文,看回答漂移。某次改完重排后漂移率从 18% 降到 6%,但只测 faithfulness 完全看不出来------这就是噪声敏感度单独成指标的原因。

坑 4:阈值拍脑袋。 一开始定 faithfulness ≥ 0.85 才算过,结果小模型+简单场景根本到不了,大模型+难场景轻松超,阈值形同虚设。改成按场景分档:标准问答场景 ≥ 0.85,长文档综合场景 ≥ 0.75,低于档位禁止上线。阈值要和业务场景绑定,不能全局一个数。

工具对比与使用建议

工具 定位 强项 适合场景
ragas RAG 专项指标库 忠实度/检索指标全面,离线批跑 版本迭代的指标基线
promptfoo prompt/agent 测试框架 llm-rubric 断言、CI 集成、diff 对比 回归门禁、prompt 调优
Opik 评测+追踪一体 线上 trace 与离线评测打通 线上问题回溯定位
Giskard 测试+红队 agent 测试、漏洞扫描 安全与边界场景

组合建议:离线指标用 ragas 定基线,线上回归用 promptfoo 接 CI,线上观测用 Opik 接 trace。小团队先上 ragas + golden set 就够,别一上来铺四个工具,评测链路本身也是要维护的成本。

小结

RAG 质量评测的落地顺序:先建 200 条左右 golden set(覆盖各业务场景、含边界 case)→ 用 ragas 跑出检索层+生成层基线 → 修检索层(切分、embedding、重排)到 recall 达标 → 再调生成层(prompt、模型)→ 用 promptfoo 把 golden set 固化进 CI 当回归门禁。评测不是一次性工程,是跟着提示词、切分策略、重排一起迭代的闭环。进阶方向:用 LLM 合成数据自动扩充 golden set、把 judge 蒸馏成小模型降推理成本、按 diff 增量触发评测而不是全量跑。


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

相关推荐
神经星星15 分钟前
「TVM教程」理解 Relax 抽象层
人工智能·深度学习
深念Y18 分钟前
# CC-Switch + Claude/Codex 折腾教训记录
运维·服务器·网络·ai·agent·web·ccsiwtch
疯狂小猫咪18 分钟前
教培 SaaS vs 定制开发:技术架构与总拥有成本对比
运维
阿拉斯攀登19 分钟前
垂钓助手-四种漂相识别:下顿顶漂黑漂点漂的状态机设计
人工智能
ECT-OS-JiuHuaShan21 分钟前
共轭互逆链路论,彻底打击庸俗辩证法和不可知论
数据库·人工智能·算法·机器学习·数学建模
m4Rk_23 分钟前
【论文阅读】Agent 记忆机制(52):Cognitive Scaffold——面向 DeepResearch Agent 的结晶化记忆框架
论文阅读·人工智能·学习·开源·github
薛定e的猫咪23 分钟前
【大模型量化】使用 llama.cpp 完成量化、本地推理与服务化部署
人工智能·深度学习·算法·llama
paopao_djshddhdj25 分钟前
钉钉与钉钉服务商有什么区别?为什么建议选择服务商
android·钉钉
xiaohaiAIgeo28 分钟前
【2026年】实验室气路泄漏监测与自动切断
人工智能·科普知识