为什么充满自信的回答可能比没有回答更糟糕

你完成了内部聊天机器人的构建。你把公司所有的 HR 政策、工程规范和入职文档都喂给了它。一名新员工询问客户晚餐的差旅费用限制。机器人立即做出了响应。它听起来非常笃定。它给出的限额是每人 75 美元。

实际政策规定是 50 美元。机器人编造了这个答案。它从未查看过你的文件。它只是根据多年前训练数据中埋藏的模式进行了猜测。这就是在私有文档上运行原始大语言模型的残酷现实。它们无法访问你的内部知识。当它们需要的事实存在于其训练权重之外时,它们会选择捏造,而不是承认无知。在生产环境中,这不再是件有趣的事,而会变成一种隐患。

检索增强生成(Retrieval-Augmented Generation,简称 RAG)正是为了解决这个问题而生的。与其要求模型记住一切,不如让它去查阅资料。

从猜测到阅读

把原始 LLM 想象成一位拥有过目不忘能力的才华横溢的同事,但他们在你入职之前就离开了公司。他们可以写出文采斐藉的文章,通过逻辑谜题进行推理,并用简单的语言解释概念。但是,如果你问他们上季度的 API 变更,他们只会编造一些听起来很有道理的东西。他们别无选择。

RAG 为这位同事提供了一个文件柜。当用户提出问题时,系统不会盲目地将问题抛给模型。它首先检索相关的文档,将它们作为上下文塞进提示词(prompt)中,然后才要求模型阅读并回答。模型从“回忆事实”转变为“理解摆在面前的事实”。

这一流程清晰地分为两个部分:离线准备工作和在线响应。

第一阶段:准备阶段(离线)

在有人输入问题之前很久,你就必须将杂乱无章的文档集合转化为可搜索的知识库。这项基础工作决定了你的 RAG 系统是蓬勃发展还是悄然失败。

**文档加载器(Document loaders)**是你的起点。这些连接器从 PDF、Notion 工作区、SharePoint 文件夹、网页和内部维基中提取原始文本。在这里,现实会首先给你带来挑战。加载器可能会从 Word 文档中提取出干净的文本,但在处理一个实际上只是没有嵌入文本层的扫描版 PDF 时却会卡壳。加载器返回一个空字符串,你的数据库什么也没存下,而你的用户随后会收到一个毫无预兆的“我不知道”的回复。务必验证加载器实际提取的内容。在信任整个流水线之前,先对每个来源的一小部分文档进行抽查。

接下来是文本分割(text splitting),也称为分块(chunking)。你不能一次性将一份 80 页的安全政策塞进提示词中;否则你会超出上下文限制,并将有效信号淹没在噪声中。相反,你需要将文档切分成块。诀窍在于选择合适的尺寸。太小的块(例如单个句子)往往会丢失关键上下文。如果一个块读到“所有请求必须经经理批准”,却忘了提到这条规则仅适用于国际旅行,就会出现这种情况。而太大的块(例如整个章节)会稀释嵌入(embedding)并干扰检索,因为它们同时涵盖了 15 个不同的主题。在实践中,许多团队从 300 到 500 个 token 的块开始,并设置 50 个 token 的重叠,这样跨越边界的句子就不会被切断。根据你的内容进行微调。API 文档可以容忍较小的块。法律合同通常需要较大的块来保留条件逻辑。

分块完成后,每一块都会被转换为嵌入(embedding)。这意味着通过一个模型处理文本,该模型会输出一组数字列表,即向量(vector),代表该块的语义含义。相似的概念在这个数学空间中会彼此靠近。“401k 匹配政策”和“退休缴款规则”会比“401k 匹配政策”和“办公室打印机设置”靠得更近。这些向量存储在**向量数据库(vector database)**中,例如 Pinecone、Weaviate 或开源替代方案 Chroma。向量库不仅仅是一个垃圾场。它是一个针对近似最近邻搜索(approximate nearest-neighbor search)优化的索引,让你即使在数百万份文档中也能在毫秒内找到最相关的块。

第二阶段:实时路径(在线)

当用户终于问道:“我们关于客户晚餐的差旅报销政策是什么?”时,实时流水线便开始运作。