跳到主要内容

把步骤拼成链 — 多轮对话真正的难点在检索这一步

这一章讲三件事: 把前面九章那些步骤拼装起来的那个结构是什么; 用户一句接一句地追问时,系统会在哪一步先崩;以及第 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」,还留着 methodretrieval 两个实词。 光凭这两个词,检索多半也能命中讲检索方法的那两条。

判断(我们的,不是书里的): 我们认为这条走查证明了「链里确实有这一步」, 但没有证明「没有这一步就会崩」 —— 因为这个追问自带足够的实词。 如果错,会错在: 如果那两个实词其实不足以命中(比如库再大一点、干扰项再多一点), 那么这个例子就是充分的。判据是把原句直接拿去检索一遍作对照 —— 这个对照书里没做,我们也没有跑。

一个真会崩的追问长这样(下面这两句是我们编的,不是书里的):

用户第一句: RAG 怎么减少胡编?
用户第二句: 「那第二种呢?」 ← 全句没有一个实词
「它比另一种快吗?」 ← 两个代词,零个名词

图说:把这两句直接丢给检索器,它只能靠「第二种」「快」这类词去比远近,
**命中什么全凭运气。** 而模型再强也救不回来——它拿到的资料本来就是错的。

5. 修法:先把追问和历史合成一个能独立成立的问题

做法

在检索之前插一步:把「之前说过的话 + 这一句追问」交给模型, 让它写出一个不依赖上下文、单独拿出来也读得懂的问题。

历史: Q: RAG 怎么减少胡编? A: 把答案落在找回来的证据上。
追问: 那它用的是哪种检索方法?
↓ 交给模型改写
合成: RAG 用的是哪种检索方法? ← 「它」被替换成了「RAG」

拿这一句去检索

图说:改写只服务检索这一步。**生成的时候,原来那句追问和历史照样全都送进提示。**
两件事分工不同,不要合并。

这一步在这一行有个名字,叫追问改写(英文常写作 condense question)5

为什么这么做有效

因为它把「需要上下文才能读懂的一句话」变成了「自己就说得清的一句话」, 而检索器要的正是后者。

检索器能看到的 :只有这一次的查询字串
对话历史在哪 :在提示里,而提示是**检索之后**才拼的

图说:所以补上下文这件事,必须发生在检索之前。
**顺序错了,补了也白补。**

代价

代价具体是什么
多一次模型调用每轮追问都要先跑一遍改写,等待时间大约翻倍
改写本身会错改写模型也可能把「它」认错人,而且错了没人发现
第一轮是浪费第一句话没有历史可合,这一步是白跑的(可以加个判断跳过)

6. 同一类改造还有两条路,书里两条都没做成

追问改写属于一个更大的家族:在检索之前先动一动问题。 这个家族至少还有两招,书里各演示了一次5

路一:把一个复杂问题拆成几个子问题

想干什么: 一个问题里包着三件事,整句丢给检索器,哪一件都命中得不彻底。 拆成三个小问题分别检索,再把答案汇总。

书那个配方的实际做法: 按连接词(and、以及之类)把句子切开, 然后用「两句话共同词的比例」当检索 —— 没有向量,没有模型6

而它的输出恰好暴露了另一件事: 两个子答案里都混进了同一句 It is useful when a question asks for multiple facts or steps —— 这句话里的 It 已经和它的指代对象分开了。 这是第 03 章那颗雷在全书的第一次发作。

路二:先退一步,把问题问得更宽

想干什么: 太具体的问题,它的向量和库里任何一块都不太像。 先把问题抬高一档,拿更宽的问题去撒网,再用原问题作答。

书的提示里给了两个示范:

「谁发现了青霉素?」 → 「医学史上有哪些重要发现?」
「iPhone 什么时候首发?」→ 「科技史上有哪些重大产品发布?」

图说:两个示范都是对的——具体问题被抬成了一类问题。

而模型实际给出的是7:

原问题: Where is the Eiffel Tower located? (埃菲尔铁塔在哪儿?)
它的「退一步」:What is the location of the Eiffel Tower?(埃菲尔铁塔的位置是什么?)

图说:这是**换了个说法**,不是退了一步。范围一点没变宽。
书照样把它当成功案例展示了。

这一招的来历书里一个字没提。 补充(不在书里):它出自 2023 年的一篇论文, 标准叫法是「退一步提问」(step-back prompting)8

