Chapter 03

质量与评估:让证据被用上,把失败定位到漏斗哪一阶

前两章把召回→精度漏斗搭完了:hybrid 在检索阶保召回,重排在第二阶保精度。证据递到了门口——这一章管两件事:怎么逼生成真的用证据(grounding),以及怎么把"答得好不好"拆成检索质量和生成质量两把尺子分开量,从而把每一个失败定位到漏斗的具体某一阶。

本章你将建立的 schema

  • 三个 grounding 杠杆:grounding 提示、citation 强制溯源、abstention 拒答
  • 分块三难:recall / precision / context 预算,三者不可兼得
  • 评估 2×2:检索侧(context precision/recall)× 生成侧(faithfulness/answer relevance)分开量
  • 失败模式按漏斗分段:索引 → 检索 → 生成,每个症状对应一阶 + 一个指标

沿用全程的场景:公司内部问答助手,语料是几百页员工手册 PDF,用户问"出差住宿的报销上限是多少?"。前两章保证了"对的那段证据有机会进候选、并被顶到前列"。这一章假设证据已经递到 prompt——问题变成:模型会用它吗?答错了,错在漏斗的哪一阶?

3.1让生成扎根:grounding 的三个杠杆

生成扎根 = 用提示、引用、拒答三个杠杆,把"基于给定证据回答"从一个建议变成一道尽量硬的约束——但它始终是概率性的,不是强制的。

为什么需要它

检索和重排只解决了"对的证据在不在 prompt 里"。§1.7 的 grounding 概率契约说得很清楚:检索质量是答案质量的上限,但拿到完美证据也不保证答对。证据进了 prompt,模型仍会用参数化记忆覆盖它、在多段之间错拼、或干脆含糊带过。这三个杠杆就是用来收窄这道缝隙的工程手段。

底层机制(比文档深一层):把证据拼进 prompt,改变的是模型生成下一个 token 的概率分布——让"能从上下文里抄到的 token"获得更高概率。这是 grounding 唯一的物理基础:它抬高了照抄证据的概率,但没有锁死它。生成仍是从整个词表上采样,参数化记忆里那条"住宿一般报 500"的高频路径依然在竞争。所以三个杠杆都是在同一个概率分布上施加偏置,没有一个能把幻觉概率压到零。

三个杠杆,按作用点排列:

  • grounding 提示:在 system prompt 里写死"只依据给定证据作答,证据未覆盖的部分不要编"。它直接给"照抄上下文"这条路径加权,是最便宜、最先上的一道。
  • 引用 / citation 强制溯源:要求模型为每个论断标出处(chunk id / 页码),并在后处理里校验这条引用确实支持该句。强制溯源会抑制编造——模型要给不出处的句子找一个出处,比直接顺口编要难,于是更倾向于只说证据支持的话。
  • 拒答 / abstention:给模型一条显式的"不知道"出路,并配一个低置信阈值(检索得分太低 / 证据不覆盖问题时触发)。没有这条出路,模型面对"证据不足"时只能硬答,幻觉就在这里产生。
陷阱 · 过度拒答

grounding 拧太紧会反噬:阈值定太高、提示措辞太严,模型会把本可以答的问题也拒掉(证据其实够,但措辞和 query 不完全字面对齐,模型保守地说"不知道")。grounding 强度是一个要调的旋钮——一端是幻觉,另一端是过度拒答,没有免费的中点。这条 trade-off 在 §3.3 会变成 faithfulness 与 answer relevance 的此消彼长。

3.2分块策略的三难

分块大小是一个三难选择:recall、precision、context 预算,没有任何一个 chunk size 能让三者同时取胜。

为什么需要它

§1.3 把分块定为漏斗第一段的入口决策:它决定"理想答案片段"能不能成为一个可被召回的单元。这一节把当时按下不表的"分块策略"展开——因为分块切坏了,后面 hybrid、rerank、grounding 全在给一个有缺陷的输入做补救。

底层机制(比文档深一层):三难来自三个互相拉扯的量。recall 要求一个完整答案别被切散——倾向切大,让答案连同它的上下文待在同一个 chunk 里。precision 要求一段只讲一件事,嵌入向量语义锐利、能在排序里顶上来——倾向切小,避免一段塞多主题被稀释成"平均向量"。context 预算是第三个约束:chunk 越大,同样 top-k 喂进 prompt 的 token 越多、越贵,且把噪声一起带进去。这三个量没有公共最优点——把一个拉好,另一个就退。

