☰
RAG系统不评测等于闭眼调参:从指标原理到落地实战指南
2026/10/14 10:59:50 网站建设 项目流程

做 RAG 不评测,等于闭着眼睛调参:一份从原理到落地的完整测评指南

前段时间帮某团队排查一个内部知识库问答系统,反馈是"效果时好时坏"。我上去一看,两周内他们把 embedding 模型换了两版,切块大小从 128 调到 512 再调回 256,top_k 来回改,连 prompt 都重写了三轮。问效果怎么衡量的,答案是"让几个同事随便问了几个问题,感觉还行"。这种状态我相信很多做 RAG 的人都经历过——你确实在调参,但没有任何一把尺子告诉你每次改动到底是变好了还是变差了,甚至可能只是你对某个测试问题产生了记忆错觉。

RAG(Retrieval-Augmented Generation)应用开发和传统模型训练最大的区别在于:它没有显式的 loss 曲线,整个系统是检索、排序、生成三段式流水线,错误可能发生在任意一段。你今天改了切块大小,检索结果变了,生成端跟着变,最终答案可能变好也可能变坏,而你不一定能归因到具体环节。所以我说,不做评测的 RAG 调试就是闭着眼睛调参,效率低不说,还容易把系统越调越糟。

这篇文章我会从评测的原理讲起,把指标到底在测什么讲清楚,然后给出可以照做的落地流程:怎么构建黄金测评集、怎么用现成开源评测框架跑出基线、怎么把评测分数翻译成具体的调参动作,最后聊一聊评测体系搭建过程中最容易踩的坑。内容偏实战,适合正在做 RAG 应用、已经被"效果不稳定"折磨过的开发者,无论你是刚起步还是已经调了几个月,都会有用。

1. 没有评测的 RAG 调试,本质是在掷骰子

先说一个我观察到的普遍现象。很多人做 RAG 的"评测"其实是这样的:准备三五个业务问题,跑一遍系统,肉眼看一下答案,觉得不错就上线。这种方式的脆弱性远超想象——你换一个提问措辞,结果可能完全不同;你自我感觉"效果好"的那次,可能只是因为你恰好在上一轮记住了某个答案。

1.1 最经典的失败循环:改参数、试效果、原地打转

我在不少项目里见过同一个循环:今天觉得答案不够准,把 chunk_size 从 300 改成 600,跑一个问题觉得"好像详细了一些";明天又觉得回答太啰嗦,把 top_k 从 3 改成 2,再跑一个问题觉得"干净了";过了两天又有人反馈某个问题答不出来,于是把 embedding 模型从 A 换到 B。一周下来,改动记录一大堆,但没有任何两个改动在同一组问题上可比过。

问题出在哪?你每次验证用的测试题都不一样,体感也会随当天状态波动。更隐蔽的是,人的记忆对"答案质量"的判断极不稳定,同一个答案今天看觉得行,明天看又觉得不行。没有固定的测试集、没有统一的评分标准、没有可以回溯的得分记录,那每次改动就是在掷骰子,中不中全靠随机性和心理安慰。

1.2 评测的核心目的:定位是"找不到"还是"不会答"

RAG 系统的链路可以粗分成两段:找文档和写答案。找文档由 embedding、检索策略、排序策略决定;写答案由大模型、prompt、上下文窗口利用方式决定。两段出问题的表象很相似——用户看到的都是"回答不对",但修法截然不同。

"找不到"的典型表现是,你问了一个问题,系统返回的上下文片段里根本没有相关的信息,或者相关信息排在非常靠后的位置,大模型没来得及利用。这种情况你去改 prompt、换生成模型都没用,得在检索侧下功夫。"不会答"的典型表现是,相关文档明明检索到了,上下文里也有答案,但生成端要么视而不见、要么瞎编、要么答非所问。这种情况你去调 embedding、换切块策略,效果也不明显。

评测就是干这个的:用同一组测试题反复测量系统,把"找不到"和"不会答"两类问题分开量化,让每一次参数调整都有明确的归因方向。

1.3 为什么"体感好"不可靠,以及指标的真正价值