这三招的关系,一张表说清:

动问题的哪一头什么时候用
追问改写把缺的东西补进来多轮对话
拆成子问题把一句切成几句一个问题包着几件事
退一步提问把问题抬高一档问得太具体,库里没有那么细的块

7. 另起一处:把标签拼进正文再嵌入

这是本章第一处另起的走查,因为它落在另一条线上:它改的是入库那一头。

问题

第 02 章说过,每份资料都带着一小袋标签(谁写的、什么类别、哪一年)。 第 05 章说过,可以按标签过滤。但过滤有个前提:你得先知道要按什么条件滤。

用户问「Alice 写过哪些健康方面的东西」时,没有人告诉系统「作者 = Alice」。

书这个配方的做法

把标签直接拼进要嵌入的正文里9:

原来的正文: Intermittent fasting improves insulin sensitivity.
它的标签 : author=Alice · category=health · year=2022

拼完之后拿去嵌入的是:
author: Alice category: health year: 2022. Intermittent fasting improves insulin sensitivity.

图说:于是「Alice」和「health」这两个词,**变成了这一块正文的一部分**。

书印出来的结果: 问「What did Alice write about health?」, 命中的正是这一块,答案是 Intermittent fasting improves insulin sensitivity10

为什么这么做有效

因为它把「按条件过滤」这件事,换成了「按意思找」。 用户不必事先知道要滤什么,系统也不必先把问题翻译成过滤条件。

边界

代价具体是什么
稀释主题拼上去的标签占了这一块向量的一部分,正文那部分的分量被摊薄
标签一改就要重嵌标签在向量里了,改一个字就要把这一块重新算一遍
不精确「2022」拼进正文能被搜到,但问不了「2005 年之后」这种范围

所以这条路和按标签过滤不是替代关系,是互补: 能写清条件的用过滤,写不清的靠这一招兜住。

8. 另起一处:第 03 章那颗雷在这里结账

这是本章第二处另起的走查。第 03 章说过「切坏了要在后面结账」,账单就在这里。

书这个配方是一条重排链:先取回 5 条候选,再拿交叉编码器逐条重打分。 问题是 What are the benefits of intermittent fasting?(间歇性禁食有什么好处?)

书印出来的五个分数是11:

重排分那一块的内容
8.5342Intermittent fasting improves insulin sensitivity.(改善胰岛素敏感性)
6.0997Exercise combined with intermittent fasting can boost fat loss.(配合运动能加速减脂)
3.8877Fasting can reduce inflammation and support cellular repair.(减轻炎症、支持细胞修复)
1.2349Drinking water during fasting helps with hydration.(禁食期间喝水有助补水)
−6.5435It may help with weight loss by reducing calorie intake.(可以通过减少热量摄入帮助减重)

减重是间歇性禁食最有名的好处,而这一条排最后,还是负分。

它和第一名差 15 分,和倒数第二名差将近 8 分。
**这不是名次差一点,是被扔出局了。**

五条里唯一的区别:
前四条各自带主语 —— fasting / exercise / water
这一条以 **It** 开头,而 It 指谁**不在这一块里**

图说:打分模型看这一块时,能确定的只有「某个东西有助于减重」。
**「某个东西」是什么,它无从判断。**

第 03 章那条判断在这里拿到了它的账单。 而书把这五个分数原样印了出来,一个字没评。

一句话把这条暗线收掉

第 03 章怎么切,这一章就怎么疼。 切的时候多留一句上下文,或者把刀口下在段落边界上,这条本该第一的文档就不会垫底。 代价是每一块大一点点;收益是它不会被打成负分。

9. 链里接上工具:书里那次是一句 if

链的下一步自然想到:有些问题不该去查资料,该去算、去调外部服务。 让模型自己决定「这一句该调哪个工具」,这件事叫工具调用。

书那个配方里,模型建出来了,但从头到尾没有被调用过。 选哪条路靠的是一句判断:这句话里有没有加减乘除或括号 —— 有就走计算器,没有就走检索12

「What is 25 * 12?」 → 有 `*` → 计算器 → 300 ✅
「What happened in 2008?」 → 没有运算符 → 检索 ✅(碰巧)
「25 减 12 等于几?」 → 没有运算符 → **走检索,答不出来**