分块策略 · 备选方案
策略怎么切赢在哪盲区
固定大小 每 N token 硬切,配 overlap 实现简单、可预测、批处理快 边界盲——把一句话、一张表从中间劈开
递归 / 结构感知 优先在段落、标题、表格边界切,再退到句号 尊重文档结构,答案片段更完整 依赖文档有干净的结构标记
语义分块 在相邻句嵌入相似度骤降处下刀 切点落在话题转折处,单段主题纯 要先逐句嵌入、算开销大;阈值需调

落地的经验数值(起点,不是定论):事实型问答(查一个上限、一个日期)用 128–256 token 的小 chunk,让命中段语义锐利;分析型 / 长推理用 512–1024 token,保住论证的上下文。无论哪档都加 overlap(相邻 chunk 重叠几十 token),防止答案正好落在切口被拦腰截断。表格先转成 HTML(保留行列与表头的从属关系)再分块——这正是 §1.3 报销表那个例子要的:别让"800 元"和它的表头"住宿费上限"落进两个 chunk。

recall precision context 预算 切大 → 答案完整 切大 → token 涨 切小 → 向量锐利 chunk size 一个旋钮,三方拉扯
图 3.1分块三难:调大 chunk 同时改善 recall(答案不被切散)却恶化 precision(向量被稀释)和 context 预算(token 变贵)。注意:三个顶点没有公共最优解——这就是为什么"事实型切小、分析型切大"是按问题类型分流,而不是找一个万能 chunk size。
想一想

团队把所有文档统一切成 1000 token 的大 chunk,理由是"上下文更全、答案更完整"。员工手册里"住宿上限 800 元"这类一句话事实的检索质量,会因此变好还是变差?(先停十秒)

展开答案(先自己答)

变差。1000 token 的 chunk 里塞了一整节的多个主题,嵌入向量是这些主题的"平均",对"住宿上限"这个具体问题不够锐利,排不进前列——recall 看似保住了(答案没被切散),precision 却塌了。这正是三难:拉大 chunk 改善了一个量,牺牲了另一个。事实型问答该往 128–256 token 切。

3.3评估闭环:把检索和生成分开量

RAG 的核心评估是一个 2×2:检索侧量 context precision / context recall,生成侧量 faithfulness / answer relevance——四个指标各管一件事,混在一起就无法定位失败。

为什么需要它

一个端到端的"答得对不对"分数没法用来 debug:分数低,是检索没捞到证据,还是证据捞到了但模型没用好?这是两个完全不同的修法(一个改分块/hybrid,一个改 grounding/prompt)。召回→精度漏斗本来就分阶,评估也必须沿同一条漏斗分阶量,才能把分数映射回"该动哪一段"。

底层机制(比文档深一层):四个指标不是四个"得 0.8 还是 0.9"的黑盒,每个底下都有一套可拆的判据。

  • context precision(检索侧):检索到的 chunk 里,相关的占比多高、且排得靠不靠前。机制上是对 top-k 逐位算 precision@k 再取均值——相关的排在前面得分更高。回答"捞回来的这堆,是不是大多有用、噪声多不多"。
  • context recall(检索侧):需要的证据是不是都被检索到了。机制(比"得分"深一层):把 ground-truth 参考答案拆成若干 claim,逐条检查该 claim 能否归因到检索到的上下文,支持的占比就是 recall。回答"答对所需的证据,有没有漏在门外"。
  • faithfulness(生成侧):答案是不是扎根于检索到的上下文、有没有编造。机制:把生成的答案拆成一条条 claim,逐条检查该 claim 能否从检索到的上下文里推出(LLM-as-judge,让模型当裁判逐条判定),被支持的 claim 占比就是分数。回答"模型有没有顺嘴编了上下文里没有的东西"。
  • answer relevance(生成侧):答案切不切用户的问题——完整、不跑题、不答非所问。注意它不管对错,只管"答的是不是问的那件事"。

为什么四个都要:因为它们会解耦。系统完全可以 faithfulness 很高、context recall 很低——模型忠实地基于一份不全的证据作答,每句话都能在上下文找到出处(faithful),但上下文本身缺了关键的那一段(recall 低),于是忠实地答错。反过来也成立:检索捞回了完美证据(recall 高),生成却无视它编了一段(faithfulness 低)。四个指标分属漏斗两端,只有分开量,才知道是检索阶欠债还是生成阶欠债。

