架构 · 2020

朴素 RAG

回答前先检索一段外部资料,再交给模型阅读。

成熟 · 使用现成模型

要解决的问题

模型只凭训练时记住的参数回答。知识会过时,遇到参数里没有的事实时,也会把句子写得很确定。

相对前一代的变化

开放域问答里早就有「先检索、再阅读」。Lewis 等人把这个结构接到生成模型上:检索器负责找文档,生成器负责写成答案,而不是只从候选句子里挑一句。

做法

原文件先解析成带页码和来源的正文,再切成块并建成索引。问题到来时取出固定数量的块,拼进提示词,由生成模型作答。检索和生成分成两步,中间不再判断这些块是否真的有用。

局限

检索偏了,模型仍会把偏的材料写进答案。它也不区分哪些问题其实不需要检索。块的大小、数量和提示词格式,都会直接改变结果。

在链路中的位置

这是整条链路的基线。查询改写、混合检索、重排序和按需检索,都是在这次固定检索之前或之中插入判断。

详解

「详解」根据 Datawhale《All-in-RAG》 的 RAG 简介、四步构建 RAG 改写,有删节和重组,未包含原教程的代码、图片和练习。原教程与本节详解均按 CC BY-NC-SA 4.0 许可提供:使用时需署名并标明修改,不得用于商业目的,再次改编须以相同许可共享。

外部资料补上模型没记住的部分

模型权重里的知识在训练结束时就固定了。事实更新、企业内部文档和训练语料里很少出现的细节,都不能靠再问一遍提示词变出来。检索增强要做的事,是在生成之前先从外部知识库取出相关材料,把这些材料写进上下文。

可以把它看成开卷考试。模型仍负责组织和表达,但依据来自这次查到的资料,而不是只来自参数里的模糊记忆。资料可以更换、可以按权限裁剪,也可以留在本地,不必为了补一条知识就重新训练模型。

离线建库,在线只做检索和生成

一条朴素链路分成两段。离线阶段把文档切成块,用嵌入模型编成向量并写入索引。在线阶段只编码用户问题,按相似度取回固定数量的块,再把问题和这些块一起交给生成模型。

检索阶段负责找资料,生成阶段负责按提示词使用资料。两步之间没有质量判断:取回的块无论偏不偏,都会进入提示词。这也是后面查询改写、重排序和按需检索要插入的位置。

从一条直线长成可编排的模块

初级形态就是上面的直线:索引之后,检索直接接到生成。高级形态仍是一条主链路,但在检索前增加改写,在检索后增加重排、压缩或过滤。模块化形态把这些步骤拆成可以替换的零件,并允许按问题动态选择路径。

  • 初级链路便宜、好排查,效果却不稳定。块的大小、取回数量和提示词格式都会直接改变答案。
  • 高级链路能修几个固定弱点,流程本身仍然写死。
  • 模块化链路可以组合路由、查询转换和多路融合,系统也因此更难观察和评估。

先改提示,再检索,最后才考虑微调

提示工程、检索和微调解决的不是同一件事。提示工程改变问法,适合模型已经知道、只是没按期望方式回答的任务。检索改变模型这一次能看见的资料,适合知识缺失、知识会变或必须引用原文的任务。微调改变模型的行为、格式或风格,成本最高,也最不容易随资料更新。

所以选型通常从提示开始。模型缺少事实时再接知识库。只有需要稳定地改变说话方式、输出习惯或复杂操作规程时,才把这些要求训练进权重。检索不能消除全部幻觉,它只是让答案有机会跟着资料走。

四步接成一个最小系统

一个能运行的最小系统依次做四件事。第一步加载文档,并保留来源、页码等元数据。第二步切块,使每块都能放进嵌入模型。第三步用同一个嵌入模型把块写入向量库,查询时取回最相近的若干块。第四步把原问题、检索结果和回答要求合成提示,由模型生成答案。

用 LangChain 时,这四步分别落在文档加载器、文本分割器、向量库和检索链上。用 LlamaIndex 时,常被收成索引和查询引擎,步骤更少,中间结果也不如显式管道容易检查。无论哪一种,都要先固定嵌入模型、块大小和取回数量,否则无法判断后来的改动究竟改善了哪一步。