把步骤拼成链 — 多轮对话真正的难点在检索这一步
这一章讲三件事: 把前面九章那些步骤拼装起来的那个结构是什么; 用户一句接一句地追问时,系统会在哪一步先崩;以及第 03 章埋的那颗雷,账单长什么样。 它在全书链条里的位置: 前九章各讲一站,这一章把站与站之间的接头做成零件 —— 而正是在这里,书自己碰上了多轮对话唯一的真难点,却从它旁边走了过去。
1. 顶层全景
单轮: 问题 ──► 检索 ──► 拼提示 ──► 生成 ──► 答案
多轮追问: 「 那它用的是哪种检索方法?」
│
│ 这句话里的「它」不在句子里
▼
┌──────────────────────────────────┐
│ 先补一步:把追问 + 之前说过的话 │
│ 合成一个能独立成立的问题 │
└──────────────────────────────────┘
│
▼
合成后的问题 ──► 检索 ──► 拼提示 ──► 生成 ──► 答案
图说:多轮和单轮的差别,只有中间那一个框。 而那个框加在检索之前,不是加在生成之前 —— 这一节的全部要点就在这个位置上。
一句话链条: 步骤要能拼装、能替换 → 于是有了一个专门的结构 → 拼出来的第一件真活是多轮对话 → 而多轮对话会在检索那一步先崩 → 崩的原因是 追问里有个词指向了句子外面。
2. 链是什么
先把名字和一个日常说法分开
这一章说的「链」是一个具体的程序结构:一串可以拆开、可以替换的步骤。
注意: 前面各章正文里出现过「全书链条」这种说法,那是日常用语,指推理的先后顺序。 这一章的「链」不是那个意思。 为了不混,本章一律写「链」,不写「链条」。
它解决什么问题
不用链的时候,每做一个 RAG 应用,你都要把这六行重写一遍:
取回文档 → 把文档拼成一段文字 → 套进提示模板 → 交给模型 → 取出文字 → 返回
图说:这六行本身不难,难的是**换一个环节就要动整段代码**。
想把检索换成两路混合?想在中间插一步重排?六行全得改。
它怎么做
把每一步做成一个零件,零件之间约定好「上一个的输出就是下一个的输入」。 于是拼装就变成了一个连接动作。
书里用到的写法长这样1:
提示模板 | 模型 | 解析器
图说:那个竖线读作「接到」。左边的输出直接喂给右边。
**提示模板**:一段带空位的文字,把问题和资料填进空位;
**模型**:把填好的那段文字变成一段回答;
**解析器**:把模型返回的对象里那段纯文字取出来(有时还顺手转成数据)。
这种竖线写法在这套库里有个名字,叫 LCEL —— 你在它的文档里到处会撞见这三个字母。
为什么这么做有效
| 要换什么 | 不用链 | 用链 |
|---|---|---|
| 换一个模型 | 改调用那几行 | 换掉竖线中间那一节 |
| 中间插一步重排 | 改流程 | 多接一节 |
| 同一套步骤跑不同的问题 | 复制一份 | 同一条链换输入 |
边界也要说清:链的顺序是写死的。 第一步没找到东西,它照样往下走 —— 这一条是第 11 章存在的全部理由。
3. 主走查:两轮对话,第二句里的「它」不在句子里
这一节是本章的主走查。这一章每个机制,在这条线上都占一步。
书的这个配方,库里只有四条文档2:
D1 RAG reduces hallucinations by grounding answers in retrieved evidence.
(RAG 把答案落在找回来的证据上,从而减少胡编。)
D2 Dense retrieval uses vector embeddings instead of keyword matching.
(按意思找用的是向量,不是关键词匹配。)
D3 BM25 is a sparse retrieval method based on term frequency statistics.
(BM25 是一种按词频统计打分的按词面找的方法。)
D4 Vector databases store embeddings to enable semantic search.
(向量库存向量,以支持按意思搜。)
每次取前 2 条。
五步,每步的实际内容
| 步 | 这一步做了什么 | 具体的字串 |
|---|---|---|
| ① | 第一轮提问 | How does RAG reduce hallucinations? |
| ② | 检索 → 拼提示 → 生成(一条完整的链走一遍) | 答案 grounding answers in retrieved evidence —— 来自 D1 |
| ③ | 把这一轮存进历史 | 历史里现在有一对:(问题 ①,答案 ②) |
| ④ | 第二轮追问 | And what method does it use for retrieval?(那它用什么方法做检索?)「it」不在这句话里 |
| ⑤ | 链先把「追问 + 历史」合成一个能独立成立的问题,再拿它去检索 → 生成 | 答案是 D3 + D2 两条拼起来:BM25 is a sparse retrieval method … Dense retrieval uses vector embeddings … |
第 ⑤ 步书印出来的原文是3:
Q2: And what method does it use for retrieval?
A2: BM25 is a sparse retrieval method based on term frequency statistics.
Dense retrieval uses vector embeddings instead of keyword matching.
图说:答对了——问的是「用哪种检索方法」,回来的正是库里那两条讲检索方法的。
**注意它又是把两条原文拼起来交差的**(第 08 章讲过为什么),
但这一次内容对题。
第 ⑤ 步里那个「合成」是这一章的全部价值
书没有把合成后的那个问题印出来。 它只在正文里写了一句 「这个链内部自己管对话历史,所以不需要自定义提示」4,然后就过去了。
而那一步正是多轮对话唯一的真难点。 下面两节把它拆开。
4. 查询侧指代:先崩的是检索,和模型强不强没关系
现象
用户第一句: RAG 怎么减少胡编?
用户第二句: 那**它**用的是哪种检索方法?
第二句拿去检索时,检索器看到的只有这一句话。
「它」在这句话里没有指代对象——**那个词在上一轮**。
图说:检索器读的是查询,不是整段对话。
你把历史塞进提示,帮不到检索这一步——**因为检索发生在拼提示之前。**
我们给这个病起个名字叫查询侧指代。
它和第 03 章那颗雷是两种病,必须分开
第 03 章那颗雷叫块内指代断裂:坏在库里,某一块文字里留下了失去指代对象的代词。 这一节这个坏在查询里。
| 块内指代断裂(第 03 章) | 查询侧指代(这一节) | |
|---|---|---|
| 谁身上有代词 | 库里的某一块 | 用户的这句问话 |
| 什么时候发作 | 那一块永远打不出高分 | 这一次检索整个跑偏 |
| 修法 | 改切法:在句子边界下刀、开大重叠 | 在检索之前把问题补全 |
| 修得掉吗 | 要重新切、重新入库 | 加一步就行,库不用动 |
书这一次的例子其实不够狠,照实说
这个追问里除了「it」,还留着 method 和 retrieval 两个实词。
光凭这两个词,检索多半也能命中讲检索方法的那两条。
判断(我们的,不是书里的): 我们认为这条走查证明了「链里确实有这一步」, 但没有证明「没有这一步就会崩」 —— 因为这 个追问自带足够的实词。 如果错,会错在: 如果那两个实词其实不足以命中(比如库再大一点、干扰项再多一点), 那么这个例子就是充分的。判据是把原句直接拿去检索一遍作对照 —— 这个对照书里没做,我们也没有跑。
一个真会崩的追问长这样(下面这两句是我们编的,不是书里的):
用户第一句: RAG 怎么减少胡编?
用户第二句: 「那第二种呢?」 ← 全句没有一个实词
「它比另一种快吗?」 ← 两个代词,零个名词
图说:把这两句直接丢给检索器,它只能靠「第二种」「快」这类词去比远近,
**命中什么全凭运气。** 而模型再强也救不回来——它拿到的资料本来就是错的。