图说:能跑通只是因为例句挑得巧。
**真正的工具调用是模型自己决定,而这里是一句写死的判断。**

这不是说 if 语句不能用 —— 规则清楚的时候它更可靠、更便宜。 问题是书把它叫成了工具增强的链,而正文承诺的是模型来决策。

10. 作者的判断与证据

书里给了证据的:

说法证据
链能把检索、推理、生成连成一个结构化的流程那条竖线写法,以及第 ⑤ 步真的答对了追问
那条管多轮对话的链自己管历史第二轮追问命中的是两条讲检索方法的文档
把标签拼进正文能靠意思命中作者名三个查询三次命中,答案与标签对得上
重排能把不该排前面的压下去那五个分数,尤其是 −6.5435 那一条

作者只是断言、没有给证据的:

说法缺什么
「不需要自定义提示」对,但漏掉了为什么 —— 那个链内部做了追问改写,而这才是重点
「退一步链把问题重构成更宽泛的问题」模型给的是同义改写,范围一点没变宽
「工具增强的链让模型调用外部工具」模型没有参与决策,是一句判断语句
「查询拆解链」拆是按连接词切的,检索是按共同词比例算的,没有模型也没有向量
「稠密 + 稀疏混合链」两路都是关键词匹配,没有向量参与
「元数据自查询链」类别是手写在测试用例旁边的,而且查询字串是空的

11. 边界与局限

  • 书没有讲追问改写这一步。 它用了一个内部帮它做了这件事的现成组件, 然后把这件事当成「不需要额外配置」一笔带过。
  • 没有一处讨论对话历史该留多长。 十轮之后历史越堆越长,合成那一步的成本也跟着涨。
  • 同一章里两套写法打架: 有两个配方用的是带下划线开头的内部接口 (还带着「忽略类型检查」的注释),而另外两个用的是公开接口13注释里那句「在 LangChain 1.0.5 里应该用 .generate()」也是错的。
  • 那个「摘要链」里既没有链也没有检索 —— 它是一段纯粹的摘要代码。
  • 重排那一段用了会吞掉一切错误的写法,而且「已有索引就加载」这个卖点因此从未生效。
  • 「渐进式披露」不是流式输出: 它先把答案整个生成完,再按句停顿着打印 —— 代码注释自己写着「模拟」。

12. 可带走的

全章那条走查,一行写完:

四条文档的小库 → 第一轮问「RAG 怎么减少胡编」→ 答 grounding answers in retrieved evidence → 存进历史 → 第二轮追问 And what method does it use for retrieval?(「it」不在句子里)。

链先把追问和历史合成一个能独立成立的问题 → 拿它去检索 → 答案是 BM25 那条 + 稠密检索那条

另有一笔账:重排里唯一真正回答问题的那句因为以 It 开头,被打到 −6.5435、五条垫底。

  1. 链 = 一串可以拆开、可以替换的步骤;那种竖线写法叫 LCEL, 一节的输出直接喂给下一节;
  2. 链的顺序是写死的 —— 第一步没找到东西,它照样往下走;
  3. 多轮对话的难点不在存历史,在检索这一步:追问里的代词指向句子外面 —— 我们管这个叫查询侧指代;
  4. 它和第 03 章那颗雷是两种病: 一个坏在库里(块内指代断裂),一个坏在查询里, 修法完全不同;
  5. 修法是在检索之前插一步:把追问和历史合成一个能独立成立的问题(追问改写); 顺序不能反 —— 历史塞进提示帮不到检索,因为检索发生在拼提示之前;
  6. 代价是每轮多一次模型调用,等待时间大约翻倍;
  7. 同一家族还有两招: 把一句拆成几句、把问题抬高一档(退一步提问);
  8. 把标签拼进正文再嵌入,是「按标签过滤」之外的另一条路 —— 用户不必事先知道要滤什么;代价是稀释主题、标签一改就要重嵌、问不了范围条件;
  9. 第 03 章那颗雷的账单是 −6.5435 —— 切的时候多留一句上下文,这条本该第一的就不会垫底;
  10. 看到「工具增强」先看模型有没有参与决策。 书里那次是一句判断语句,模型建出来没用过。

13. 原文地图

