跳转到内容

第 45 章 从零构建有引用的 RAG

45.1 搜索结果怎样变成有根据的主张?

Section titled “45.1 搜索结果怎样变成有根据的主张?”

对“北京住宿”,第 44 章把现行制度排在了第一位。助手仍有许多事要做:取回原文、保留限定条件、装进本次输入、产生答案,再把主张与证据连起来。生成之后随手附一份参考书目,不能证明这种联系。

检索增强生成(retrieval-augmented generation,RAG)在产生答案时使用检索到的外部材料。本章走通本地完整路径,但先明确答案由谁产生:可运行的生成器是逐字取用来源的抽取式组句器(extractive composer),不是 LLM。它按明文规则复制适用证据并附来源地址,因此无需账号就能观察每一步。生成器接口可以替换;接入神经模型、测量回答质量属于额外实验,本章没有用回放或虚构记录冒充。KA-1 的两个检查点仍未通过验收。

45.2 先解析原文,再决定怎样缩短

Section titled “45.2 先解析原文,再决定怎样缩短”

例 45.1:750 从哪里来。 ka4-documents-v1 将 KA-0 的虚构来源扩展为可以按行定位的双语文档,原索引卡和标注不变。现行中文文档全文如下:

表 45.1:travel-v2,版本 v2,原文各行。

行号确切文字
1虚构北京差旅住宿制度;金额单位为元/间夜。
2自 2026-01-01 起生效,替代 travel-v1。
3住宿限额为 750 元。

第 3 行提供金额,第 1 行给出地点和单位,第 2 行规定生效时间。旧制度第一行相同,随后两行分别为“适用于 2025-01-01 至 2025-12-31。”和“住宿限额为 600 元。”来源均是作者创作、按 CC BY-SA 4.0 提供的教学材料,不是下载的公司制度。

解析(parse)把存储的文档转换成一致的文本表示。本实现保留 UTF-8 文本,按明确的换行拆开,不做 OCR、不推断表格列,也不修复破损 PDF。切块(chunking)为较小片段建立稳定标识。解析器每行产生一块,保存从 1 开始的行号,以及从 0 开始、包含左端点而不含右端点的码点偏移;结束偏移不包含换行。中英文偏移不同,中文引用必须回查中文原文,不能照搬英文偏移。

只有金额的一块似乎很适合搜索,却不能支持完整的报销主张。因此,本项目区分用于搜索的子级索引卡与父文档的确切原文。继承的 B 卡是 beijing lodging lodging 750,这是作者写的检索摘要,不是引文,指向 travel-v2。命中它后,应用取回父文档第 1—3 行作为回答证据。逐行小块仍保留,用于诊断切分错误或过度压缩。小系统采用透明的人工索引卡;扩大规模时,还要检验这些卡片或文本块怎样产生。

45.3 元数据也是证据契约的一部分

Section titled “45.3 元数据也是证据契约的一部分”

父记录保存来源 ID、版本、事项、有效期、发布方、许可和 book:// 来源 URI。URI 是本地来源标识,不表示浏览器一定能打开。完整语料与摘要值保证来源可以回查。“现行”要按请求指定的日期判断,不能看电脑时钟,也不能取最后入库的文件。

表 45.2:一份请求经过哪些步骤。

步骤具体输入 → 输出可以检查什么
Parse,解析双语来源 → 确切行文本原文和偏移
Chunk,切块行与人工索引卡 → 有地址的片段子块标识、父文档关联
Metadata,元数据来源 → 版本、事项、日期、发布方、许可有效期和来源
Index,建索引原 KA-0 卡片 → 规范词列表分词器、长度和集合统计
Retrieve,检索问题及明确日期、事项 → 候选完整分数与适用候选排序
Pack,装配选中父文档各行 → KA-2 上下文确切序列化文本及长度
Generate,生成上下文 → 抽取式答案对象明确的生成方与候选答案
Cite/Abstain,引用或拒答对象 → 核查后的答案或拒答分支来源回查、适用性和支持关系