想一想

只有 faithfulness 一个指标,分数 0.95。能不能据此说"这套 RAG 答得准"?如果不能,缺的那把尺子量的是漏斗哪一阶?(先停十秒)

展开答案(先自己答)

不能。faithfulness 只问"答案的每句话是否被检索到的上下文支持"——它对"上下文本身全不全"是盲的。检索若漏了关键证据,模型会忠实地基于残缺上下文作答:faithfulness 照样 0.95,答案却是错的。缺的是 context recall,量的是检索阶"漏没漏"。这正是 §3.5 进阶挑战那道题的内核——两个生成侧指标管不到检索阶的漏。

检索差 · 生成好 忠实地答错 修分块 / hybrid 否则 grounding 帮不上 双高 · 理想区 证据齐 + 答得忠实切题 双低 先修检索,再看生成 检索好 · 生成差 证据齐却幻觉/跑题 修 grounding / prompt 检索质量 → context precision / recall 生成质量 → faithfulness / answer relevance
图 3.2评估 2×2:横轴是检索质量(context precision/recall),纵轴是生成质量(faithfulness/answer relevance)。注意:左上角"检索差+生成好"最隐蔽——模型忠实地基于残缺证据作答,faithfulness 漂亮但答案是错的,这时拧 grounding 没用,得回去修分块/hybrid。右下"检索好+生成差"才是 grounding/prompt 该管的区。把端到端分数拆到这张图上,才知道往哪一阶使劲。

工具与前提:RAGAS(把上面四个指标做成了开箱即用的实现)、TruLens、DeepEval 三套自 2024 年起就稳定可用,都按"检索侧 / 生成侧分开量"组织指标。共同前提是要有一批带标注的 QA pairs(问题 + ground-truth 答案,context recall 尤其依赖参考答案来拆 claim)。还有一条原则:评估是持续跑的回归,不是上线那天量一次就完——语料在变、模型在换、query 分布在漂,§3.4 会给出"为什么离线评估会骗你"的硬理由。

洞察 · 指标即漏斗的探针

把四个指标按漏斗摆开:context recall 探的是检索阶"漏没漏",context precision 探的是重排阶"排得净不净",faithfulness 探的是生成阶"编没编",answer relevance 探的是"答的是不是问的"。一个低分指标,直接指向漏斗的一段——这就是分开量的全部价值:把"答得不好"翻译成"漏斗第几阶欠债"。

3.4失败模式:按漏斗分段

RAG 的失败几乎都能挂到漏斗的某一阶——索引/分块、检索、生成。按阶分类,是因为每一阶的失败有不同的症状、根因和修法。

为什么需要它

用户只会报告"答得不对"。把这个笼统症状对上漏斗的某一阶,是从"它坏了"到"这里坏了、这么修"的关键一跳。下面按 §3.3 的探针顺序,逐阶列常见失败模式。

索引 / 分块 检索 生成 chunk 太小 / 太大 边界盲切 索引陈旧 freshness dense 漏精确词 query-doc 不对称 lost-in-the-middle metadata 误杀 证据对仍幻觉 不会拒答 没抽取出 context recall↓ recall / precision↓ faithfulness↓ 每一阶有自己的失败模式和探针指标
图 3.3把失败模式挂回召回→精度漏斗:索引/分块阶的失败让 context recall 掉,检索阶让 recall/precision 掉,生成阶让 faithfulness 掉。注意:同一个"答得不对"的症状,根因在哪一阶并不写在脸上——先用 §3.3 的四个指标定位是哪一阶欠债,再对着本阶的失败模式找修法,而不是一上来就调 prompt。

索引 / 分块阶

陷阱 · chunk 太小

症状:检索命中一句话,但答案需要的上下文丢了("上限 800 元"——哪个城市档?)。根因:切得太碎,完整答案被拆散。方向:加大 chunk 或加 overlap;事实型也别小过 128 token。指标上表现为 context recall 偏低。

陷阱 · chunk 太大

症状:相关 chunk 明明存在,却排不进检索前列。根因:一段塞多主题,嵌入被稀释成平均向量,对单一问题不够锐利。方向:缩小 chunk、按结构切。指标上表现为 context precision 偏低。

