跳到主要内容

检索与摘要 — RAG,给模型补课的两条路

这一章讲三件事: 模型不知道的事,怎么临时补给它(检索); 东西太多装不下,怎么压小(摘要);以及塞料时最容易踩的一个坑—— 你给它的每条信息,它都觉得「必有深意」。 这是原书第 5 章的下半章,也是今天绝大多数 LLM 应用的主干技术。

1. 这一章讲什么

第 06 章说了动态上下文「从哪来」。最大的一个来源,大到值得单独一章: 从你自己那堆资料里出来。

主走查是书里那个 60 行的最小 RAG 程序:11 条用户旧书评、 一本候选书《The Beach》、一次检索、一份提示词、一个分数。 我们会把这 60 行从头走到尾,每一步旁边都有真字串。

2. 顶层全景:先检索,后生成

模型两手一摊的时刻:问它训练集里没有的事——公司内网文档、上周的新闻、 你昨晚的购买记录。理想情况它说不知道;不理想,它面不改色地编。

RAG——检索增强(Retrieval-Augmented Generation)生成,2020 年 5 月一篇论文 给它定的名:先从你的资料库里检索出相关片段,再把这些片段塞进提示词, 让模型「开卷」作答1

离线(准备): 文档 → 切成小块 → 每块算一个「位置」→ 存进库里
在线(请求): 用户问题 → 算同一个坐标系里的位置 → 找出离它最近的几块
→ 塞进提示词 → 模型看着这些料作答

图说:RAG 的两半。检索的学问全在「位置怎么算」——
两派算法,是本章 3.2 与 3.3 的内容。

塞料之前,先立一条警告,这是全章的红线:

契诃夫之枪谬误

剧作家契诃夫有句名言:第一幕墙上挂了把枪,后面就必须打响—— 否则就别挂。LLM 把这条编剧纪律也学进去了:你塞给它的每条上下文, 它都默认「必有深意」,并努力把它用上2

所以检索错了一条不相关的,不是「多了一句废话」, 而是它会被过度解读、把答案带偏。书作者把这叫契诃夫之枪谬误。 唯一的解药是:要检索,就检索对——这正是后面两派路数要解决的问题。

3. 核心原理

3.1 检索是一个搜索问题

把检索形式化:你手里有一条「搜索串」(比如候选书的简介), 和一堆「文档」(用户历年的书评、帖子)。 目标:找出与搜索串最相似的几段,最好每段还带个分数3

「相关」定义不清,业界退而求其次:搜「相似」。「相似」怎么算,分两派。

3.2 词法检索:数共同词,老而弥坚

词法检索——按「用了哪些词」来算相似,不管词的意思。 这招比 LLM 老得多,是信息检索学科几十年的家底4

最简版本是 Jaccard 相似度,书里拿它算了完整一遍,我们跟着走:

搜索串: 「The Beach 讲一个年轻背包客寻找未被旅游染指的海滩」
片段: 用户旧书评一条

第一步,去停用词——the、a、is 这类对意思没贡献的高频词,扔掉
第二步,词干化——walking、walks、walked 全归并成 walk
第三步,算分:两边共有的词 ÷ 两边合起来不重复的词 = 0 到 1 之间一个数

图说:Jaccard 三步。两个「backpacking」对上,比十个「the」对上值钱——
但 Jaccard 分不清这个,它眼里每个词都一样重。

Jaccard 的好处:不用预先建任何东西,零内存开销,小数据集上飞快—— GitHub Copilot 就用它,在 IDE 当前打开的文件里闪电找出相关片段(作者一手经验)5

它的粗糙也明显:共同的词是 go 还是 backpacking,它一视同仁。 进阶版 TF-IDF——按「这个词在全部文档里稀不稀有」给匹配加权, 越稀有的词对上越算数——以及更精细的 BM25,都补这个洞; 代价是要预先统计全库词频6