我见过有人对指标有抵触情绪,觉得"分高不代表真的好用"。这个说法有一定道理,但注意,指标不是用来替代人的判断,而是用来建立可比性。体感判断的问题在于不可复现、不可对比、不可回归;指标判断的价值在于,它能告诉你这周的系统相比上周是进步了还是退步了,在哪个维度上进步、在哪个维度上退步。

建立评测体系之后,你做实验的方式会彻底改变:一次只改动一个变量,在完全相同的测试集上跑分,对比分数变化,留下记录。这样积累几轮之后,你会对自己系统里"什么改动有效、什么改动无效"形成清晰的经验,而不是永远凭感觉。这也是这篇文章想帮大家建起来的东西。

2. 评测指标:先搞清楚每个数字到底在测什么

RAG 评测指标看着一堆,其实归类之后并不复杂。我习惯先分两组:检索侧指标和生成侧指标。前者衡量"找得准不准",后者衡量"答得好不好"。每一组再细分两个核心指标,一共四个,足够覆盖 90% 的调试场景。

2.1 先分清楚两组概念:检索侧 vs 生成侧

检索侧关心的是:给定一个问题,系统从向量库里捞出来的那些文档片段,和真正需要的相关信息到底重合度有多高。生成侧关心的是:拿到这些文档片段之后,大模型生成的最终答案质量如何。

如果你没有做过标注,可能觉得这两件事很难分开测。实际上方法是有的:检索侧需要一个"标准答案文档"作为参照物,生成侧需要一组"标准答案文本"或至少一个可判定的知识范围。有了这两个参照物,机器就能打分,不需要人肉一条条看。

2.2 四个核心指标逐一拆解:测什么、怎么算、对应什么故障

下面这张表是我自己使用时最舒服的对照表,先记住它,后面所有调参动作都围绕它展开。

指标属于测什么高分意味着对应故障
上下文召回率(context recall)检索侧需要的信息有多少被检索出来了检索结果比较完整,没漏关键内容找不到、答不出
上下文精确率(context precision)检索侧检索结果里有多少是真正有用的、没带太多噪声返回片段紧凑、噪声少,大模型不会分心回答冗长、被无关内容带偏
忠实度(faithfulness)生成侧答案是否严格基于检索上下文,有没有凭空捏造模型在老老实实"抄作业",没有幻觉一本正经地编答案
答案相关性(answer relevancy)生成侧答案是否正面回答了用户的问题答即所问,没跑题没绕弯答非所问、长篇大论讲无关内容

上下文召回率通俗理解就是"该捞的捞上来没有"。打个比方,你去图书馆找一本讲"如何做蛋糕"的书,系统只给你找来了烹饪百科的封面,内容页没给你,这就是召回率低——相关的东西存在,但没被捞出来。上下文精确率则是"捞上来的东西里有多少是有用的"。同样找蛋糕书,系统一口气给了你十本书,其中八本是讲川菜、西餐、面点的,只有两本真在做蛋糕,这是精确率低——东西捞多了,但噪音太大。

忠实度对应的是"幻觉"问题,这在 RAG 里最常见。上下文里明明没有这个信息,模型却答得有模有样,甚至引用了不存在的统计数字。忠实度指标专门盯着这一点,看答案里的每个关键信息能不能在上下文中找到依据。答案相关性则是防止模型"顾左右而言他"——你问 A 产品的价格,它给你讲 A 产品的开发历史,虽然信息是真的,但用户要的不是这个。

2.3 指标不要贪多:先跑最小组合,后面再扩展

市面上有些评测框架一上来就提供十几个指标,覆盖面确实广,但对第一次搭评测的人而言,我建议先跑最小组合:上下文召回率、上下文精确率、忠实度、答案相关性。原因很简单,这四项刚好对应 RAG 四个最典型故障方向,每个方向都清楚,调试时不会也不知道从哪下手。

等你跑熟了,再按需增加指标。比如你想专门监控检索排序的效果,就引入"平均倒数排名"或"NDCG"这类排序指标;想监控生成内容的格式规范性,再加"格式准确率"。但第一次评测,请克制住加一堆指标的冲动,指标越多,翻车时越难归因,而且有些冷门指标实现得并不成熟,打分稳定性和可解释性都存疑。