陷阱 · 边界盲切

症状:一张报销表被从中间劈开,"800 元"和表头"住宿费上限"落进两个 chunk,检索到数字却丢了它修饰的对象。根因:固定大小硬切,不尊重表格/段落边界。方向:结构感知分块、表格转 HTML 再切(§3.2)。

陷阱 · 索引陈旧 / freshness

症状:源文档上周已删,用户却拿到一段出处是那份旧政策的答案——正是 ch.01 进阶挑战那道题。根因:删除发生在源文档,但查询侧读的是索引;删源文件时,索引侧的写路径没有同步把对应 chunk 的向量从索引里删掉。方向:把索引更新(增 / 删 / 改)接进文档生命周期,让删除事件触发索引侧重跑——这是索引侧的写路径问题,不是检索或生成能补救的。

检索阶

陷阱 · dense 漏精确词

症状:问 1099-MISC、SKU-7741 这类罕见精确 token,命中的却是一堆"沾边但不是这个"的段落。根因:dense 嵌入的有损压缩抹平了精确字形。方向:上 hybrid(dense + BM25),让精确词项检索补盲。

陷阱 · query-doc 不对称

症状:用户用大白话短问("住宿能报多少"),文档是书面长句("差旅住宿费用报销标准"),向量没贴近,召回掉。根因:问句和文档句式/长度不在一个分布上。方向:查询改写 / HyDE 把 query 拉到文档那侧(04 章展开)。

陷阱 · lost-in-the-middle

症状:正确证据进了 prompt,但夹在一长串上下文的中间,模型像没看见。根因:Liu 等 2023 测出长上下文里存在位置偏置——模型对开头和结尾的信息利用得好,中间的差,呈 U 形。一个要紧的细微差别:这是经验测量结果,不是 RoPE 之类位置编码机制必然导致的;而且在更长上下文的模型上仍然存在。方向:靠重排把最相关的证据顶到首尾位置,而不是指望更大窗口。

陷阱 · metadata 过滤误杀

症状:加了"部门=HR"之类过滤后,本该召回的证据被滤掉了。根因:过滤条件比意图更窄,或 metadata 标注本身有缺。方向:放宽 / 校验过滤字段;过滤应缩小范围、不该误杀正解。

生成阶

陷阱 · 检索对了仍幻觉

症状:3 段证据都正确明确,模型仍答错(用参数记忆里的"500 元"覆盖,或把"二线 600"张冠李戴到一线)。根因:grounding 概率契约——证据正确 ≠ 答案正确。方向:强化 grounding 提示 + citation 校验(§3.1)。指标上表现为 faithfulness 低。

陷阱 · 不会拒答

症状:证据其实不覆盖问题,模型不说"不知道",硬编一个答案。根因:没有显式拒答路径和置信阈值。方向:加 abstention 出口 + 低置信阈值(§3.1),注意别拧到过度拒答。

陷阱 · 没抽取出

症状:答案明明在上下文里,模型却略过了。根因:噪声或互相矛盾的 chunk 太多,把正解淹没;或证据埋在 lost-in-the-middle 区。方向:重排压噪声、减少喂进生成的 chunk 数——堆更多文档反而更糟(见下)。

两个反直觉 · 必须显式说破

(1) 验证只有在生产里才真正可行。Barnett 等 2024(Seven Failure Points of RAG)的核心观察:RAG 的多数失败点要到运行时才暴露——真实 query 的分布、文档的脏边角、检索与生成的耦合,离线测试集盖不全。离线"看起来对"会误导你,把没真正验证的系统当成验证过的。

(2) 更大的上下文反而更糟。把窗口开大、塞更多文档进 prompt,不仅修不了 lost-in-the-middle(位置偏置照在),还会降低抽取准确率——更多 chunk = 更多噪声和潜在矛盾,正解更容易被淹没。"召回不够就多塞点"是错的方向;该做的是重排提精度,不是加量。

3.5综合:从症状到漏斗坐标

这一章给了一张诊断映射:任何"答得不对",都能顺着两步落到漏斗的具体一阶——先看检索侧两个指标(recall 漏没漏、precision 净不净),再看生成侧两个指标(faithfulness 编没编、answer relevance 切不切题)。哪个低,指向哪一阶。

