问题所在:一位聪明却看不到你文件的实习生
到这一步你已经明白,大语言模型本质是一个在「冻结」的文本快照上训练出来的下一词预测器。那份快照就是模型的全部世界。问它你公司的退款政策、上周的事故报告,或一份它从没见过的合同,它只有两个选择:承认不知道,或者——更常见地——给出一个流畅、像模像样、却完全编造的答案。这种自信的编造叫幻觉,它不是靠改提示词就能消除的 bug。
把模型想成一位才华横溢的新实习生:他读过大半个公开互联网,却完全打不开你的档案柜。他不是故意撒谎,只是用「听起来对」的内容去填空白。解法既显而易见又很人性化:提问之前,先把相关页面递给他。检索增强生成正是如此——取出正确的文档,贴进提示里,再发问。模型于是基于你提供的文本来推理,而不是凭记忆瞎猜。
嵌入:把语义变成坐标
要把对的页面递给模型,你得先找到它们。关键词搜索在这里会失灵:用户问「我怎么把钱拿回来?」而你的政策写的是「退款资格」。没有一个词重合,意思却完全一样。让 RAG 能跑起来的关键,是嵌入——一个把任意一段文本转成一串数字(向量)的模型,使得语义相近的文本落在空间里彼此靠近,不管用词是否一致。
在阶梯前段你已经见过这个思想,那就是 word2vec:「国王 − 男人 + 女人 ≈ 王后」。现代的嵌入模型对整句乃至整段做同样的事,所用的正是与 LLM 同源的 Transformer 机制。每段文本都成为一个几百乃至上千维空间里的点。所谓「靠近」就只是几何——通常是两个向量间的余弦相似度,它衡量的是两者的夹角,而非长度。
query: "how do I get my money back?" -> [0.21, -0.05, 0.88, ...] chunk: "Refund eligibility and process" -> [0.19, -0.07, 0.85, ...] chunk: "Office holiday schedule 2026" -> [-0.4, 0.6, 0.02, ...] cosine(query, refund_chunk) = 0.94 <- near, retrieve this cosine(query, holiday_chunk) = 0.11 <- far, ignore
向量数据库与检索流水线
如果只有十份文档,你大可把查询和每一份逐一比对。但真实语料库往往有数百万个片段,每次请求都和每个片段比一遍会慢到无法忍受。向量数据库正是为此而生:它存下你所有的嵌入,给定一个查询向量,便用「近似最近邻」索引在毫秒级返回最相近的若干个。它就是让大规模数据检索变得可行的那台搜索引擎。
整套流程分两个阶段。第一阶段是离线的、只做一次的慢活:加载文档、切成片段、为每个片段生成嵌入、把向量存起来。第二阶段是在线的快循环:每个用户问题都触发它——给问题生成嵌入、找出最近的片段、贴进提示,再要求模型只依据这些提供的文本作答。
它为何能减少幻觉——又为何无法根除
RAG 能减少幻觉,是因为它改变了任务本身。没有它时,模型必须从被压缩、有损的记忆里回想出一个事实——这种回忆任务它常常做不好。有了它,模型做的是阅读理解:答案就明明白白地摆在提示里,于是「抄出相关句子并改写」远比「记住读过的一切」来得容易、可靠得多。额外的好处是,你可以把来源段落展示给用户,让答案变得可核查——这是裸 LLM 永远做不到的。
但要诚实面对它的边界。RAG 的上限完全取决于检索质量。如果对的片段压根没被取出来——切片太差、嵌入模型太弱、查询太含糊——模型就会基于错误的上下文作答,或者退回到老一套的瞎猜。即便上下文完美无缺,模型仍可能与之矛盾、读错它,或把它和陈旧记忆混为一谈。RAG 能大幅降低幻觉率,却无法将其归零。请把「有依据」理解成「来源更可靠」,而非「保证为真」。
把它做好——以及哪些噪音可以忽略
搭一个初版 RAG 系统其实很简单,这正是它迷人之处——一个周末就能跑出可用的原型。让它可靠才是真功夫,而这功夫几乎全在检索,而非模型。把精力花在:怎么切片(尊重标题和段落边界)、怎么嵌入(选一个强劲、新近的嵌入模型),以及加一道「重排序」——用更精细的模型把候选片段重新打分,再送进提示。关键词搜索与向量搜索的混合,通常胜过任何单独一种。
要像对待工程产物那样去评测它,而不是凭感觉。准备一小批答案已知的真实问题,分开衡量两件事:检索有没有把对的片段捞出来(这是检索质量问题),以及模型有没有忠实地依据它作答(这是生成质量问题)?把这两个分数拆开,你才知道该修哪一头——后面讲评测的几篇会大量依赖这个习惯。同时留一个简单的关键词基线在手边也很值得,用来证明你的向量流水线确实对得起它带来的复杂度。
最后,抵制两种炒作。RAG 不是通往通用人工智能的垫脚石,也不是什么全新的记忆形式——它就是一套取文本的「管道」。另外,随着上下文窗口越来越大,有人问能否干脆跳过检索、把所有东西一股脑贴进去。对单份小文档,有时确实可以。但对任何真实语料库,灌进几百万个 token 更慢、更贵,反而更不准——因为模型对埋在巨大草堆里的事实关注得更差。检索之所以始终有用,恰恰是因为「挑选该读什么」本身就是关键所在。