搭建一条简单的 RAG 流水线:概念走查
把检索增强生成一段一段地搭起来。没有魔法,不绑定具体技术栈——只讲流水线的形态和那些真正要紧的决策。
检索增强生成(RAG)听上去像是单一的一种技术。它其实是一条流水线:一连串细小、寻常的阶段,合起来让语言模型能用你自己的文档来回答问题,而不只是凭它训练时背下来的东西。这些阶段单拎出来没有哪个算精巧。本事在于把它们连接好,使得在模型落笔写下答案的那一刻,你文本中正确的那段话恰好就摆在它面前。本走查把这条流水线一段一段地搭起来,并指出每一段最容易在哪里出岔子。
RAG 究竟为什么存在
语言模型知道它训练过的东西。它不知道你公司的员工手册、上周的事故报告,或者你一分钟前上传的那个 PDF 的内容。你可以拿这些材料去微调模型,但那既昂贵、更新又慢,还容易出错。RAG 走更便宜的路:别动模型,对每一个问题去找出相关的段落,把它们贴进提示词里。模型于是从它看得见的文本作答,而不是从它可能并不具备的记忆里作答。
可以把它想象成一位能干的助手加一本摊开的书。助手聪明,却对你的具体情况一无所知;书里有具体情况,却不会推理。RAG 在恰当的时机把恰当的那一页递给助手。下面的一切,都是为了"在恰当的时机给出恰当的那一页"。
阶段 1:把文档切块
你没法把一整座图书馆递给模型,所以你要把文档切成更小的片段,称为块(chunk)。一个块就是一段文字——几个自然段、一节,或一页。切块比看上去更要紧。块太大,会用周围的噪声稀释掉相关的那句话,还白白占用提示词里的空间。块太小,则会丢掉让一句话有意义的上下文——一句说"这并不受支持"的话,若没有说明"这"指什么的那一段,就毫无用处。
一个合理的默认做法是沿着文档的自然结构来切块:按节、按标题或按段落,而不是按盲目的字符数。把相关的想法放在一起。许多流水线还让块之间略有重叠,这样靠近边界的一句话至少在某一个块里仍以完整面目出现。不存在放之四海皆准的正确大小;它取决于你的文档,而且一旦你能测量结果,就值得回头重新审视。
阶段 2:嵌入与向量库
为了日后能找到相关的块,你需要一种办法,按含义——而非仅仅匹配关键词——来把一个问题和每一个块作比较。这正是嵌入所提供的。嵌入模型把一段文本变成一串数字——一个向量——其位置经过安排,使得含义相近的文本在那个数值空间里彼此靠近。"我怎么重置密码?"和"找回账户访问权限的步骤"几乎不共享任何词语,但作为向量却紧挨在一起。
你把每一个块都跑一遍嵌入模型,把每个块的向量连同原始文本一起存起来。这个集合存放在向量库里:一种专门构建来快速回答"哪些已存向量离这一个最近?"的数据库,哪怕跨越数百万条目也行。对于小项目,向量库可以是一个简单的内存结构;规模化时它是一个专用数据库。两种情况下接口都一样:放进向量,要回最近邻。
阶段 3:查询时的检索
现在流水线实时运行起来。一位用户提出一个问题。你用同一个为切块所用的模型来嵌入这个问题——这一点很要紧,因为来自不同模型的向量不可比较。你把这个问题向量递给向量库,要回最近的那些块。向量库返回排在最前的几个:含义离这个问题最近的那些段落。
"几个"是一个真实的决策。返回太少,你就有错过那个藏着答案的段落的风险。返回太多,你就用勉强相关的文本挤满了提示词,这既花钱更多,又分散了模型的注意。一个小数目——足以覆盖答案,又不至于多到信号淹没在噪声里——通常是起点。检索也是纯语义搜索有时会在产品编号或名称这类精确词项上栽跟头的地方,这正是为什么有些流水线会把它和老派的关键词搜索混合起来。从简单做起,只在你看到那种失败时才加上它。
阶段 4:组装提示词
现在你手上有了用户的问题和检索到的几个块。生成步骤把它们组装成单个提示词。在概念上,它看起来像这样:
You are answering using only the context below.
If the answer is not in the context, say you don't know.
Context:
[chunk 1 text]
[chunk 2 text]
[chunk 3 text]
Question: [the user's question]
里头有两条指令在默默地挑大梁。"仅使用下面的上下文"告诉模型优先采用所提供的段落,而非它自己的记忆,而这正是 RAG 的全部要点。"如果上下文里没有答案,就说你不知道"给了模型拒绝作答的许可——没有它,模型往往会用一个自信的猜测来填补空缺。把那种失败模式点出来,正是诚实的"未找到"与一次编造之间的分别。
阶段 5:生成与标注来源
组装好的提示词送进语言模型,它写出植根于检索文本的答案。因为你保留了每个块的原始来源,你能做一件微调做不到的事:展示答案来自何处。给每个块带上一个标识符——文档标题、章节、页码——并让模型去引用它,或者干脆把来源段落显示在答案下方。引用把一段不透明的回应变成用户可以核验的回应,而可核验性往往正是让一个 RAG 系统可信到足以部署的关键。
这也是流水线的诚实度受到考验的地方。如果检索递交的是错误的段落,模型就会流畅地从错误的段落里作答。一个自信的答案并不是它正确的证据。这就直接引向了大多数初次搭建会跳过的那一部分。
RAG 流水线实际在哪里崩坏
失败很少出在模型上。它们在上游,在检索环节。如果相关的块从未被返回,那么世上最好的模型也用不上它——"垃圾进,流畅的垃圾出"。常见的祸首:块切得让答案横跨边界,于是在任何一个块里都不完整地出现;嵌入模型没能捕捉你所在领域的词汇;或者干脆就是要回的块太少了。当一个 RAG 系统给出错误答案时,先别急着怪模型。去看针对那个问题检索实际返回了什么。多数情况下,答案根本就不在检索集里,而修复之道在切块或检索,不在提示词。
捕捉这一点的办法,是用一些你已知答案的真实问题来评估流水线,并去检视检索到的块,而不只是看最终文本。一条检索到正确段落、随后回答得当的流水线,是在正常工作。一条靠运气答对、却检索到错误段落的流水线,是一个潜伏的 bug,正等着一个更难的问题来引爆。
总结
RAG 不是一个戏法,而是一条短短的装配线:把你的文档明智地切块,将它们嵌入向量库,为每个问题检索出最近的块,把它们组装成一个有依据的提示词,再生成一个标注了来源的答案。模型是容易的那一部分。整个系统的质量由切块和检索决定——在恰当的时机把恰当的那一页摆到模型面前。把每一阶段都老老实实搭好,再用真实问题去测量检索,你就会把力气花在失败真正所在之处,而不是花在它看上去最唬人的地方。