词法检索的死穴:认词不认意。 「I forgot my backpack」和「I'm going backpacking」词干重叠,其实无关(假阳性); 「didn't remember their rucksack」和「忘了背包」意思是近亲,却一个词都不共享(假阴性)。 错别字、同义词、跨语言,全都破防7

3.3 神经检索:把一段文字变成一个坐标

想按意思匹配,就得先把「意思」变成能算距离的东西。 嵌入(embedding)——用一个小模型,把一段文字变成一个坐标(一长串数字); 意思相近的文字,坐标就近。 这样的文字在空间里各就各位, 找相似就变成了找最近邻——离查询点最近的那几个坐标8

注意:嵌入模型不是 LLM。 它是另一种模型—— 架构同源(也是 Transformer 家),但产出不是「下一个词」而是「一个坐标」; 它通过对比学习——把意思相关的文字拉到坐标系里相近处、 不相关的推远,如此反复调教——练出这个本领。 它比 LLM 小几个量级、便宜几个量级,所以把整座文档库都「坐标化」也负担得起9

落地是两步(对照第 2 节那张图):

  1. 离线建索引——把全部文档切块、逐块算坐标、存进库, 这份「为查找预先备好的数据结构」就叫索引;
  2. 在线查询:把查询串也算成坐标,让库返回最近的几个坐标连同原文块。

3.4 切块:一块一个主意

文档怎么切,直接决定检索质量。三条准则10:

  1. 不超嵌入模型的窗口(2024 年 OpenAI 的嵌入模型上限 8,191 个标记);
  2. 一块只装一个主意——装了两个话题的块,坐标会落在两个话题中间的无人区;
  3. 大小适合进提示词——检索出来是要塞给模型的。

切法上:滑窗(256 词一窗、128 词一步,窗口留重叠,防一个想法被腰斩) 简单均匀;按自然边界切(段落、章节)能保证一句不断; 代码场景还有一招:给块补上归属——方法是某个类的成员, 就把类定义和初始化代码也拼进去,坐标才立得稳11

3.5 向量库:找最近邻是个已解决的问题

嵌入坐标一般上千维,在大库里找最近邻并不平凡—— 但已经被人解决得很好。向量数据库——专门存向量、并能飞快回答 「谁离这个点最近」的存储系统:自己跑,有 FAISS 这类库; 买服务,有 Pinecone 这类托管商12

3.6 主走查:60 行的最小 RAG,从头走到尾

现在把 3.2–3.5 全部装配起来,走书里的完整例子。 场景:用户在书店网站浏览,系统要根据他过去的书评, 预测他会给眼前这本书打几分13

第 ① 步,料。 用户写过 11 条书评,其中两条是:

"I hate stories about backpacking. It's boring."
"Another bland romantic utopia. This time on a tropical island."

第 ② 步,建索引。 11 条全部送嵌入模型(text-embedding-3-small), 各得一个坐标,存进 FAISS。

第 ③ 步,查询。 候选书是《The Beach》,系统拿它的简介做查询串:

"The Beach by Alex Garland critiques backpacker culture by exposing the
selfishness and moral decay behind their pursuit of an untouched paradise."

第 ④ 步,取回最近的两条(k=2)。 返回的正是开头那两条—— 注意匹配上的不是字面:「critiques backpacker culture」和 「I hate stories about backpacking」共享的词其实不多,是意思对上了

第 ⑤ 步,组装提示词(样板话 + 检索结果):

Here is a book I might want to read:
The Beach by Alex Garland critiques backpacker culture by …

Here are relevant reviews from the past:
I hate stories about backpacking. It's boring.
Another bland romantic utopia. This time on a tropical island.

On a scale of 1 (worst) to 5 (best), how likely am I to enjoy this book?
Reply with no explanation, just a number.

第 ⑥ 步,模型答:2。 一个讨厌背包客故事的人, 大概率不会喜欢一本「背包客天堂幻灭记」——检索给对了料, 模型的常识就能把最后一步走完14