3. 构建黄金测评集:地基不牢,评测全白做

指标的分数是基于测试集算出来的,所以测试集的质量直接决定评测有没有意义。垃圾测试集配上再精妙的指标,产出的也只是精妙的废话。构建黄金测评集这步我踩过不少坑,花在它上面的时间,值得占整个评测体系搭建的一半以上。

3.1 黄金测评集的标准结构

一份能用的 RAG 黄金测评集,每条记录至少包含四部分:问题(question)、相关知识片段(ground_truth_context)、标准答案(ground_truth_answer)、期望来源(expected_source)。有的教程会说前三项就够,但实际调试时第四项非常有用——它告诉你这条问题期望命中的是哪个章节、哪份文档,方便在检索结果里快速定位是哪一环断了。

比如一条数据可以是:

  • 问题:某产品的退货政策是什么?
  • 相关片段:用户可在签收后 7 天内联系客服申请无理由退货,商品需完好。来源:《售后服务手册》第 3.2 节。
  • 标准答案:用户可在签收后 7 天内联系客服申请无理由退货,前提是商品完好。
  • 期望来源:《售后服务手册》3.2 节

有了这套结构,你可以精确知道"标准答案在文档里是存在的,如果系统没答出来,那问题出在检索或者生成环节"。

3.2 问题集怎么来:从真实日志出发,而不是拍脑袋

最常见的错误是开发团队坐在一起,凭印象编二三十个"可能有人会问的问题"。这样建的测试集有两个问题:一是覆盖不到真实用户的实际问法,二是容易带上开发者的"已知答案偏好"——因为写问题的人已经知道答案在哪,写出来的问题天然更容易被检索命中。

我的建议是优先从真实数据里挖。三个来源:

  1. 用户搜索日志:把线上搜索框里的真实 query 拉出来,聚类去重,筛出高频且有代表性的作为候选问题。这是最贴近真实场景的来源。
  2. 客服/运营高频问题清单:如果你的系统有客服团队,他们手里一定有一份"被问烂了的问题"清单,这些就是黄金问题。
  3. 文档结构反推:从知识库的章节标题、目录结构出发,模拟"用户想了解这里的内容时会怎么问",补充前两类来源覆盖不到的盲区。

每条问题最好打上难度标签:简单(可从单个段落直接找到答案)、中等(需要拼接多个段落的信息)、困难(需要推理或者答案分散在多个章节)。标注难度的价值在于,评测结果出来时你可以看系统在不同难度上的表现差异,针对性地优化薄弱档位。

3.3 黄金文档怎么标:切分粒度决定可判定性

这一步非常关键,也最容易翻车。标注"相关知识片段"的时候,建议以回答问题所需的最小信息单元为粒度,而不是直接把某个大段落整块拖进去。

举个例子,某文档里有一个大章节讲"产品售后政策",里面同时包含退货规则、换货规则、保修期限三块内容。标注"退货流程是什么"这条问题时,相关知识片段就应该指到"退货规则"那几行,而不是整个章节。原因很现实:如果你的标准上下文是一整块大文本,检索系统只要捞到了章节里的任何一小段,召回率就算满分的,这会带来虚高的分数——系统实际捞到的可能只是章节里一句次要的话,而评分却认为它"完全命中"了。

我操作时的经验是:先自己把标准答案写出来,然后去看这条答案的信息点在原文里落在哪几个句子,把这几句话连同必要的上下文句子一起作为相关知识片段。宁可标得精确一点、范围小一点,这样评测结果才真正反映"系统有没有定位到关键信息"。

3.4 最小规模建议与分布

黄金测评集不是越大越好,起步阶段60~100 条就能跑出有效基线,重点是覆盖度和代表性,而不是盲目堆数量。我自己常用的大致分布是这样:

问题类型占比建议示例
事实型(单一来源直答)40%"退货期限是几天?"
综合型(多来源拼接)30%"退货和换货的适用条件分别是什么?"
推理型(需要归纳推断)20%"哪些情况不符合退货条件,结合换货政策判断我该选哪个?"
否定/边缘型(故意刁难)10%"产品完全没有保修吗?"

