做 RAG 系统的朋友,应该都有过这种经历:检索出来的片段感觉挺像回事,但生成的答案跟原文对不上;或者答案读着通顺,可用户问的问题根本没有被回答。这时候你会怀疑检索、怀疑生成、怀疑模型,最后发现缺的是一套 RAG 评估系统——没把“检索得准不准、答得对不对”拆成可量化的指标,一切优化都是拍脑袋。前几天我在梳理一个内部项目时,又踩了一遍这个坑,所以想把这几年攒的评估方法论、落地细节、避坑点完整写出来,给正在被评估问题折磨的同行一个可直接抄作业的参考。
1. 为什么RAG需要一套独立的评估系统
1.1 两个“准”字背后是两类完全不同的故障
RAG 的完整链路其实只有两步:先从知识库里把相关文档片段捞出来,再让大模型基于这些片段组织答案。问题是,这两步都会出错,而且出错的表现形式完全不一样。
检索侧的“不准”,是“该捞的没捞上来”或“捞上来的不相关”。比如用户问“退货运费谁承担”,知识库里明明有运费规则,但向量检索返回的是“退货流程需要提交申请”这种泛泛段落,真正写清楚“超重运费由用户承担”的句子根本没进入候选集。这种情况再强的生成模型也救不回来,因为它没有读到关键材料。
生成侧的“不对”,却是“材料给够了,答案却跑偏”。检索返回的片段里已经明确指出“食品类商品不支持七天无理由退货”,模型生成的答案却说“所有商品均可七天无理由退货”。这种错误和检索无关,纯粹是生成阶段没约束住。
你如果只盯着“答案对不对”看,永远分不清病灶到底在哪个环节。所以我才坚持把 RAG 评估拆成两个独立的维度:先测检索,再测生成。检索指标差,去换 embedding、调切片策略;生成指标差,去改提示词、做解码约束。两步分开定位,问题才能被一个个拧出来。
1.2 没有评估的优化都是在盲调
我见过不少团队,优化 RAG 全靠“人工点几个问题看看”。今天把切片从 500 字改成 800 字,点开三个问题,觉得答案好像变长了,就说“有效果”;明天换了个 embedding 模型,又点开两个问题,觉得措辞更像人话了,就说“再提一档”。这种直觉式验证,在项目早期还能糊弄过去,一旦评估集扩大到上百条,你会发现两个方案各有胜负:A 方案在 A 类问题上更好,B 方案在 B 类问题上更稳。到底上哪个?没有统一指标,只能拍板,拍完就后悔。
评估系统解决的核心问题不是“评个分”,而是建立一条可量化的基准线。有了基准线,你才能说“把切片从 800 字调到 500 字后,检索命中率上升了 6.3 个百分点”,或者“加了重排之后,NDCG@10 涨了 0.12,但生成延迟多了 80 毫秒”。这样每次改动都是一次科学实验,而不是一次玄学祈祷。
2. 评估维度拆解:检索质量和生成质量分开看
2.1 检索侧:怎么衡量“有没有把该捞的捞出来”
检索侧的评估核心是一个词:排序。系统给每个问题返回 top-k 个片段,我们要判断这些片段里有多少是真正有用的、有用的排在第几位。常用指标有几个,我按实际使用频率排个序:
- 命中率(Hit Rate@k):前 k 个结果里是否至少包含一个相关片段。这是一个只看“有没有”的宽松指标,适合快速摸底。
- 召回率(Recall@k):相关片段中有多少被捞进了前 k。比如知识库里有 5 个片段都跟“退货运费”相关,系统只捞出来 2 个,Recall@k 就是 0.4。
- 平均倒数排名(MRR):第一个相关片段出现的位置的倒数。如果第一个相关片段排在第二位,MRR 就是 1/2。这个指标对“用户最关心最相关的那条是否靠前”很敏感。
- NDCG@k:考虑相关性的等级和位置关系。如果评估时把片段标注为“高度相关”“部分相关”“不相关”,NDCG 能区分出“高度相关的排第一”和“部分相关的排第一”之间的差距。
打个比方:检索准不准,就像在池塘里捞鱼。命中率是“你这一网下去捞没捞到鱼”,召回率是“池塘里该捞的 5 条鱼,捞上来了几条”,MRR 是“你惦记的那条大鱼是不是第一个咬钩”,NDCG 是“好鱼是不是都排在前面”。业务方最爱看命中率,因为人话好懂;算法优化时我主要盯 Recall@k 和 NDCG@k,因为它们更能反映排序质量的细微变化。
构建检索评估集时,必须为每个问题标注“golden documents”,也就是这个问题真正依赖的片段编号。这一步如果偷懒,后面所有检索指标都是空中楼阁。
2.2 生成侧:怎么衡量“答得对不对”
生成侧的评估比检索侧更主观,但也可以拆成几个可独立观察的维度。
第一个也是最重要的:忠实度(Faithfulness)。模型生成的答案里,每一句话是否能被检索到的上下文支持。如果上下文说“该产品保修期为一年”,模型却说“保修期为两年”,这就是不忠实。我习惯用一个简单粗暴的检查方法:把答案拆成若干独立事实点,逐个去上下文中找依据;找到一个算一分,找不到就打零。忠实度低,说明模型在“自由发挥”,这是 RAG 最不能接受的问题。
第二个是答案相关性(Answer Relevance)。很多时候答案没有虚构,但它不解决用户的问题。用户问“怎么申请退款”,模型回答“退款需要满足以下条件……”,然后把整个退款条款背了一遍。这算相关吗?信息是相关的,但不 answering 用户的“怎么申请”这个操作诉求。所以相关性要站在用户角度判断:这个答案是不是真的回应了提问意图?有没有给用户下一步行动的方向?
第三个是完整性(Completeness)。一个复杂问题往往包含多个子问题,比如“退货需要什么条件?运费谁出?多久到账?”三个子问题在答案里缺一不可。完整性指标就是要检查答案是否覆盖了用户问题里的所有关键诉求点。很多 psychological 模型偷懒,只答了第一个子问题,后两个就直接忽略了。完整性的评估可以作为忠实度、相关性之外的第三把标尺。
生成侧评估我建议三条指标分开打分,而不是合成一个总分。因为三个指标对应的优化手段完全不同:忠实度低,改提示词或做解码约束;相关性低,做 query 改写和多轮语义理解;完整性低,给模型更强的指令约束,比如“请逐点回答用户问题”。合成一个总分,反而会掩盖具体短板。
2.3 系统侧:可别忘了延迟、成本和拒答
检索和生成指标是“效果”指标,但 RAG 系统上线后,还有三个“工程”指标同样会决定体验。
延迟。检索要查向量库,生成要流水式输出,整套链路可能一上来就是一两个显著延迟。用户等 3 秒会不耐烦,客服场景可能等 5 秒就想挂电话。评估的时候应该记录 p50/p95 延迟,确认优化指标不会过度牺牲响应速度。
成本。token 消耗是大头。检索返回的片段越多,生成上下文越长,单次调用就越贵。如果一个优化能让忠实度提升 2%,但成本增加 50%,业务上通常是不划算的。评估报告里最好把 token 消耗同步打出来,做成本和效果的权衡。
拒答率。很多 RAG 系统设计成“不确定就不答”。拒答率太高,用户会觉得这助手形同虚设;拒答率太低,又会回复一堆幻觉。理想的系统应该在不确定性高的时候主动说“我不知道”,而不是硬编。评估时把拒答率和答案质量分开统计,你才能知道“不敢答”和“乱答”哪个更严重。
3. 评估集怎么建:从种子问题到 golden labels
3.1 评估集的构成原则
评估集是整个评估系统的心脏。没评估集,空谈指标毫无意义。构建评估集最忌讳的是拍脑袋写几十个问题,因为人脑天然偏向简单、直接的问题,而真实用户问得千奇百怪。
我在实践中总结了一套构成原则:
- 覆盖高频业务场景。从用户日志里拉 top 问题,按关键词聚类,确保每个业务模块都有代表问题。
- 加入复杂推理问题。不能只有“xx怎么开通”这种单跳问题,还要有“我上个月那笔退款为什么还没到账”这种需要查订单状态、退款规则、时间节点多个知识片段的多跳问题。
- 混入对抗样本。比如“可以用花呗吗”这种知识库没有明确答案的问题,或者“产品能不能退货”这种模糊、有歧义的问题。对抗样本的目的是测出系统“看不懂问题”或者“强行乱答”的边界。
- 数量先小后大。第一版评估集 100 条就够了。100 条能稳住基准线,再慢慢增加到 300、500 条。一口吃个胖子,标注质量反而崩。
另外,评估集要分群。按业务模块分、按难度分、按问题类型分。分群的意义在于,当总指标下降时,你能立刻看出是“退款类问题崩了”还是“复杂推理问题崩了”,不用满世界找原因。
3.2 标注流程:相关性判断和参考答案生成
评估集的每条数据长这样:
- question:用户问题。
- golden_docs:这个问题的检索答案中,应该被召回的文档片段 ID 列表。
- golden_answer:人工写的标准答案,或者至少是标准答案的关键点列表。
- difficulty:简单/中等/困难。
标注流程我建议分三步。
先标检索相关性。标注者对每个问题,从知识库里找出所有可能相关的片段,并给相关等级(高度相关/部分相关/不相关)。这一步不必一次找全,可以先把系统检索到的 top-20 片段扔给标注者筛选,降低大海捞针的负担。但注意,这样会漏掉系统没检索到但实际相关的片段,所以最好每季度随机抽检知识库里的某些片段,反过来看看有没有漏网之鱼。
再写参考答案。正确姿势是“从文档中来,到文档中去”。参考答案的每个要点必须能对应到某一片段。写答案要点时不要自由发挥,不要把标注者自己的知识代入。如果你发现某个问题连标注者都写不出有把握的答案要点,大概率这个问题本身就是知识库覆盖不了的,应该把它标成“拒答”样本,测试系统的拒答能力。
最后做一致性校验。同一个问题,至少让两名标注者独立标注,然后对比差异。如果两个人的 golden_docs 重合率低于 80%,说明这个问题本身有歧义,需要讨论解决或者直接从评估集扔出去。标注不一致的评估集,测出来的指标天然是随机噪声。
3.3 自动化生成评估集的补充思路
纯人工标注太贵,我一般会用半自动方式先产出一批候选,再人工抽检修正。
一个常见做法是“从文档反推问题”:取出知识库里的文档片段,把片段交给大模型,让它根据片段内容生成用户可能会问的问题和对应的标准答案要点。这种方法的优势是覆盖率容易做高,整个知识库都能扫一遍;劣势是模型生成的问题往往“太平顺”,和真实用户的提问方式差距大。真实用户会问“这玩意儿能不能退啊”,模型生成的是“该产品的退货政策是什么”。所以自动生成的问题必须混入真实用户日志,两类问题一起用。
另一个补充思路是拿线上日志攒问题。把用户真实交互里被点踩、被转人工的问题捞出来,作为对抗样本加进评估集。这些才是系统真正最容易翻车的地方。我总建议团队每两周从日志里抽一批坏案例补充到评估集里,让评估集随系统一起“成长”。不然系统越优化,却老在同一个地方翻车,评估集根本没反映出来。
4. 评估执行:人工、规则、LLM-as-a-Judge怎么选
4.1 规则评估:正则+关键词适合哪些指标
评估集建好之后,怎么算指标?很多人第一反应是“让大模型打分”,但实践中更好的做法是先低后高、先规则后模型。
规则评估适合那些“有硬性标准”的指标。比如:
- 是否包含指定的实体、数字、专有名词。例如答案里必须包含“退货政策”链接,或者必须包含“运费由用户承担”这几个字。
- 是否答非所问。可以定义几个“触发词”,比如用户问“怎么申请”,答案里必须有“点击”“提交”“流程”这类操作词;完全没有,大概率是跑题了。
- 是否保留了引用格式。如果你的 RAG 要求答案末尾带 [1][2] 这类引用标记,规则一查便知。
规则评估的优点是快、便宜、可解释,缺点是太浅。它只能判断“有没有出现”,不能判断“语义对不对”。所以我把它当第一层过滤器:先用规则把明显不达标的答案捞出来,剩下模棱两可的再用模型打分。这样既能省掉不必要的模型调用,又能保证评估结果有硬性约束。
4.2 LLM-as-a-Judge:实践中的prompt设计和防偏置
当评估指标转向忠实度、相关性、完整性这种语义指标时,人工标注最准,但成本高、速度慢。实际落地时,我几乎都用大模型当裁判(LLM-as-a-Judge),再用人工抽检兜底。
用好大模型裁判,prompt 必须写得非常细。我常用的结构是:
- 明确角色和任务:你是一个严格的 RAG 评测专家,请对以下的答案进行评分。
- 给出评分标准:1-5 分,每个分值是什么意思,最好给正面和反面示例。
- 要求逐条判断:不要只给总分,要按“忠实度”“相关性”“完整性”分别打分,并给出理由。
- 要求输出结构化结果:比如 JSON,包含三个指标的分数和一句简述。
还有一个关键细节:把检索到的上下文也一起给裁判。否则裁判无法判断答案有没有忠实于上下文。你光看答案本身,根本不知道它是不是编的。
大模型当裁判有几个已知偏置,我在实践中都碰到过:
- 位置偏置:裁判容易给排在前面的答案更高分。应对方法:评估多个候选答案时,把顺序打乱多评几次取平均。
- 自偏好偏置:裁判模型遇到自己生成的答案,会不自觉地给高分。所以在对比评估里,要把所有候选答案匿名化,不让裁判知道来源。
- 评分尺度漂移:同一份评估集,因为 prompt 里示例顺序微调,分数整体抬高 0.5 分。解决方法是固定 prompt 版本,任何修改 prompt 的行为都视为“换了新评估器”,结果不能直接和旧版本对比。
- 上下文过长导致裁判“注意力疲劳”。如果检索结果特别长,裁判可能只看开头就下结论。解决办法是截断或要求分段评估。
用大模型裁判时,我习惯用 temperature=0,并且对同一条样本的多个相似上下文版本做多次采样,效果更稳。成本上,100 条评估集用一次大模型调用,开销不高,完全可以接受。
4.3 混合策略:分层评估管线
单靠规则太浅,单靠大模型裁判又可能飘。我最终用的是一套分层评估管线:
第一层,规则过滤器。检查硬性条件:有没有关键实体、有没有引用标记、答案长度是否合理、是否触发了禁用词。规则不过,直接判不合格,不再进入后面的模型评估。
第二层,大模型裁判。通过规则过滤的答案,调用大模型按忠实度、相关性、完整性打分。输出结构化 JSON,存进结果表。
第三层,人工抽样复核。每周抽取 20-30 条大模型裁判的评分样本,人工核对分数是否合理。发现系统性的裁判误判(比如把空泛回答打高分),就回头修 prompt 或调整评分标准。
这套分层评估跑了一两个月之后,你会积累一批“裁判一致性很高”的评估样本。这部分样本可以逐渐替代人工,成为日常回归测试的主要度量方式。人工复核则聚焦在边界样本上——那些裁判给了 3 分,你觉得实际只有 2 分的样本,好好研究一下,通常就是系统优化空间最大的地方。
5. 实操:一个最小RAG评估系统的搭建过程
5.1 数据准备:选文档、切块
为了讲清楚落地过程,我拿一个模拟的小型知识库来演示:假设你做的是某类消费品的客服助手,知识库包含产品说明书、售后服务政策、常见问题三个模块。
第一步,把文档转换成可检索的片段。切片策略我之前调过很多次,最稳定的做法是“按标题和段落边界切,而不是按固定字数切”。举个例子,一个 FAQ 条例通常是一个语义独立的单元,强行拆成 500 字可能把“退货条件”和“退货流程”切散,检索时就会缺胳膊少腿。我一般先用文档解析器拿到标题层级,然后按二级标题下的内容块切,块大小控制在 500-800 字,超过 800 字的段落再根据句子边界二次切分,给相邻片段留 50-100 字的重叠。重叠不是为了检索更准,而是为了避免关键信息落在片段边界被截断。
第二步,把切片 ID 和文本存好,对文本做 embedding 并放进向量库。向量库的具体选择取决于你的部署条件,这个咱们不展开。没有向量库时,也可以先用内存里的暴力检索脚本跑通评估流程,效果一样。
切片完记得留住一份“切片映射表”,因为检索评估要的是“哪个切片被召回”,而不是“哪段文本被召回”。拿切片 ID 做指标计算,又准又高效。
5.2 离线评估脚本
评估流程我一般写成离线脚本,输入是评估集 JSON,输出是指标报告。伪代码大概长这样:
评估集 = 读入("eval_set.json") for each 样本 in 评估集: question = 样本.question 检索结果 = 向量库.search(question, top_k=5) 检索指标.update(样本.golden_docs, 检索结果) 上下文 = 取检索结果中的文本 生成结果 = 大模型.generate(question, 上下文) 生成指标.update(样本.golden_answer, 生成结果, 上下文) 报告 = { "Retrieval": { "HitRate@5": 统计(检索指标.HitRate@5), "Recall@5": 统计(检索指标.Recall@5), "MRR@5": 统计(检索指标.MRR), "NDCG@5": 统计(检索指标.NDCG@5) }, "Generation": { "Faithfulness": 统计(生成指标.Faithfulness), "AnswerRelevance": 统计(生成指标.AnswerRelevance), "Completeness": 统计(生成指标.Completeness) }, "System": { "AvgLatency_ms": 统计(检索延迟 + 生成延迟), "TokenCost": 统计(生成输入输出token数), "RefusalRate": 统计(拒答样本比例) } } 写出报告("report.json", 报告)关键点是维护好“先检索、后生成”的流水线,不要混在一起算。检索指标和生成指标必须分别计算。
评估集 JSON 里至少要有 question、golden_docs、golden_answer 三个字段。我还会加一个“difficulty”字段,这样报告里可以按难度分群统计,方便定位问题。
5.3 结果分析和优化动作
跑完脚本,拿到报告后怎么读?我按优先级给大家一个排查顺序:
- 先看忠实度(Faithfulness)。低于 0.8,说明答案里混入了不少上下文之外的信息。立刻检查提示词里有没有强制约束“只基于以下上下文回答,不要使用先验知识”。如果提示词没问题,那就是检索到了错误上下文,模型被误导了。
- 再看检索召回率(Recall@k)。低于 0.7,意味着相关片段经常没进 top-k。开始调切片策略、embedding 模型、检索重排。我常用的组合拳是:先用向量召回 top-100,再用重排模型精排取 top-5。这对召回率提升非常明显,但会引入额外的推理延迟,要结合延迟指标权衡。
- 然后看答案相关性和完整性。如果召回不差、忠实度不差,但相关性低,多半是 query 理解出了问题:用户问的是“怎么申请”,系统没有识别出操作意图。可以在检索前加一个 query 改写模块,把口语化问题转成标准检索式。
- 最后看系统侧指标。延迟高就精简上下文、用更快的生成模型;token 成本高就考虑减少 top_k 或压缩上下文。
用这套流程,我过去至少定位过三类“高发癌症”:改 prompt 导致忠实度提升但相关性下降;调大 top_k 让召回率提升但生成开始“东拉西扯”;以及只优化离线指标、没注意线上用户真实问法导致的“评估集近视”。评估系统不是做出来就完事,它得跟着真实问题一起进化。
6. 常见问题与排查技巧实录
6.1 评估结果不稳定怎么办
同一个评估集,两次跑出来的分数忽高忽低,这是新手最容易慌的问题。有次我的忠实度从 0.85 突然跌到 0.79,排查半天,发现是裁判模型那段时间服务波动,或者只是切了 prompt 里一个标点符号。遇到不稳定的第一反应不是怀疑业务,而是先确认评估环境是不是一致。
我的稳定化三板斧:
- 把裁判模型的 temperature 固定为 0。哪怕只是让策略更稳定,这个小改动也能去掉大量随机性。
- 对分数敏感指标,采用“多次采样投票”模式。比如让裁判模型对同一条样本评 3 次,取中位数或众数。成本增加有限,但稳定性显著上升。
- 记录评估配置指纹。把 prompt 版本、模型版本、版本号全部存进结果表。评估出现跳变时,先对比配置指纹,锁定了变量再谈原因。
6.2 指标高但用户体验差
评估集里跑分漂亮,上线后被用户骂“答非所问”。这种分裂通常来自“评估集和真实分布的偏差”。你评估集里全是工整的书面问法,真实用户却用倒装句、错别字、口语黑话。
我踩过这个坑后,强制团队每两周同步一次线上真实问题样本到评估集。方法很简单:从日志里捞用户输入,去掉 PII 后,挑出高频但评估集里没有的问法,人工写标准答案后加进评估集。只有让评估集贴近真实,指标才有意义。
另外,单个指标的高分是没意义的。比如检索命中率做到 0.95,但答案完整性只有 0.5,用户依然会觉得“这助手根本不懂我”。看报告时要综合看,不能只盯一个“好看”的数字。
6.3 检索和生成指标打架时怎么决策
有一类典型场景:调小 top_k,检索精确率(Precision)升了,但答案相关性和完整性反而跌了。因为 top_k 太小,把一些次相关但能补充细节的片段挤出了上下文;调大 top_k,检索召回率涨了,上下文变长,模型注意力被无关片段干扰,忠实度又降了。检索指标和生成指标像跷跷板,这时候该怎么取舍?
我的一般决策框架是:先守底线,再谈体验。忠实度和安全保障类指标是底线,不能为了检索指标好看而牺牲。兜底方案是“动态 top_k”:简单问题用较小的 top_k,复杂问题用较大的 top_k,或者对召回结果按重排分数做截断判断,低于某个分数阈值的片段即使进 top_k 也剪掉。
生成侧也可以给答案加上“只能引用给定上下文中出现的信息”的指令,并允许模型在上下文不足时主动拒答。这样即使检索指标一般,生成的答案质量也不至于崩。说白了,检索指标是诊断工具,生成质量才是用户体验。但如果没有检索指标,你永远不知道答案崩是因为材料没给够,还是模型没用好材料。
6.4 评估系统本身也要“打补丁”
最后分享两个小技巧。一是给评估集的每条样本记录“自己是从哪来的”:是人工写的、从文档反推的,还是线上日志补的。当一个用户反馈的坏案例进来,你可以立刻反查到评估集里相类似的样本,看当时模型答得怎么样。这样每个线上问题都能追溯回离线评估。
二是把“bad case”本身变成评估集的增量。我每次发现真实的用户差评,都会先跑一遍离线评估,看系统在同类问题上是不是也翻车。如果翻车,就把这条样本加进评估集,修复后再跑。这个循环多走几轮,评估集就会越来越“毒”,系统也会越来越皮实。
RAG 评估这件事,没有一劳永逸。我在实际维护中最大的体会是:评估系统的价值不在于给每轮开发打一个漂亮分数,而在于把模糊的“好像效果还行”变成具体的“哪项指标出了问题,下一步该动哪个模块”。只要你的 RAG 还在迭代,这个评估框架就值得持续投入。每次改完代码,跑一下评估集,看三分钟报告,再决定下一步——这个习惯养成之后,你会少掉至少一半的无效优化工作。