症状 → 漏斗定位速查
症状低分指标漏斗阶方向
命中一句、丢了上下文context recall索引/分块加大 chunk / overlap
相关段排不进前列context precision索引 + 重排缩小 chunk / 上重排
精确词(编号/型号)漏召context recall检索hybrid + BM25
证据齐却答错faithfulness生成grounding / citation
证据不足却硬答faithfulness生成abstention 拒答
答得忠实但答错了问题answer relevance生成 / 检索先查 recall 再查 query 改写

把三章串起来:分块(§1.3 / §3.2)决定了"理想答案片段"能不能成为一个可召回单元——它是漏斗的入口闸门;检索(hybrid)保住召回、重排(§2.4)逼出精度,把对的证据顶到 prompt 首尾;生成端的 grounding(§1.7 / §3.1)在概率分布上施压,让证据真的被照抄而非被参数记忆覆盖。评估 2×2 是贯穿这条链的探针组:context recall/precision 量前半段(证据进没进、净不净),faithfulness/answer relevance 量后半段(用没用、切不切题)。漏斗是结构,指标是仪表——同一条线,从入口量到出口。04 章的查询改写、Contextual Retrieval、Agentic RAG,每一个都还是插在这条漏斗的某一段上。

§本章 self-check

先合上教程,把答案写在纸上或编辑器里。写完再点开对照——直接点开等于把这一节当再读一遍。

  1. 答案忠实但答错了用户的问题,最该先看哪个指标?为什么不是 faithfulness?
  2. faithfulness 和 context recall 各自的判据是"逐条检查 claim",但拆的是谁的 claim、对照什么?两者怎么区分?
  3. 团队抱怨"召回不够",提议把上下文窗口开到 4 倍、多塞文档进去。按本章,这为什么是错方向?该怎么做?
  4. grounding 提示拧得越严越好吗?说清它另一端的代价,以及这代价对应 2×2 里哪个指标。
答案(先做完再展开)
  1. 先看 answer relevance。faithfulness 只回答"答案的每句话是否被检索上下文支持"——答案完全可以句句忠实于证据,却答的不是用户问的那件事(跑题但忠实)。faithfulness 高分根本不看"切不切题",所以它对"答错了问题"是沉默的;answer relevance 才是量"答的是不是问的"那把尺子。
  2. faithfulness 拆的是生成的答案的 claim,逐条对照检索到的上下文,看能否推出——量"编没编"。context recall 拆的是 ground-truth 参考答案的 claim,逐条对照检索到的上下文,看能否归因——量"漏没漏"。前者属生成侧、后者属检索侧;同样是"逐条查 claim",但一个查"答案对不对得上证据",一个查"证据够不够支撑标准答案"。
  3. 更大的窗口修不了 lost-in-the-middle(位置偏置仍在,证据夹中间照样被忽略),而且多塞文档会引入更多噪声和矛盾,降低抽取准确率。"召回不够"的正解是上 hybrid 补召回、靠重排提精度把最相关的顶到首尾,而不是加量。
  4. 不是。拧太严会导致过度拒答——证据其实够、只是措辞和 query 不字面对齐,模型也保守地说"不知道"。代价对应 answer relevance 下降(该答的没答、不切题)。grounding 强度是一个旋钮:一端是 faithfulness 掉(幻觉),另一端是 answer relevance 掉(过度拒答)。
进阶挑战 · 刚好够不着

两个指标都很漂亮,用户却投诉"答得不对"

离线评估跑出来:faithfulness 0.95、answer relevance 0.9,看着都很好。可用户实测一直投诉"答案不对"。这两个漂亮分数里,是哪一个在说谎?真正欠债的指标是谁?

提示(卡住再展开)

这两个分数都只看"答案 vs 检索到的上下文"——faithfulness 说答案忠实于上下文,answer relevance 说答案切题。但它们都没看上下文本身全不全。如果检索漏了关键证据,模型就会忠实地、切题地基于一份残缺证据作答:每句都对得上手里的上下文(faithfulness 高)、也确实在回答这个问题(answer relevance 高),可手里的上下文压根缺了那段——答案因此是错的。该看的是 context recall(对照图 3.2 的左上象限:检索差 + 生成好 = 忠实地答错)。两个生成侧指标没说谎,它们只是管不到检索阶的漏——这正是必须四个指标分开量的理由。