60 行代码,离线一半在线一半,一个能跑的 RAG 就这么多。 生产级的缺口里,这本书补得上两样:评估(第 13 章整章谈) 与更新(应用一改版,攒下的旧样本就作废——同在第 13 章); 权限与多租户,书里管不着。

3.7 两派怎么选:能 debug 与懂意思

作者(两个搜索出身的工程师)给了一张不带立场的对照15:

词法检索神经检索
匹配依据意思
查不到时一看就懂:查询的词和文档的词没对上,改词干、补同义词就行坐标不透明,几乎没法查
调相关性可按字段加权(标题命中 > 正文命中)只能重训模型、重建索引
杀手锏成熟、快、Elasticsearch/Algolia 现成跨语言、跨媒介(字找图、图找字),说不同词的同一件事也能对上

作者的原话值得记住:别瞧不起老派搜索索引—— 它们好管理,而且「找不到想找的东西」时好查原因得多16

3.8 摘要:装不下,就一层一层压

检索是「放大最相关的」,摘要走反方向:把一大坨压成一小段,保住要点

让模型摘要,直接贴文加一句「Tersely summarize all of the above」就行—— 书作者当场试了,效果不错。然后把整章草稿贴进去,收到的是:

This model's maximum context length is 4097 tokens, however you requested
requested 9491 (8491 in your prompt; 1000 for the completion).

装不下——而装不下,正是你需要摘要的原因。死锁的解法是分层摘要: 先按章摘要,再把各章摘要拼起来摘要。《The Beach》一章一摘要、再总摘要; 《圣经》1,189 章,每章压到 50 个词也还是超,那就递归—— 摘要的摘要再摘要(章 → 66 卷 → 全书)——代码库则按目录树上卷17

两条经验:

  • 成本账:只要每层摘要短于原文的十分之一,总成本就只由原文总量决定, 层数再多也不怕;
  • 传话失真(rumor problem):每多一层,就多一次「听岔」的机会, 岔子还会层层放大。对策是别抠门——每层摘要留足长度, 失真就控制在无害范围;切分时沿自然边界,别让一块里九成是甲章、一成是乙章18

通用摘要 vs 任务导向摘要。 压缩必有损:一条度假帖子的通用摘要 会留下「去了哪、好不好玩」,丢掉「飞机上哪本书好啃」—— 而后者恰恰是图书推荐要的那口料。对策是带着最终任务去摘要: 书里那个例子,让模型专记「对挑圣诞礼物有用的信息」, 一条痛骂背包客的帖子被摘成一句:「Does not like backpacking or backpackers.」 代价:任务一变,全部重摘;通用摘要则能跨应用复用19

4. 作者的判断与证据

有硬证据的:

  • 最小 RAG 是完整可跑的代码,检索结果与评分都是真实运行输出1314;
  • Copilot 用 Jaccard 搜打开的标签页,是作者团队的一线工程决策5;
  • 分层摘要的实例(《The Beach》章摘要)由 ChatGPT 实际生成17

作者的经验判断(标出来,别当定律):

  • 「意思相近则坐标相近」是嵌入模型的设计目标,实践里大体成立, 但「无关内容被排在近邻」的事故并不罕见——契诃夫之枪谬误才是一级警告;
  • 「传话失真」是作者打的比方(传话游戏),他们自己也说 「一般来说没那么糟」,前提是每层别太短18;
  • 嵌入窗口 8,191 是 2024 年口径,会过时10

判断(我们的,不是书里的): 两派检索的选择,本质是「可调试性」与「语义召回」的交换。 工程上的稳健答案是混用:神经检索管召回,词法检索管兜底与排查—— 书里没把「混用」写成方案,但两位作者的履历(搜索工程师 + Copilot) 和那张对照表指向同一个方向。 如果错,会错在: 如果向量库的过滤与混合检索继续成熟, 「神经不可调试」这条会很快贬值;但「查不到时你要能说出为什么」这条工程原则不过时。