实现先对所给完整集合计算 BM25 统计,再在取首项前过滤适用范围,不会把只剩一份文档的集合拿来悄悄重算词的文档频次。命中一个来源后,两种实验条件都会检查事项、发布方和生效日相同的并行版本,使矛盾制度有机会进入检查。第 46 章的基线仅在“是否先过滤再选择”这一点上不同。

有支持的路径
  1. 文本片段B 卡 → travel-v2
  2. 模型之外第 1—3 行:单位、日期、750
  3. 处理步骤装配 → 抽取 → 检查
  4. 文本片段750 [travel-v2:1-3]

卡片用于搜索,引用取自原文。

范围丢失
  1. 文本片段只留第 3 行:750
  2. 处理步骤缺少单位、日期 → 拒答

数字正确,不足以支持完整主张。

图 45.1:引用让来源限定条件与金额一起保留。 原创教学图示 · CC BY-SA 4.0

图 45.1 让 750 与限定它的信息一起经过全流程。来源地址在检索、装配上下文和产生答案时始终保留。预算在生成器收到输入前就被检查,不是先丢掉日期,再声称内容已经放下。

对于英文 Beijing lodging,明确指定日期 2026-09-14、事项 travel 后,B 是唯一适用候选,其 BM25 分数仍为 1.574906。未过滤的完整分数顺序与第 44 章一致,为 B、A、C、D、E。中文 北京住宿 得到相同卡片分数。这里的日期和事项是 RAG 实验明确提供的输入,不能倒过来说此前只拿查询文字做的排名比较也收到了这些信息。

实际英文上下文就是第 40 章展示的 JSON,仅将末尾问题改成 Beijing lodging,其余字段及三行完整证据不变。中文对应改成 北京住宿。因此,包含历史示例在内的完整输入都能从正文还原。紧凑序列化后的实测长度分别为 591 和 348 个码点。总容量 1,400,预留输出 200,英文还剩 1400−200−591=6091400-200-591=609,中文还剩 1400−200−348=8521400-200-348=852。这些不是模型分词器测量值。

组句器返回下面的有来源文本,刻意保留抽取式形式:

虚构北京差旅住宿制度;金额单位为元/间夜。 自 2026-01-01 起生效,替代 travel-v1。 住宿限额为 750 元。 [travel-v2:1-3]

对应对象的状态为 answered,amount_yuan 为 750,原因为 null;引用保存版本 v2、起止行号 1 和 3,以及表 45.1 的确切文字。应用按地址从版本化语料取回这些行,检查有效期、含单位的父文档范围、金额,以及逐字抽取的答案。仅仅证明来源存在,还不能通过。

最终对象在答案和引用字段中重复保存了证据。紧凑序列化后,英文实测为 502 个码点,中文为 346,均超过示意用的 200 码点预留。这符合第 40 章只检验输入装配的本地实验范围,却不能证明模型输出上限已经生效。接入模型时,要把结构和引文一起计入所需输出容量,并明确处理截断。

这里的主张—支持关系(claim-support relation)范围很窄:一段答案作出制度断言,金额、地点、单位和日期必须一同保留。校验器要求逐字复制,并完整覆盖已标注的支持范围;在本语料中,这个范围就是父文档的全部三行。它不理解任意改写,也不是通用语义蕴含判断器。若模型换一种同义说法,需要另一个经过评测的支持校验器或人工裁决。当前检查拒绝同义改写,是实现限制,不能证明改写本身错误。

45.5 证据路径不足以支持答案时,就明确拒答

Section titled “45.5 证据路径不足以支持答案时,就明确拒答”

例 45.2:只改变一个条件。 保留问题,从可用语料中移除第二版。第一版确实存在于虚构档案,却不适用于指定日期。过滤后没有适用的住宿来源,系统返回:

{"schema_version":"ka-answer-v1","status":"abstained","answer":null,"amount_yuan":null,"citations":[],"reason_code":"insufficient_evidence"}

再换一种条件:新增同一发布方、同一生效日的制度,却写着 800。诊断语料单独标明这是作者刻意构造的矛盾。两个适用版本都进入上下文,组句器发现两个金额不一致,返回 conflicting_evidence,不会选择较大数字,也不会把分数并列时碰巧排前的来源当成裁决。

表 45.3:静态失败对照。