规模到 200 条以上之后,边际收益会明显下降,除非你的业务场景特别多元。与其扩充到 500 条,不如把现有 100 条维护好,定期更新、淘汰过时内容。

4. 用开源评测框架把第一轮基线跑出来

黄金测评集准备好之后,接下来就是跑分。这个环节我不推荐自己写评测脚本,直接用现成的开源评测框架更省力,也更不容易埋坑。

4.1 为什么不要自己写评测脚本

我知道很多人(包括我以前)的第一反应是"指标很简单,我自己调大模型 API 评一下不就行了"。实际做下来你会发现问题:自己写 prompt 让大模型打分,打分标准很容易漂移,这次和下次的标准不一致;而且你很难统一管理多次评测的记录、对比历史分数。开源评测框架把这些都封装好了,内置了评测 prompt 模板、打分逻辑、结果报告,你只需要接入自己的数据和系统。

市面上的开源评测框架思路都差不多:你要提供测试集、调用你 RAG 系统的接口,配置一个用于打分的评测大模型,框架自动跑完一轮给出得分明细。大概率你的 RAG 技术栈里已经用了某个框架,先用它自带的评测能力,如果有现成 OpenAI 兼容接口的那就更好,省去额外部署。

4.2 一个最简可跑的评测流程

我把流程拆成三步:准备接口、准备测评集、运行评测。

第一步,确保你的 RAG 系统有一个查询接口,入参是用户问题,出参至少包含最终答案和检索到的上下文片段。这是硬前提——评测框架要拿到这两样东西才能算四个核心指标。

第二步,把黄金测评集整理成标准格式,一般是 CSV 或 JSON。列字段就是你前面标注好的问题、相关片段、标准答案、期望来源。

第三步,配置评测参数:

# 评测配置示意 eval_config = { "testset_path": "data/golden_set.csv", "rag_endpoint": "http://your-rag-service:8000/query", "metrics": ["context_recall", "context_precision", "faithfulness", "answer_relevancy"], "eval_llm": { "provider": "your_compatible_provider", "model": "your_eval_model_name", "temperature": 0 }, "output_dir": "reports/run_20250101" } run_evaluation(eval_config)

注意temperature要设成 0,否则评测大模型打分时如果带随机性,同一批测试集两次跑出来的分数会有波动,影响结果可比性。

框架跑完后,会生成一个报告,包含每个指标的总分和按条目的得分明细。第一轮跑出来的分数就是你的基线,先别急着调参,认真看看这份报告。

4.3 基线表不是成绩单,而是故障分布图

很多人跑完基线第一反应是"分数怎么这么低"。其实分数低不丢人,关键是你要能从报告里读出来"系统到底弱在哪"。

我一般会列一个这样的表来整理故障分布:

条目召回率精确率忠实度相关性初步怀疑环节
问题 1(事实型)0.20.80.90.6检索漏召回
问题 2(综合型)0.90.30.70.8召回噪声太多,带偏生成
问题 3(推理型)1.01.00.50.9生成侧幻觉
问题 4(否定型)0.40.70.90.3没检索到关键内容,答非所问

光看这张表,你就能得到几个结论方向:如果召回率普遍低,问题在检索侧,重点看 embedding 和检索策略;如果召回率还行但精确率低,问题在排序和切块策略,检索结果里塞了太多无关片段;如果检索侧两个指标都高但忠实度低,问题出在生成环节,模型没好好利用上下文;如果四项里就相关性低,那大概率是模型没理解问题意图,需要处理问题改写和多轮对话的上下文。

这就是我反复强调的"评测驱动的调参":你不是在猜,而是拿着故障分布图精准定位。

5. 评测驱动的调参:从分数到动作

有了基线,接下来才是重头戏。这一节我把常见的"低分组合"和对应的调参动作整理成一套可执行的方法论。核心原则是:一次只改一个变量,然后在同一份测评集上重新跑分。

5.1 当上下文召回率低:先别急着调切块大小