5. 边界与局限

  • RAG 只解决「模型不知道」,不解决「模型不会用」—— 检索对了,模型照样可能忽略或误用这些料(契诃夫之枪的另一面);评估见第 13 章;
  • 嵌入模型有自己的窗口,长文档必须先切块,而切块本身就是失真来源;
  • 摘要是有损压缩,任务导向摘要与问题绑定,问题漂移要重摘;
  • 本章没讲权限与多租户——企业 RAG 里「这条料该给谁看」是大坑,书里没覆盖。

6. 可带走的

主走查一行回顾:11 条书评 → 坐标入库 → 《The Beach》简介变坐标 → 最近邻两条(「讨厌背包客」「热带乌托邦」)→ 进提示词 → 模型答 2—— 检索负责「找对料」,模型负责「用常识走完最后一步」。

  1. RAG = 先检索后生成,治「模型不知道训练集之外的事」;
  2. 契诃夫之枪谬误:塞进来的每条料,模型都觉得必有深意——错料比没料更糟;
  3. 词法检索:数共同词;Jaccard 零成本起步,TF-IDF/BM25 按词频加权;
  4. Copilot 在用 Jaccard——老技术没过时;
  5. 神经检索:把文字变坐标,按意思匹配;嵌入模型不是 LLM,小而便宜;
  6. 切块三准则:不超窗口、一块一个主意、适合进提示词;代码块要补上归属;
  7. 查不到时,词法能 debug、神经基本不能——选型先想清楚谁替你排查;
  8. 装不下就分层摘要;每层留足长度防传话失真;
  9. 摘要要带任务:通用摘要可复用,任务导向摘要更准但改题重来;
  10. 成本规律:摘要够短,总成本只看原文量

7. 原文地图

主题原书章原文位置
RAG 定义与出处Chapter 5text/08-ch05-chapter-5-prompt-content.txt:522(搜「May 2020 paper titled」)
契诃夫之枪Chapter 5text/08-ch05-chapter-5-prompt-content.txt:546(搜「gun fallacy」)
Jaccard、CopilotChapter 5text/08-ch05-chapter-5-prompt-content.txt:579(搜「Jaccard similarity」) · :605(搜「GitHub Copilot」)
TF-IDF、BM25Chapter 5text/08-ch05-chapter-5-prompt-content.txt:612(搜「inverse document frequency」)
嵌入模型、对比预训练Chapter 5text/08-ch05-chapter-5-prompt-content.txt:630(搜「embedding model」) · :695(搜「contrastive pre-training」)
切块准则Chapter 5text/08-ch05-chapter-5-prompt-content.txt:653(搜「bite-sized chunks」) · :657(搜「8,191 tokens」)
向量库Chapter 5text/08-ch05-chapter-5-prompt-content.txt:715(搜「FAISS」) · :719(搜「Pinecone」)
最小 RAG 全程Chapter 5text/08-ch05-chapter-5-prompt-content.txt:746(搜「I hate stories about backpacking」) · :854(搜「rating of 2」)
词法 vs 神经Chapter 5text/08-ch05-chapter-5-prompt-content.txt:857(搜「Neural versus lexical」)
分层摘要、传话失真Chapter 5text/08-ch05-chapter-5-prompt-content.txt:914(搜「hierarchical summarization」) · :938(搜「rumor problem」)
任务导向摘要Chapter 5text/08-ch05-chapter-5-prompt-content.txt:1000(搜「Does not like backpacking」)