改变的条件可以观察的失败或安全结果为什么不能据此作答
只剩已过期的 600 来源没有适用证据,拒答档案内容为真不等于当前有效
查询护照换发不支持的任务或无证据分支语料没有所需内容
同等权威的现行来源分别写 750 和 800因矛盾拒答排名不能裁定制度权威
只保留第 3 行因来源不完整拒答金额失去了单位和适用条件
总容量缩至 250,仍预留 200因预算不足拒答完整输入放不进剩下的 50 个单位
答案提议为 600,却引用 v2Schema 通过,支持关口拒答被引原文与金额矛盾
答案提议为 750,却引用 v1引用关口拒答数字正确不能修好来源错误

证据不足时暂不作答,称为 abstention,不同于第 41 章的模型服务安全拒绝或技术错误。本地程序也明确不处理超出组句能力的任务,包括把订单常见问题当成实时状态。第 42 章的读取工具,才是那一类请求应使用的来源。

45.6 运行时检索不会更新模型参数

Section titled “45.6 运行时检索不会更新模型参数”

若把组句器换成参数固定的语言模型,更新检索制度会改变输入,不改变模型参数,这正是第 2 章强调的运行边界。但 RAG 研究也可以包含训练。Lewis 等人的原始架构联合训练查询编码器和 BART 生成器,固定文档编码器与索引,并对检索出的隐文档变量作边缘化。它与本书显式的解析、切块、装配、引用流水线不同,不能把后来所有工程技巧都追溯给那一篇论文。

RAG 仍会失败:文档选错、限定条件缺失、证据带恶意指令、组句不忠实、引用无支持,都可能穿过部分环节。引用检查只能减少明确范围内的错误,不能证明真实世界中的语料正确、完整且最新。本例的虚构发布方与日期都是明示假设。

rag.py 将共享文档、分词器、BM25、KA-2 装配器与 Schema,以及可替换的生成器函数接起来。rag_eval.py 对同一个函数运行固定场景,包括上述失败对照。02-retrieval-rag.ipynb 在 CPU 上执行并展示解析块、索引词项、候选分数、上下文、候选答案、支持检查和最终结果。记录标明 local-extractive-v1,不冒称商业模型或回放后的生成实验。

data/part-vii/rag-traces.json 保存完整结果,来源摘要能检测语料变化。继承资产冻结清单确保扩展语料没有覆盖 KA-0、已有学习表示或 KA-1 评测。以后接模型时,须记录模型和分词器版本、解码配置及实际响应,再通过相同证据和任务关口。适配器存在,不等于模型通过验收。

有引用的 RAG 把原文、有地址的片段、检索、实际上下文、答案和支持检查连成一条可查路径。版本、单位、日期与金额一起传递。本地可运行组句器让各步可见,证据契约不满足就明确拒答。要继续改进,还需要分别定位每次失败来自哪一层。

  1. 一个块只有“住宿限额为 750 元”。要回答北京在 2026-09-14 按元/间夜计算的限额,还需要取回什么?
  2. 两份同等权威、同日生效的制度互相矛盾,重排分数分别为 0.9 和 0.8。这足以解决冲突吗?应输出什么?
  3. 生成答案说 750,并引用一份确实存在、却写着 600 的旧文档。来源存在、金额正确、引用支持,哪些可以分别通过?
  4. 明天原文改了,索引里却还是旧卡片。诊断时需要保留哪两类资产?第一步应该重新训练模型吗?
  1. 还要取回父文档中说明北京、元/间夜单位和生效日期的行。数字对了,缺少限定条件仍然不够。本地校验器要求三行完整保留。
  2. 不足以解决。相关性分数不能证明权威,也不能裁决矛盾规则。应保留两个来源,以冲突原因拒答,并在自动回答路径之外寻求权威澄清。
  3. 来源存在可以通过,750 对现行制度也可以正确,但引用支持失败,因为引文规定 600,且已经过期。正确答案配错误引用,仍不是合格的有引用答案。
  4. 保留原文及版本摘要、带构建来源的索引卡或切块表示,并检查检索与上下文记录。先修复过期索引或来源路径,复跑固定案例;这个失败本身不能说明参数需要更新。