召回率低意味着"该捞的没捞上来",这时候下意识去调 chunk_size 的人特别多,但我会优先做下面三件事:

  1. 检查 embedding 模型与领域文本的匹配度。如果你的文档是专业领域内容(法律、医疗、技术手册),通用 embedding 模型效果可能一般,换一个在领域语料上微调过的模型,或者数据量够的话自己做微调,召回率经常会有明显提升。
  2. 检查查询端到检索端的语义鸿沟。用户问得很口语化,而文档写得很书面,两者的向量表征不一致,检索就漏。常见解法是加一层 query 改写:先让大模型把用户问题转成更接近文档表述的检索 query,再去做向量检索。
  3. 检查召回策略是不是太单一。第一版系统往往只做向量检索,但有些信息靠关键词精确匹配更有效(比如型号、编号、人名)。所以混合检索(关键词 BM25 + 向量检索)在很多业务场景下是必要选项。

只有这几步都试过了,召回率还上不去,我才会考虑调 chunk_size 和 top_k。切块太小吃不下一段完整信息,切块太大稀释语义,都需要根据实际内容微调。

5.2 当上下文精确率低:噪声太多,重点治理排序与切块

精确率低的含义是"捞上来了但有很多没用的"。最常见的是 top_k 设置过大,把大量弱相关片段也塞给了生成端。这时候的动作序列是:

  • 先把 top_k 调小试试。从 5 降到 3,往往噪声就能少一截,代价是召回率可能跟着降一点,你需要找到平衡点。
  • 引入或调整排序策略。向量检索初筛之后,再用一个更强的排序模型精排,是提升精确率性价比最高的手段。第一次接的时候可以先用现成的大模型排序接口,效果通常立竿见影。
  • 检查切块策略是不是生成了大量"半截句"。如果你的切块把一段话拦腰截断,剩下的一半没有任何完整信息,自然成了噪声。改成按段落或语义边界切,能明显改善精确率。

我自己的经验是:精确率低的时候,优先看检索结果的排序质量,而不是急着换 embedding。很多时候 embedding 模型本身是够用的,纯粹是策略和参数不配合。

5.3 当忠实度低:检索到了,但答案在脑补

忠实度低说明一个问题:上下文里其实有答案,但生成端没好好用,反而发挥出了一堆上下文里不存在的信息。这种情况调节检索参数基本没用,要往生成侧使劲。

先看你的 prompt 里有没有把指令写清楚,比如"请仅根据提供的上下文回答,不要使用外部知识,如果上下文没有答案请明确说不知道"。别小看这句话,很多开源模型在指令不清的情况下就是会自由发挥。

然后检查上下文是不是被截断了。一些系统为了控制 token,会从检索结果里截取前 N 个片段,如果真正包含答案的片段排在后面被截掉了,模型就只能"无米下锅"式地编造。这种情况要优化的是上下文选择策略,而不是单纯调截断长度。

另外有个小技巧:把忠实度低的条目拉出来人工看一遍,区分是"模型没看上下文"还是"上下文被处理坏了"。前者改 prompt 和温度,后者改管线逻辑,两者不能混为一谈。

5.4 当答案相关性低:答非所问,问题在"理解用户"

相关性低的时候,问题往往不在检索也不在生成,而在系统压根没理解用户想问什么。典型场景是用户的问题里有指代、有口语化省略,甚至包含多轮对话的历史信息。这时候我会优先看:

  • 有没有做 query 理解/改写。原始 query 直接拿去检索,前文已经说过,会因为表述差异导致检索偏移,进而答非所问。
  • 有没有处理多轮对话状态。如果用户问的是"那保修呢?",系统必须知道"那"指的是上一轮提到的产品,否则这个问题本身就无法回答。
  • 有没有对模糊意图做澄清。与其让模型猜,不如在意图不明确时主动反问,这比硬答一个相关性极低的答案体验好得多。

相关性低有时候不是单一环节的问题,而是多个环节叠加的结果。所以我建议结合明细报告,挑几条相关性最低的题,一条条手动跑一遍,追踪从 query 到检索再到生成的完整链路,就一定能找到断点。

5.5 一次只改一个变量:用实验记录代替感觉