Footnotes

  1. 出处:「Chapter 5. Prompt Content」第 516-526 段(text/08-ch05-chapter-5-prompt-content.txt:522,搜「May 2020 paper titled」)。论文全名:「Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks」,2020 年 5 月。

  2. 出处:「Chapter 5. Prompt Content」第 542-552 段(text/08-ch05-chapter-5-prompt-content.txt:546,搜「gun fallacy」)。原文:「the model often feels compelled to use every bit of information it gets」。

  3. 出处:「Chapter 5. Prompt Content」第 558-570 段(text/08-ch05-chapter-5-prompt-content.txt:560,搜「search problem」)。

  4. 出处:「Chapter 5. Prompt Content」第 573-577 段(text/08-ch05-chapter-5-prompt-content.txt:573,搜「lexical retrieval」)。

  5. 出处:「Chapter 5. Prompt Content」第 600-606 段(text/08-ch05-chapter-5-prompt-content.txt:604,搜「GitHub Copilot」)。原文:「the Jaccard similarity was a natural choice in GitHub Copilot, where it is used to quickly find relevant snippets from all the files currently open in a programmer's IDE」。 2

  6. 出处:「Chapter 5. Prompt Content」第 607-617 段(text/08-ch05-chapter-5-prompt-content.txt:612,搜「inverse document frequency」)。

  7. 出处:「Chapter 5. Prompt Content」第 622-628 段(text/08-ch05-chapter-5-prompt-content.txt:623,搜「false positives」)。

  8. 出处:「Chapter 5. Prompt Content」第 629-635 段(text/08-ch05-chapter-5-prompt-content.txt:630,搜「embedding model」)。原文:「any snippets carrying similar meaning will correspond to vectors 『near』 one another」。

  9. 出处:「Chapter 5. Prompt Content」第 690-700 段(text/08-ch05-chapter-5-prompt-content.txt:695,搜「contrastive pre-training」)。原文:「embedding models are tiny in comparison to LLMs and orders of magnitude cheaper」。

  10. 出处:「Chapter 5. Prompt Content」第 653-662 段(text/08-ch05-chapter-5-prompt-content.txt:658,搜「8,191 tokens」)。 2

  11. 出处:「Chapter 5. Prompt Content」第 664-689 段(text/08-ch05-chapter-5-prompt-content.txt:665,搜「moving window」)与(:683,搜「augmenting your snippets」)。

  12. 出处:「Chapter 5. Prompt Content」第 713-723 段(text/08-ch05-chapter-5-prompt-content.txt:715,搜「FAISS」)与(:719,搜「Pinecone」)。

  13. 出处:「Chapter 5. Prompt Content」第 725-738 段(text/08-ch05-chapter-5-prompt-content.txt:725,搜「no-frills RAG」)。 2

  14. 出处:「Chapter 5. Prompt Content」第 739-855 段(text/08-ch05-chapter-5-prompt-content.txt:746,搜「I hate stories about backpacking」)与(:854,搜「rating of 2」)。代码用 text-embedding-3-small、FAISS IndexFlatL2、gpt-4o-mini。 2

  15. 出处:「Chapter 5. Prompt Content」第 857-886 段(text/08-ch05-chapter-5-prompt-content.txt:857,搜「Neural versus lexical」)。原文:「lexical retrieval matches based upon words, neural retrieval matches based upon ideas」。

  16. 出处:「Chapter 4. Designing LLM Applications」第 530-533 段(text/07-ch04-chapter-4-designing-llm-applications.txt:531,搜「good old-fashioned search indexes」)。

  17. 出处:「Chapter 5. Prompt Content」第 893-932 段(text/08-ch05-chapter-5-prompt-content.txt:905,搜「requested 9491」)与(:915,搜「hierarchical summarization」)。《圣经》1,189 章、66 卷是书里原例。 2

  18. 出处:「Chapter 5. Prompt Content」第 933-953 段(text/08-ch05-chapter-5-prompt-content.txt:938,搜「rumor problem」)。原文:「that game of Telephone isn't too long」。 2

  19. 出处:「Chapter 5. Prompt Content」第 955-1000 段(text/08-ch05-chapter-5-prompt-content.txt:1000,搜「Does not like backpacking」)。表 5-3 的完成出自 text-davinci-003。