第五周:精通 RAG —— 什么时候它不如直接塞进上下文
窗口变大之后 RAG 的适用面在收窄,但没有消失。判据不是「文档多大」,是「你需不需要说出这句话是从哪儿来的」。
RAG 是让模型「成为公司知识专家」的标准答案。但在窗口动辄几十万 token 的今天,第一个该问的是:为什么不直接把文档塞进去?
塞进去 vs 检索
先把两边的真实代价摆出来:
| 直接塞进上下文 | RAG | |
|---|---|---|
| 实现成本 | 几乎为零 | 切分、嵌入、向量库、检索调优 |
| 每次调用成本 | 按全文长度付费,每次都付 | 只付检索到的那几段 |
| 质量 | 会随输入变长而下降 | 取决于检索准不准 |
| 出处 | 没有 | 每段都能指回来源 |
第三行是关键,而且证据很硬:Chroma 测了 18 个前沿模型,每一个都随输入变长而变差。这不是「装不下」,是装得下但效果在掉 —— 有效信息被稀释了。
所以「窗口大了就不需要 RAG」这个推论是错的:窗口大小和有效利用率是两回事。上下文腐烂讲的是同一件事的另一面。
真正的判据是出处
我认为选不选 RAG,最硬的判据是第四行:你需不需要说出「这句话是从哪儿来的」。
需要出处的场景,RAG 几乎是唯一选择 —— 因为检索这一步天然产生了引用。而这一点在很多垂类里是硬需求:
- 合规、法务、医疗 —— 结论必须可追溯
- 客服 —— 用户会问「你在哪儿看到的」
- 任何模型会编造细节的场景
最后一条值得展开。我在自己的项目里量过一次:让模型从产品文档生成测试用例,它写出的断言里引用了 12 条界面文案,回头去文档里查,只有 4 条查得到。剩下的「邮箱格式无效」「账户已锁定,请 10 分钟后再试」全是编的 —— 文档承诺了规则,没承诺产品会显示这些字。
而这类编造,靠「把文档全塞进去」是治不了的,因为模型看得见文档,仍然会顺着语感往下写。治它的是要求每条产出带出处,并且校验那个出处真的存在。
这个做法在测试生成这个垂类里有个名字叫 external anchoring —— 有综述指出,幻觉控制做得好的系统无一例外都引入了这样一个外部锚点机制。
切分是最影响质量的一步
RAG 的效果排序里,切分(chunking)的影响常常大过换嵌入模型。三条实际经验:
一、按结构切,不要按字数切。 按 500 字硬切会把一个表格劈成两半,两边都没用。按标题、按段落、按函数切,切出来的块才是自足的。
二、块里要带上下文。 一段写着「不能早于今天」的文字,脱离了「截止日期」这个标题就没有意义。给每个块加上它的标题路径,成本极低,收益很大。
三、留重叠。 边界上的信息容易两边都丢。
检索之后还要排
检索回来的十段,不是每一段都该进上下文。这里有一个和上面呼应的理由:塞得越多,有效信息越被稀释。
所以检索之后至少要做一件事:按相关性截断,并且记下砍掉了什么。
我自己在这一步吃过亏:最初只在控制台 warn 一句「截掉了约 3000 token」,等到要复盘一次覆盖不全的运行,我答不上来「被砍掉的里面有没有关键规则」,因为原文已经不在了。
改法很简单也很有效:有损的是喂给模型的那一份,存下来的那份必须完整 —— 完整原文落盘、能调回来,而且日志里要记砍掉的是哪几段,不只是砍了多少 token。
边界与代价
RAG 引入了一整条新的失败链。 切分错、嵌入不匹配、检索没召回、重排把对的排下去 —— 每一环都可能出错,而且它们的失败都表现为「模型答得不对」,很难归因。上 RAG 之前先确认「直接塞」真的不行。
向量检索不是唯一的检索。 几十条数据用 includes 就够了;上千条可能 BM25 比向量好;结构化的东西该走数据库。在小数据上引入向量库,是拿一个真问题换一个假问题。
出处能证明「有来源」,不能证明「理解对了」。 模型可以正确引用一段文档然后得出错误结论。出处解决的是编造,不是推理错误 —— 但它仍然是这类产品的地基,一个真实的搜索型 agent 就把全部信任建立在这上面。