RAG评测这四个字,最近一年里我身边讨论的频率越来越高。做检索增强生成的同学都会有这种感觉:模型demo跑通很容易,接个向量库、把prompt调通,看起来像模像样;但一旦要上线,或者要跟别的方案比个高低,就被同一个问题卡住——这系统到底好不好?好在哪?差在哪?没有人能拍着胸脯说清楚。这篇文章就是围绕RAG评测这件事,把我实际做过的评测方案、踩过的坑、以及最后沉淀下来的一套流程完整梳理一遍。适合正在做RAG应用、想给知识库问答系统建立评估机制的团队参考,也适合刚接触检索增强生成、想搞明白评测到底怎么入手的朋友读。
1. RAG评测为什么这么难:不是跑通就完了
很多人刚接触RAG评测时,第一反应是“不就是拿测试集问一遍,看答得对不对吗”。真做起来你会发现,这套逻辑在传统问答系统里成立,在RAG里完全不够用。RAG是“检索+生成”的串联系统,任何一个环节出问题,最后都会反映在答案上,但根源却可能藏在完全不同的地方。
1.1 传统问答指标套不上
传统问答任务喜欢用EM、F1这类指标,判断答案是否和标准答案逐字匹配,或者重合度够不够。RAG场景下的问题往往更开放,比如“我们的网络重试策略对超时时间有什么要求”,标准答案可能是一段几百字的说明,模型给出的回答结构不同、措辞不同,但核心事实是对的。这时候你让EM算一下,得分大概率是0,但用户其实很满意。
所以RAG评测天然要拆成两层来看:检索层负责“有没有把需要的信息找出来”,生成层负责“有没有把找出来的信息用好”。如果混在一起只看答案,你根本说不清一个错误回答到底是向量检索没召回,还是大模型编造了内容,还是上下文顺序干扰了推理。评测的第一个难点就是分层,这也是整套体系里最容易被忽视的一步。
1.2 三个真实痛点
我实际做完一轮评测之后,总结了三个绕不开的痛点。
第一个是检索和生成相互掩盖问题。检索召回了正确文档,但生成模型在拼接上下文时忽略了重点,输出一个泛泛而谈的结论,你会误以为是检索失败;反过来,检索召回了一堆不相关内容,但生成模型恰好从一个片段里抓到有用信息,答对了,你也很难发现召回质量在恶化。单看端到端准确率,这些情况全部会被平均掉。
第二个是没有统一标准。同一套评测集,有人看重答案是否包含所有要点,有人看重是否有依据,还有人只看最终评分。不同团队跑出来的数字很难横向比较。最典型的是“忠实性”这个维度,有的评测员把它理解成“答案是否通顺”,有的理解成“是否完全来自检索文档”,标准不一致,数据就不具备参考价值。
第三个是人工评测成本高且不稳定。RAG的问题没有唯一答案,必须靠人打分。一次评测少说两三百条,每条都要读文档、读答案、逐项打分,一个人连续评一小时就开始疲劳,前后标准会漂移。更麻烦的是评测结果不好复现,你今天调了prompt,三天后想对比效果,发现当时的人工标注细节已经记不清了。
这些痛点决定了RAG评测不能靠拍脑袋,必须有明确的维度、可重复的流程、以及尽量自动化的手段。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 评测维度怎么搭:先拆再合
我在做RAG评测时,习惯把系统拆成三个层次分别打分:检索层、生成层、端到端层。每个层次回答的问题不同,评测方法也不同。
2.1 检索层评测:知识库捞得准不准
检索层的核心问题是:给定用户问题,系统从知识库里捞回来的Top K个文档片段,到底是不是相关的、是不是覆盖了回答所需的全部信息。
这里要区分两个概念:“相关”和“有用”。一个片段和问题主题相关,但没包含关键细节,那它对生成有害无益,因为它会占用上下文、干扰注意力。所以在检索层我通常同时看两个方面——相关性(相关片段是不是排在前面)和覆盖率(必需信息是不是都在召回结果里)。
实际操作中,检索层评测需要先给每个测试问题标注“黄金文档”。也就是从知识库里人工挑出回答这个问题所必需的文档片段。然后跑一遍检索流程,看Top K里命中了几个。相关指标包括Recall@k、Precision@k、MRR、NDCG,后面我会细讲计算方式。
这里有一个很容易踩的坑:只标“相关”不标“必需”。比如问“退款到账需要几天”,一个片段讲“退款流程整体说明”,里面提到工作日期限,它相关但不一定是最直接的信息源。如果标注时把这类泛化片段也算作黄金文档,检索指标会虚高,但生成结果依然不理想。我后来强制要求标注员给每个问题标注“没有它就一定答不出来”的片段,指标可信度提升了很多。
2.2 生成层评测:答案保真不保真
生成层评测重点看三个能力:忠实性、答案相关性、信息完整性。
忠实性指的是答案是否严格基于检索到的上下文,有没有编造知识库里不存在的内容。这是RAG区别于纯大模型对话的关键,也是用户信任度的来源。相关性指的是答案是否直接回应了用户问题,而不是答了一堆正确的废话。完整性则指标准答案里列出的关键信息点,有没有全部覆盖。
在实操中,完整性和检索层的覆盖率很容易混,但它们是两回事。覆盖率衡量的是“检索结果里面信息全不全”,完整性衡量的是“最终答案里面信息全不全”。检索全但生成漏了,说明生成环节有问题;生成全但检索漏了,说明模型可能是在凭记忆发挥,反而更危险。
生成层评测目前主流做法是用另一个大模型当裁判,也就是LLM-as-a-judge。它有成本低、速度快、可复现的优点,但也存在自身偏好、随机性等问题,我后面会专门讲怎么缓解。如果你对自动化打分不放心,可以抽一部分样本做人工复核,通常抽20%左右就够发现系统性偏差。
2.3 端到端层评测:用户体感好不好
端到端层不是简单的“最终答案评分”,而是从完整会话维度看体验。除了答案本身,还包括首字延迟、整体响应时间、追问理解、多轮记忆、答案稳定性等。这些指标不会直接告诉你检索或生成哪里有问题,但它们决定了用户愿不愿意用你这个东西。
我自己常用的端到端指标有三类:任务完成率、无效输出率、稳定性。任务完成率可以近似用“答案是否解决了提问意图”来评估;无效输出率看的是“答非所问、道歉、拒绝回答”的比例;稳定性则是同一个问题在相同条件下多次提问,答案的关键事实是否一致。很多团队只关心第一个,其实“偶尔抽风”才是用户流失最大的原因。
端到端评测的数字只能当作指挥棒,不能当作手术刀。它告诉你“这里不对”,但具体是检索还是生成的问题,需要结合前两层指标去定位。这也是为什么一定要把维度拆开——先分层评测,再端到端汇总。
3. 评测集构建:七分数据三分指标
评测指标再花哨,数据不行全白搭。RAG评测集的质量直接决定评测结果有没有说服力。我见过不少团队在指标代码上花了两周,最后用脚写了一个50条的小测试集,测出来的数字根本不敢对外讲。数据构建至少要占整个评测工作量的七成。
3.1 三种数据来源与取舍
RAG评测数据主要有三个来源:已有文档人工出题、线上真实用户问题、大模型辅助生成后人工校验。
已有文档人工出题最靠谱。找熟悉业务的人读一遍知识库,针对每个核心章节写出用户可能会问的问题,然后标注标准答案要点和对应的文档片段。这样数据质量高、覆盖明确,但缺点是费人力和时间,尤其当知识库动辄上千个文档时,很难做到全面覆盖。
线上真实用户问题最贴近实际。如果你的应用已经上线,可以直接把用户日志里高频问题抽出来做成评测集。这类数据最大的价值在于问题表达真实,包含各种口语化、错别字、指代不清等“脏”输入,能暴露系统在理想化测试集上完全看不出的问题。但用户日志通常需要人工清洗和标注,否则问题和答案映射关系会很乱。
大模型辅助生成后人工校验是折中方案。让大模型读文档、生成多组问答对,再由人去改。效率很高,但有一点必须警惕:大模型生成的“标准答案”往往会超出原文范围,带入模型自身的知识。人工校验时不能只改语句,还要逐条确认每个答案要点都能在对应文档片段里找到依据,否则你的评测集本身就是“不忠实的”,测出来的忠实性分数毫无意义。
我在一个内部项目里采用的组合是:六成人工出题、三成真实用户问题、一成模型辅助生成后全量人工校验。整体跑下来,前两类数据最能发现系统问题,第三类主要用来补充长尾场景。
3.2 标注规范怎么写
评测数据不是把问题和答案丢进Excel就完事,必须有明确的标注规范。结合我自己的实践,一个合格的RAG评测条目至少包含五个字段:
| 字段 | 含义 | 标注注意事项 |
|---|---|---|
| 问题 | 用户的实际提问 | 尽量口语化,不要按标准问法写 |
| 标准答案要点 | 回答该问题必需的若干事实点 | 每条必须是独立可核验的陈述 |
| 黄金文档列表 | 必须检索到的文档片段ID | 只标“没有它答不出来”的片段 |
| 问题类型 | 如事实查询、步骤咨询、对比分析、决策建议 | 用于后续分层分析 |
| 难度/边界情况 | 如需要多跳检索、存在近似干扰文档 | 用来区分压力测试和常规测试 |
其中“标准答案要点”的写法最关键。它不应该是一整段话,而应该拆成若干条原子事实。比如“退款到账一般3-5个工作日”应拆成“退款到账时间受银行处理影响”“大多数情况下3-5个工作日”两条。原子化之后,生成模型的完整性打分可以用“命中要点数/总要点数”来算,非常便于统计。
还有一个我一开始忽略的点:标注必须写清楚“哪些文档片段是干扰项”。也就是说,知识库里存在一些内容上很像、但回答当前问题用不到的片段,人工标出来之后,检索评测可以额外观察模型是不是被这些干扰项带偏了。这比单纯看Recall更贴近真实场景。
3.3 最小样本量与数据划分
多少条评测数据才够用?没有一个绝对答案,但可以按维度给个经验底线:如果按问题类型细分,每个类型至少30条;整体评测集不建议少于200条。低于200条,指标置信度很低,稍微调个seed结果的波动可能都比算法改动带来的提升还大。
数据划分也很有讲究。不要随机划分,要按文档来源或问题类型划分。理想做法是:保证评测集涉及的文档不参与向量库构建阶段的验证调参,避免模型间接“记住”评测内容。划分比例一般训练/开发/评测按60/20/20,对于评测集要固定不轻易改动,否则不同版本之间数字没法对比。
我自己会把评测集分成三组:核心集(常规问题,追求稳定性)、压力集(多跳、模糊表述、长文档摘要)、对抗集(包含相似干扰项、缺文档、用户给错信息)。三组分开报告,比一个笼统的数字有说服力得多。比如系统核心集分数高但对抗集崩了,说明鲁棒性不足,上线前就得重点修。
4. 核心指标的计算与自动化评测实现
评测维度和数据集定下来之后,就到了真正出数字的阶段。这一部分我讲三个检索指标的计算过程、基于大模型的生成打分方法,以及一套可落地的自动化评测pipeline。
4.1 检索指标:Recall@k、MRR、NDCG计算示例
检索层的指标一定要区分“分位数”。评测时通常固定Top K,K值根据你应用实际送入生成模型的片段数量来定。假设你的RAG系统一次从召回结果里取5个片段送给模型,那检索指标就看Recall@5、Precision@5这些。
Recall@k的计算逻辑:对每个问题,先看人工标注的黄金文档有多少个,再看检索结果的Top k里命中了多少个,比值就是单条Recall@k。举个例子,一个问题有3个黄金文档,系统Top 5召回了其中的2个,那Recall@5 = 2/3 ≈ 0.67。全体问题取平均就是整体Recall@5。
Precision@k则是计算“Top k结果里有多大比例是有效命中”。同样那个例子,Top 5里有2个黄金文档,Precision@5 = 2/5 = 0.4。Recall和Precision一起看,才能知道系统是“召回多了但混入大量噪声”,还是“召回太少导致信息缺漏”。
MRR(Mean Reciprocal Rank)特别适合单文档答案类问题。它的逻辑是:第一个正确答案排在第一位,得分1;排在第二位,得分1/2;排在第k位,得分1/k;没有正确答案得分0。比如一个问题第一个黄金文档排第3位,那它的RR就是1/3。把所有问题的RR取平均就是MRR。
NDCG(Normalized Discounted Cumulative Gain)更精细,它不只关心“命中没命中”,还关心“命中结果的排序质量”。计算分三步:先算DCG,把每条命中结果按位置折损,排序越靠前贡献越大;再算理想情况下的IDCG;最后用DCG/IDCG得到NDCG。公式不复杂,核心思想是“正确答案排得越靠前,分数越高”,适合相关性强弱有梯度的场景。
我实际用下来,业务上最直观的还是Recall@k加MRR这两个组合:Recall告诉你信息全不全,MRR告诉你系统是不是把最重要的内容排到了最前面。NDCG在对比不同embedding模型或者排序策略时更有区分度。
4.2 生成指标:LLM打分器的使用方法
生成层的忠实性和相关性,现在业界最通用的做法是请一个大模型当裁判。但这不代表随便问一句“这个答案好不好,打分1-5”就行。想让LLM打分稳定,prompt设计有门道。
第一,定义要具体。不要写“请评估答案质量和忠实性”,要分开成两个任务,分别打两个分。忠实性的打分标准必须绑定上下文,比如规定“答案中每个关键事实都必须能在给定上下文中找到直接依据;若出现上下文没有的信息,本维度直接判0分”。
第二,让模型先判断再打分。不是直接输出分数,而是先让模型一步一步地列出“答案中的关键事实点”,逐条判断是否源于上下文,最后再汇总成得分。这样做能显著减少模型走捷径、凭印象打分的概率。
第三,固定生成参数。所有评测请求统一用temperature=0,尽可能减少随机性。同时关掉一些“创造性”开关,文字不要多,只输出JSON格式的分数和理由,方便解析。
第四,采用“双裁判+仲裁”机制。如果预算允许,用两家不同的大模型分别打分,不一致时让第三个模型仲裁。没有预算的话,至少对同一个模型用两次不同前缀的prompt打分,取较低分作为保守估计。这一步能明显提高评测结果的稳定性。
还有一个不太被提到但很重要的点:给LLM裁判看的“参考答案”要写标准答案要点,而不是完整段落。完整段落会让裁判不自觉地比较措辞,而要点列表能引导它关注事实覆盖情况。这和3.2里要求的原子化答案是同一个逻辑。
4.3 一条可落地的评测pipeline
自动化评测的代码结构不复杂,我给一个我项目里用过的核心逻辑,你可以直接照着写:
python复制import json
from typing import List
def run_eval(eval_set: List[dict], rag_system, judge_model):
results = []
for item in eval_set:
question = item["question"]
gold_doc_ids = set(item["gold_doc_ids"])
answer_points = item["answer_points"]
# 1. 检索
retrieved = rag_system.retrieve(question, top_k=5)
retrieved_ids = [r["doc_id"] for r in retrieved]
hit_count = len(set(retrieved_ids) & gold_doc_ids)
# 2. 检索指标
recall = hit_count / len(gold_doc_ids) if gold_doc_ids else 0
precision = hit_count / len(retrieved_ids) if retrieved_ids else 0
mrr = 0.0
for rank, doc_id in enumerate(retrieved_ids, start=1):
if doc_id in gold_doc_ids:
mrr = 1.0 / rank
break
# 3. 生成
context = [retrieved[i]["text"] for i in range(len(retrieved))]
answer = rag_system.generate(question, context)
# 4. 生成层LLM打分
score = judge_model.judge(
question=question,
answer=answer,
context=context,
answer_points=answer_points
)
results.append({
"question": question,
"recall@5": recall,
"precision@5": precision,
"mrr": mrr,
"faithfulness": score["faithfulness"],
"answer_relevance": score["answer_relevance"],
"coverage": score["coverage"],
})
# 5. 汇总
summary = {}
for metric in ["recall@5", "precision@5", "mrr", "faithfulness", "answer_relevance", "coverage"]:
summary[metric] = round(sum(r[metric] for r in results) / len(results), 4)
return results, summary
这段代码省略了实际检索和生成的具体实现,但流程完整:先检索,再算检索指标,然后生成,最后LLM打分。实际跑的时候,建议把每一条的详细结果存成JSON文件而不是只存汇总分数。因为评测不只是为了得到一个数字,更重要的是当数字异常时你能去追溯是哪几条case出了问题。
跑完一遍之后,我会让系统输出一个按问题类型分组的指标透视表。比如对比“事实查询”和“步骤咨询”的Recall差异。这种分组报告比整体均值更能指导优化方向。如果步骤咨询的检索Recall只有0.4,说明现有的chunk切分或索引结构对长过程类文档不友好,这是非常有价值的信号。
5. 常见问题与避坑实录
前面讲的是正确做法,这一部分讲我踩过的坑和对应的排查思路。这些问题如果不注意,评测报告做得再漂亮,也会误导决策。
5.1 LLM打分的稳定性问题
第一次用LLM打分时,我遇到一个很诡异的现象:同一批数据上午跑和下午跑,忠实性分数差了8个百分点。排查之后发现,不是模型变了,而是裁判prompt里有个字段“请尽量严格”被系统当成了可选项,而且评测请求带上了随机采样参数,temperature没有固定为0。更隐蔽的是,裁判模型对不同选项位置存在偏好,比如两个答案质量接近时,它倾向于给先出现的答案更高分。
解决方法是固定temperature=0、固定候选顺序、多次打分取平均值。具体来说,每条评分跑3次,去掉最高最低取中间值。代价是耗时变长,但换来的是数字稳定,否则你很难判断一个改动到底是真有提升还是打分波动。对于预算有限的团队,至少要保证对照组和实验组都在同一天、用同一批评测进程跑,不要跨天后对比。
5.2 上下文窗口导致“假漏召回”
有段时间我们的Recall@5一直低,但人工看检索结果觉得相关文档其实已经被召回了,只是排在第六、第七位。后来发现不是召回算法的问题,而是我们评测时传给检索器的query经过了改写模块,改写后的query丢失了原始问题里的关键限定词,导致检索排序整体偏差。
还有另一种“假漏召回”更隐蔽:某些chunk长度超过模型上下文窗口,在拼接阶段被截断,导致生成模型根本没看到关键信息,但检索层指标是正常的。这时你需要分别记录“检索Top K id”和“实际送入生成模型的文本”。如果两者的交集小于检索结果,说明有截断问题,要优先调整chunk切分策略或上下文拼装逻辑。
排查这类问题,我的习惯是先把单个case的完整链路打印出来:原始query、改写后query、Top 10文档id和分数、拼进提示词的最终上下文长度。只要看一遍,基本就能定位是改写、召回、排序、截断哪一步出了问题。
5.3 评估数据泄漏
数据泄漏是RAG评测里最容易被忽略的“隐形杀手”。最常见的场景是:你从知识库里抽取文档构建评测集,后续为了调优又把同一批文档放进了向量库做检索验证。结果模型对评测问题异常熟悉,检索阶段只要是相关片段就能排到前面,生成阶段甚至不依赖检索结果也能靠模型记忆答个大概。最终评测分数虚高,上线后立刻打回原形。
规避办法是:用于构建评测集的文档在评测期间绝对不能出现在向量库里。如果知识库规模有限,至少要保证评测集中的问题对应的黄金文档被强制排除在检索库之外,或者单独建一个隔离的评测环境。更严格一点,在生成评测报告时记录“答案里的关键事实是否能在检索上下文中找到对应文本”,如果找不到但在提交的gold docs里存在,就要警惕系统是不是在开卷考试中使用了“闭卷记忆”。
5.4 指标与体感不一致怎么办
有时候评测报告显示所有指标都在涨,但你实际试用系统发现体验变差了。我遇到过的一种典型情况是:检索Recall提升靠的是把Top K从3调到8,但生成模型面对更长的上下文反而容易被噪声带偏,忠诚度下降了,用户感觉答案变啰嗦、不聚焦。指标涨了,体验降了。
原因在于我们把检索指标和生成指标当成独立目标去优化,忽略了它们之间的交互。解决办法是引入一个“联合指标”:当检索结果中干扰项比例超过某个阈值时,对端到端答案质量做惩罚。具体可以定义一个“有效上下文比”——在送入模型的Top K片段里,与最终答案中关键事实相关的片段占比。这个值低,说明检索虽然提到了相关文档,但有效信息密度不够,模型被稀释了。
指标与体感不一致时,不要急着怀疑评测体系,先回到具体case里看用户反感的答案长什么样。通常你会找到一种评测指标没有覆盖的模式,比如“答案正确但开头先说了一堆免责声明”或“每句话都对,但整体没有回答问题”。这时候就该给评测集补充这类case,而不是反过来硬调指标权重。
最后再分享一个小技巧
做了这么多轮RAG评测,我最大的感触是:评测体系本身也需要版本管理。每跑完一轮重要优化,把评测集、指标配置、裁判模型版本、分数结果一起打一个快照。三周之后你想复盘“当时为什么检索指标突然掉了2个点”,如果还有快照可以回溯,就能定位是数据变了、模型换了还是评分prompt改了。如果没有快照,只能凭记忆猜,那评测就失去了复现能力。
另一个经验是:先跑通一条50条的小评测集再扩展,不要一开始就追求上千条。50条能帮你把评测流程中的bug都逼出来——字段缺失、id对不上、打分解析失败、上下文拼接越界。流程稳了之后,再补到200条、500条才有意义。否则你会在一个坏流程上浪费时间,而且得到一堆无法可信对比的数字。
RAG评测没有一劳永逸的标准答案,但一定有一个适合你业务的最小可行体系。从确定维度、构建高质数据集、实现自动化流程,到不断补充边界case打磨指标,整个过程本身就是让系统变可靠的过程。希望这篇梳理能帮你少踩一些我踩过的坑。