主题原书章原文位置
链的定位(把检索、推理、生成连起来)CHAPTER 10 Implementing RAG with Chainstext/17-fm-introduction.txt:7(搜「Chains provide a structured way to connect retrieval」)
竖线写法(提示接模型)同上text/17-fm-introduction.txt:1175(搜「(answer_prompt
主走查:四条语料、两轮问答、追问里的 it同上text/17-fm-introduction.txt:242(搜「RAG reduces hallucinations by grounding」) · text/17-fm-introduction.txt:302(搜「And what method does it use for retrieval?」) · text/17-fm-introduction.txt:320(搜「BM25 is a sparse retrieval method based on term frequency statistics. Dense retrieval」)
「链自己管历史,不需要自定义提示」同上text/17-fm-introduction.txt:276(搜「manages chat history internally」)
退一步链:提示里的两个示范与实际输出同上text/17-fm-introduction.txt:1812(搜「What are important discoveries in the history of medicine?」) · text/17-fm-introduction.txt:1906(搜「Step-Back Reformulation: What is the location of the Eiffel Tower?」)
把标签拼进正文再嵌入同上text/17-fm-introduction.txt:736(搜「page_content=f」) · text/17-fm-introduction.txt:836(搜「author: Alice category: health year: 2022」)
重排账单:−6.5435同上text/17-fm-introduction.txt:1708(搜「Score: 8.5342」) · text/17-fm-introduction.txt:1716(搜「Score: -6.5435」)
工具选择是一句判断语句同上text/17-fm-introduction.txt:900(搜「llm = HuggingFacePipeline(pipeline=local_pipe)」) · text/17-fm-introduction.txt:965(搜「if any(op in query for op in」)
内部接口与公开接口在同一章打架同上text/17-fm-introduction.txt:156(搜「retriever._get_relevant_documents」) · text/17-fm-introduction.txt:1167(搜「retrieved_docs = retriever.invoke(question)」)

Footnotes

  1. 出处:「CHAPTER 10 Implementing RAG with Chains」第 1175 段(text/17-fm-introduction.txt:1175,搜「(answer_prompt | llm).invoke」)与第 1826 段(text/17-fm-introduction.txt:1826,搜「step_back_prompt | llm」)。这一章的定位写在第 7 段(text/17-fm-introduction.txt:7,搜「Chains provide a structured way to connect retrieval」)。补充(不在书里,来自通用知识): 这种竖线写法在 LangChain 里叫 LangChain Expression Language,缩写 LCEL;完整的三段式是「提示模板 | 模型 | 解析器」,书在第 1 章用过完整的三段,这一章只用了前两段。

  2. 出处:同章第 242–252 段(text/17-fm-introduction.txt:242,搜「RAG reduces hallucinations by grounding」)。取几条写在第 264 段(text/17-fm-introduction.txt:264,搜「search_kwargs={k: 2}」)。这个配方用的嵌入模型是 768 维那个,生成模型仍是第 08 章那个 2.5 亿参数的(text/17-fm-introduction.txt:270,搜「google/flan-t5-base」)。

  3. 出处:同章第 314–320 段(text/17-fm-introduction.txt:314,搜「Q1: How does RAG reduce hallucinations?」与 text/17-fm-introduction.txt:320,搜「BM25 is a sparse retrieval method based on term frequency statistics. Dense retrieval」)。追问写在第 302 段(text/17-fm-introduction.txt:302,搜「And what method does it use for retrieval?」);历史的维护在第 298 段(text/17-fm-introduction.txt:298,搜「chat_history.append」)。书没有印出合成后的那个问题。

  4. 出处:同章第 276 段(text/17-fm-introduction.txt:276,搜「manages chat history internally」)。原文只说这个链内部自己管对话历史,所以不需要自定义提示。「内部怎么管」——也就是先把追问和历史合成一个独立问题再检索——一个字没有。

  5. 补充(不在书里,依据我们自己书架上的另一本书):在检索之前先改写问题,是一个有四个常见变招的家族,其中之一正是「接上前文对话消解指代」;这类改写只服务检索,生成时原问题照旧全量送进提示;代价是串行两次模型调用、等待时间翻倍级增加。 依据:本库另一本已拆的书《Learning LangChain》第 04 章(docs/learning-langchain/04-retrieval-generation-rag.md)。 事实=那一章写着改写家族有四个变招——去掉无关文本;接上前文对话消解指代(上一句问了旧金山天气,这句冒出的「LA 呢?」要补全成「洛杉矶天气」);多撒几张网抓相关说法;把复杂问题拆成几个简单问题分别检索再汇总。并明确「改写器只服务检索;生成时问题照旧全量送达」。 2

  6. 出处:「CHAPTER 7 Response Generation with LLM in RAG Systems」第 1344 段(text/14-fm-introduction.txt:1344,搜「splitting on connectors」)。这个配方是纯 Python 的,检索靠的是两句话共同词占比,没有向量也没有模型。它把一个问题切成了四步(text/14-fm-introduction.txt:1605,搜「1. What is RAG」),而第 1、2 两个子答案末尾都挂着同一句以 It 开头的话(text/14-fm-introduction.txt:1615,搜「It is useful」);那句话在证据清单里是独立的一块(text/14-fm-introduction.txt:1639,搜「It is useful when a question asks for multiple facts or steps. (from doc2)」)—— 它的指代对象在另一块里,而这一块被单独检索、单独交付。

  7. 出处:「CHAPTER 10 Implementing RAG with Chains」第 1810–1816 段(text/17-fm-introduction.txt:1812,搜「What are important discoveries in the history of medicine?」)与第 1906 段(text/17-fm-introduction.txt:1906,搜「Step-Back Reformulation: What is the location of the Eiffel Tower?」)。提示里的两个示范都是正确的「退一步」,而模型给出的是同义改写。

  8. 补充(不在书里,依据我们自己书架上的另一本书):「退一步提问」出自 2023 年的一篇论文(Zheng 等,《Take a Step Back: Evoking Reasoning via Abstraction in Large Language Models》,arXiv:2310.06117),做法是让模型把具体问题改写成更概括的问题去检索,等于撒一张更大的网。 依据:本库另一本已拆的书《Essential GraphRAG》第 04 章(docs/essential-graphrag/04-better-retrieval.md)。 事实=那一章写着 step-back prompting(退一步提示)出自 2023 年的论文(Zheng 等),让 LLM 把具体问题改写成更概括的「退一步问题」;并给了它的边界——有些问题概括化之后语义会漂移。

  9. 出处:「CHAPTER 10 Implementing RAG with Chains」第 734–736 段(text/17-fm-introduction.txt:736,搜「page_content=f」)。它把标签拼成「键: 值」的形式接在正文前面,再整块拿去嵌入。原始的三条文档与它们的标签写在第 702–728 段(text/17-fm-introduction.txt:708,搜「author: Alice, category: health」)。顺带:这一行用的是全角引号,照抄会报错。

  10. 出处:同章第 830–836 段(text/17-fm-introduction.txt:830,搜「Query: What did Alice write about health?」与 text/17-fm-introduction.txt:836,搜「author: Alice category: health year: 2022」)。输出里印出来的那一行,正是拼过标签之后的正文 —— 这就是它能靠意思命中作者名的原因。

  11. 出处:同章第 1706–1716 段(text/17-fm-introduction.txt:1708,搜「Score: 8.5342」;text/17-fm-introduction.txt:1716,搜「Score: -6.5435」)。查询在第 1702 段(text/17-fm-introduction.txt:1702,搜「What are the benefits of intermittent fasting?」),重排模型与第 06 章那个是同一个(text/17-fm-introduction.txt:1666,搜「cross-encoder/ms-marco-MiniLM-L-6-v2」)。这些分数没有上下限,不是「有多大把握」那种数 —— 第 06 章讲过。

  12. 出处:同章第 965 段(text/17-fm-introduction.txt:965,搜「if any(op in query for op in」)。生成模型在第 900 段建了出来(text/17-fm-introduction.txt:900,搜「llm = HuggingFacePipeline(pipeline=local_pipe)」),此后再没有被调用过。计算器那一步用的是直接求值,虽然清空了内置函数表,书里没有一句安全提示(text/17-fm-introduction.txt:918,搜「eval(expr」)。

  13. 出处:同章第 156 段(text/17-fm-introduction.txt:156,搜「retriever._get_relevant_documents」)与第 1167 段(text/17-fm-introduction.txt:1167,搜「retrieved_docs = retriever.invoke(question)」)。前者是以下划线开头的内部接口(代码里还跟着一句忽略类型检查的注释),后者是公开接口 —— 同一章里两套写法并存。 第 168 段那句注释(text/17-fm-introduction.txt:168,搜「In LangChain 1.0.5, use」)给的指引也是错的。