最后强调一个纪律:任何一次调参只改一个变量。很多人喜欢同时换模型、改 prompt、调 chunk_size,结果分数涨了也不知道是谁的功劳,跌了也不知道是谁的锅。

我自己的做法是维护一张简单的实验记录表:

实验序号变更项变更前召回率变更后召回率精确率变化忠实度变化相关性变化结论
1引入 query 改写0.620.78+0.05持平+0.08有效,保留
2top_k 从 5 调 30.780.71+0.12+0.03持平精确率提升但召回率受损,继续观察
3引入精排0.710.70+0.15+0.02+0.04有效,保留

这张表积累上二三十行,你对系统的理解会比任何一篇教程都深。因为所有的结论都是基于你自己的数据和业务场景得出的。

6. 评测体系搭建中的坑与长期维护

最后一部分,把我在实际搭建评测体系过程中遇到的坑集中说一遍。这些坑你大概率也会踩,提前知道能省不少时间。

6.1 测试集污染:评测问题被索引进去了

这是个隐蔽但后果严重的坑。如果你用来构建测评集的问题来自真实用户日志,而你的知识库索引里又包含了这些日志文本(比如日志被同步到了向量库),那么评测时系统会直接命中原文,端到端效果虚高,看起来四项指标全都完美,但一上线就原形毕露。

规避方法很朴素:把你的黄金测评集单独管理,严禁同步进向量库,并在每次新增索引数据前检查一遍。如果你怀疑可能已经污染了,就用一批新造的、从未被索引收过的问题重新测一轮,对比分数就能判断。

6.2 评测大模型本身的偏好

评测框架依赖一个"裁判"大模型来打分,而这个裁判是有偏好的。同一个回答,换一个裁判模型,分数可能从 0.7 变成 0.9。这不是什么秘密,所有用大模型做评测的方案都有这个问题。

实操上的建议是:选定一个裁判模型后不要频繁更换,保证历史分数的可比性;如果确实要换,那就用同一批老数据重新跑一遍基线,把新旧裁判的分差找出来,而不是直接对比跨裁判的历史成绩。另外重要条目的分差检查很有价值——当某个指标分数变化超过 10% 时,随手抽两三条人工复核一下,看是系统真的变了还是裁判抽风了。

6.3 指标波动与随机性:别用一次结果下结论

大模型生成天然带随机性,即使你把评测温度和生成温度都设成 0,某些框架的实现仍然可能因为并行调用、缓存等产生微小波动。所以我建议每次评测至少跑 2 到 3 遍,取平均值作为报告值,特别是当你的测试集只有几十条时,单条条目的波动会直接影响总分。

如果你的测试集太小(比如 30 条),随便更换一条测试问题总分都可能跳好几个点。这种时候与其纠结 0.05 的分差,不如把精力放在扩充测试集上,样本量上来之后,分数稳定性才会改善。

6.4 黄金集也要维护:新文档、新场景、新问题

知识库会更新,业务会变化,你的黄金测评集也必须跟着走。我见过最典型的反面案例是:测评集三个月没动,系统版本迭代了一堆,评测分数一路走高,但用户反馈越来越差——因为评测集里的问题都是老问题,系统早就被针对性优化过了,新场景完全没覆盖。

我的维护节奏是:每次知识库有显著新增时,给新内容补充 5~10 条测评问题;每月抽 10% 的老问题人工复核,确认标准答案没有过期;每季度做一次全量回归,让新旧问题同场跑分,看趋势变化。这套节奏听起来麻烦,实际每次只需要半小时到一小时,换来的却是"评测结果始终可信"这个最重要的底层能力。

6.5 最后分享一个我自己的小习惯

现在每做一个 RAG 项目,我会强制自己在写第一行业务代码前,先建好最小可用的评测基线。哪怕只是 30 条问题、跑一遍四指标,也必须在第一轮代码之前完成。这样做的效果是:整个开发过程里,每写一个功能、每改一个参数,我都能立刻看到它对四个核心指标的影响,而不是等系统搭完才回头补评测。这个习惯帮我在好几个项目里避免了大返工,那种"上线前才发现检索方案选错了"的绝望感,你们都懂的。评测给你的是定心丸,也是方向盘。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询