跳到主要内容

通读笔记 —— RAG with Python Cookbook(Dominik Polzer / O'Reilly)

这份是备料笔记,不是拆解正文。给后面写大纲、写正文的人用。

0. 这一版是什么

  • 书卡上写的「仅含第 1、2 章的抢先版」已经过时:library/rag-python-cookbook/ 里现在是 2025/2026 完整版,11 章,转码出 39 段(配方书的 Solution / Discussion / See Also 各占一段)。
  • chapters.jsonbookMeta.date = 2026-05-04,版权页写 "Copyright 2026 O'Reilly Media", ISBN 979-8-341-60056-0。正文里多处写「as of January 2026」。
  • 别和 rag-with-python-cookbook(Deepak Dhyani / BPB)串。 两本同名不同书。
  • 旧的七章拆解(照两章抢先版写的)已经挪到 docs/_superseded/rag-python-cookbook-early-release/, 内容基本作废——那七章只覆盖今天的第 3、4 章,且当时把它们叫「第 1、2 章」。

1. 章节与文件对照(转码后章号错位一格,极易引错)

原书章章名文件字符
版权页(跳过)text/01-fm-rag-with-python-cookbook.txt2.0k
Prefacetext/02-fm-preface.txt8.4k
1Getting Started with RAGtext/03-ch01-...txt30.5k
2Foundation Modelstext/04-ch02-...txt32.4k
3Loading Datatext/05-ch03-...txt49.3k
4Data Preparationtext/06-ch04-...txt41.4k
5Embeddingstext/07-ch05-...txt44.1k
6Vector Databases and Similarity Searchestext/08-ch06-...txt48.8k
7Retrievaltext/09-ch07-...txt48.4k
8Agentic RAG(只有 3.6k 在这个文件里)text/10-ch08-...txt3.6k
8Agentic RAG 的八个配方正文text/11-…-33-fm-{solution,discussion,see-also}.txt合计 ~61k
9Graph RAGtext/34-ch09-...txt32.8k
10Evaluating RAG Systemstext/35-ch10-...txt54.3k
11RAG Web Appstext/36-ch11-...txt38.4k
Index(跳过,导航章)text/37-fm-index.txt38.1k
About the Authortext/38-fm-about-the-author.txt0.8k
Colophon(跳过)text/39-fm-colophon.txt1.2k

第 8 章的坑: epub 把第 8 章的每个配方的 Solution / Discussion / See Also 拆成了独立 spine 项, 所以 10-ch08 只有开头 3.6k(章导言 + 8.1 的 Problem),真正的内容在 11–33 号文件里。 写出处时要指到具体那个 11-fm-solution.txt 之类的文件名,人读起来看不出是哪一节, 必须在脚注里写清「第 8 章 · 配方 8.x」

2. Preface(text/02-fm-preface.txt)

  • 全书自述的组织法(第 16–29 段):1–2 打地基 → 3–4 数据流水线 → 5–6 嵌入与存储 → 7 进阶检索 → 8–9 智能系统(agentic / 知识图谱)→ 10–11 上生产(评测 + 部署)。
  • 读者假设(第 12 段):会 Python、懂 API 和数据处理,不要求机器学习背景
  • 利益相关(必须写进总纲第 2 节): 致谢里作者自陈,他在 Siemens Energy 做采购(procurement), 把 RAG 从原型推成了核心系统(第 114 段)。全书的跑例——采购分析师、供应商合同、 质量文件比对 ISO 标准——都来自这个岗位。这不是中立视角,是「大企业内部文档处理」视角。
  • 代码仓:https://github.com/polzerdo55862/RAG-with-Python-Cookbook(第 59 段)。
  • 起点选在 2017 年 "Attention Is All You Need"(第 4 段)——书自己给的时代坐标。

3. 第 1 章 Getting Started with RAG(text/03-ch01)

章导言(第 4–23 段)——全书的问题陈述

三个结构性缺陷,RAG 一次全治(第 6–9 段):

  1. 拿不到你的私有数据(除非你喂给它);
  2. 上下文窗口有上限 → 长文档「昂贵或根本不可能」一次读完;
  3. 信息不够时它会编,而不是承认不知道(hallucinate rather than admit uncertainty)。

RAG 的定义句(第 9 段):用户提问 → 先从知识源检索 → 把检索到的内容当上下文交给模型生成。 最简形态 = retriever(检索器)+ generator(生成器)(第 11 段)。 书自己给的比方:向量库 = 只索引你自己文档的专用搜索引擎,不像 Google 索引整个网(第 11 段)。

伏笔(第 15、17 段): 现代系统「常常用多个检索器」——一个查文档、一个查结构化数据库、 一个调外部 API,模型自己决定该调哪个。这句在第 1 章只是一句白描, 到第 8 章(Agentic RAG)才是全书的落点。图 1-2 的 Harry Potter 多检索器架构就是第 8 章的预告。

反直觉的一句(第 21 段): 「很多人把 RAG 等同于聊天机器人,但聊天界面往往不是最优解。」 作者主张:先用对话摸清意图 → 再转成仪表盘 / 表单 / 自动报告; 很多 RAG 系统完全跑在后台,通过 API、定时报告、或嵌进现有业务系统交付结果。

1.1 挑选高价值场景(第 27–126 段)

  • 两个高价值方向:数据抽取(把合同、发票、技术图纸、会议记录变成结构化可搜的东西) 和 流程自动化(重复但需要判断和随机应变的活)。
  • 表 1-1 是全书最有用的一张表(第 50–102 段),1–5 分打 RAG 适配度:
    • 1 分:「我想和我的季报聊天」——单份文档、量小,直接读或丢给 ChatGPT 就行;
    • 2 分:「帮我总结这一份文档」——通用模型已经做得很好,自建 RAG 加不了价值;
    • 2 分:「自动化高风险决策」——能做但太险,没有人在环 / 审计 / 失效兜底就别做;
    • 4 分:会议录音堆着但进不了知识库(质量取决于转写);
    • 4 分:技术图纸对着规格书查(多模态比对,人做很烦);
    • 5 分:一万份合同里哪些有自动续约条款(量大到人审不动,且输出可核对);
    • 5 分:客服工单里的产品问题聚不起来(跨文档找模式,人在规模上做不到);
    • 5 分:每天几百封客户咨询要分派给对的团队(重复分类,成功标准清楚,ROI 可测)。
  • 判据可以抽成一句(我们的归纳,不是书里的原话):5 分那三条的共同点是「量大 + 输出可验证 + 单条价值低但总量高」;1–2 分那三条的共同点是「单份、低量、通用模型已覆盖」。
  • 图 1-3 的实际案例(第 104 段):制造商每天收供应商几千份质量文件 → 抽取关键信息 → 比对向量库里的 ISO 标准 → 查合规 → 出报告;参数不合规(例子给的是焊缝厚度)时, 系统自动查出供应商联系人、起草跟进邮件。 这是全书最完整的一个「高价值」实例。

Discussion 里的判断(第 112–120 段),这几条是这一章真正的干货:

  • RAG 能做自动化,是因为 LLM 读得懂意图和上下文,不只是匹配模式: 供应商说 "delayed due to weather" 和 "shipment rescheduled", 规则系统要两套处理分支,RAG 知道两句话说的是同一件事(第 112 段)。
  • 反面判据(书里最硬的一条边界):「如果你能写一条正则或一句 SQL 覆盖 95% 的情况, RAG 只是在增加不必要的复杂度和成本。」(第 114 段)
  • 权衡一句话:RAG 拿简单性换灵活性。 规则式自动化跑得更快、每次更便宜、 而且失败方式可预测;RAG 处理边缘情况更好,但要养模型托管、提示工程、评测框架(第 116 段)。
  • 量的门槛(第 118 段):一天处理 10 份文档不值得建 RAG,一天 1000 份就值。
  • 第一个项目别挑跨系统集成的(第 120 段)——成功既要技术也要组织买账。

1.2–1.4 环境(第 128–326 段)——工具台账,拆解里最多压成半节

  • IDE 与编码助手清单(VS Code / PyCharm / Jupyter / Spyder / Vim;Copilot / Cursor / Claude Code)。
  • Jupyter 的边界写得不错(第 244–255 段):notebook 单元格可以任意顺序执行, 从上往下读看不出隐藏依赖;内容存成 JSON,diff 和合并冲突很难处理; 所以「不适合需要严谨测试和版本控制的生产部署」。建议:探索用 notebook, 可复用逻辑抽成 .py 模块再 import 回来,配 %autoreload 2
  • .env 那一节(第 263–320 段):.env 是明文、无加密无访问控制,只当本地开发的便利; 生产用 AWS Secrets Manager / Azure Key Vault / HashiCorp Vault, 它们提供静态与传输加密、审计日志、自动轮换、细粒度权限; 云平台通常把密钥直接注成环境变量,所以应用代码不用改,还是 os.getenv 别用邮件 / Slack / 共享盘传 .env(第 318 段)。

1.5 第一个 RAG 应用(第 328–504 段)——全书的骨架图

三个必备件(第 336–342 段):嵌入模型(把文本变成向量)+ 向量库(搜这些向量)+ LLM(拿检索到的上下文生成答案)。

两条线(第 344–364 段):

入库线(离线): 切块 → 每块算嵌入 → 存进向量库
问答线(在线): 问题算嵌入 → 在库里找最近的 → 选出最相关的几块 → 连问题一起交给 LLM → 生成
  • 例子是「哈利波特知识库聊天机器人」,text-embedding-3-small + Chroma + gpt-5-mini
  • 书自己声明第 1 章故意不用框架(第 380 段):「不用 LangChain / LlamaIndex, 是为了让核心概念保持清楚」——这句可以用来支撑我们的讲法。
  • chunk_text(text, size=1000, overlap=200):先按 \n\n 找断点,找不到再按 ". ", 都找不到就硬切;start = end - overlap这是全书第一次出现「重叠」,给的值是 200。
  • Discussion 的边界(第 492–494 段):这套基础形态在两种情况下失效—— ① 问题需要多步推理(「比较 X 和 Y,然后推荐 Z」);② 不同数据类型需要不同检索策略。
  • 一条可直接带走的排障法则(第 494 段):先单独测检索,再去调生成。 「如果答案听着很泛、和你的文档无关,问题通常出在检索质量,不在 LLM。」

1.6 框架怎么选(第 506–596 段)

  • 表 1-2 九类库(编排 / 模型与嵌入 / 向量存储 / 数据处理 / 多模态 / NLP / 评测 / Web 与部署 / 传统数据库)。
  • 判断很硬(第 590–596 段): 框架会拖进大量依赖, 「100+ 依赖的项目(全量用框架时很常见)比只用 5–10 个核心库的项目要多花明显更多时间排障」; 只需要向量搜索 + 生成的简单应用,直接用 OpenAI + Chroma 是 2–3 个依赖,用框架是 50+
  • 方向性的一条:从简单实现迁到框架容易,从框架退回简单实现「难得多」(第 596 段)。 这是「先简后繁」的理由,不是口号。

1.7 跑代码(第 598–662 段)

  • 代码错误(要记进「书的缺口 / 勘误」清单):第 611–612 段 git clone git@github.com:polzerdo55862/RAG-with-Python-Cookbook.git 之后 cd rag-oreilly-book —— 目录名对不上,clone 出来的目录叫 RAG-with-Python-Cookbook
  • 第 616–626 段:Windows 那段装 requirements_ch08.txt、macOS 那段装 requirements_ch02.txt, 两边不一致,只是举例但会让人困惑。
  • 目录结构里写 01_loading_data/ 02_chunking_data/ 03_text_embedding/ … 11_building_rag_apps/ —— 仓库目录编号和书里的章号错位(书里第 3 章才是 Loading Data)。

4. 第 2 章 Foundation Models(text/04-ch02)

章导言(第 4–24 段)

这一章的定位很重要:基础模型在 RAG 里出现在两个地方,不是一个(第 6、8 段):

  • 生成步:读检索到的上下文 + 用户问题,产出答案;
  • 准备步(也叫 ingestion phase,入库阶段):从图片里抽文字、转写音频、 给长文档做摘要、给内容补元数据以改善检索质量

这是全书反复用的一条暗线:模型不只在回答时用,更多是在入库时用。 (第 3、4 章大量依赖这一点:图片摘要、音视频转写、元数据抽取。)

2.1 提示模板(第 28–90 段)

四件套:角色 + 指令 + 检索到的上下文 + 输出要求。 例子是采购分析师(又是作者的岗位)。关键设计:显式命令模型「只用检索到的上下文回答, 没覆盖就说没有」,这样每个答案都能追回真实来源(第 46、76 段)。

Discussion 的权衡(第 82 段):模板太松,模型会掺训练数据、编造引用; 模板太紧,模型会拒答那些部分落在上下文之外的边缘情况。 另外:模板本身占 token,每次检索调用都要付一遍(第 84 段)。

2.2 选生成模型(第 92–194 段)

四档(表 2-1,第 104–154 段,标注 "as of January 2026"):

用在哪书举的例子输入价(每百万 token)
超高效 ultra-efficient简单任务、高吞吐实时、分类、边缘设备Gemini 3 Flash、Claude Haiku 4.5、GPT-5 nano~$0.05–0.20
高效/日常一般生产力、生成与摘要、聊天与客服Gemini 2.5 Flash、GPT-5 mini、GPT-4.1~$0.25–0.60
旗舰 flagship复杂/创造性任务、深度分析、代码生成、对客自动化Gemini 3 Pro、GPT-5、Claude Sonnet 4.5~$1–5
推理/前沿多步推理、科学数学、长上下文规划、自主 agentGPT-5、Claude Opus 4.5~$2–30

书给的机制解释(第 156 段,值得原样保留的一句道理): 输出 token 通常贵得多,因为模型必须一个一个地生成,每生成一个都要考虑前面所有的; 而输入 token 是在一次前向传播里并行处理的

选型口径(第 183–189 段):目标是「挑仍然满足要求的最小模型」,而且是逐个流水线步骤地挑。 从延迟约束倒推:实时聊天要几秒内,「用户很少愿意等超过 10 秒,除非答案价值特别高」; 标准聊天场景(通常三到四块检索结果)超高效或高效档就够,大模型带来的增量不足以抵延迟。 批处理容忍分钟到小时级,所以精度提升可能值回更贵的模型。

Tip(第 193 段)——这是第 8 章的伏笔:「复杂工作流拆成小步,每步选各自合适的模型。 这样你能用高效模型解多步问题,因为每次调用只处理一个定义清楚的任务, 而不是要在整个工作流上推理。」

2.3–2.6 四条接入路(第 202–608 段)

怎么接书给的选择理由
OpenAI(2.3)OPENAI_API_KEY + openai SDK「高吞吐生产部署时,速率限制和吞吐通常超过对手」;Azure OpenAI 可让数据留在 Azure 内(第 295、299 段)
Gemini(2.4)复用 OpenAI SDK,只改 base_urlgenerativelanguage.googleapis.com/v1beta/openai/强多模态 + 长上下文;文档超过 10 万 token,或要把图/音/文放进同一个请求时选它(第 358 段)
Anthropic(2.5)必须用 anthropic SDK,协议不兼容 OpenAI「倾向于给出更长的解释,并且显式标出歧义,而不是挑一个看着合理的答案」;法务审阅、合规分析、代码生成(第 417–419 段)
Ollama(2.6)本地跑,http://localhost:11434/v1,同样复用 OpenAI SDK数据敏感不能出网,或调用量大到自托管更便宜(第 597 段)
  • 消息结构(第 231 段):role(system / user / assistant)+ content,两个字段是必填。
  • 反向的一条(第 360 段):低延迟短上下文场景(聊天、检索步、agent 循环), OpenAI 的首 token 时间通常比 Gemini 快——Gemini 的架构是为长上下文和多模态优化的, 小请求上反而有额外开销。
  • 本地模型的硬件门槛(第 595 段):8B 模型至少 8 GB 显存,13B 要 16 GB 以上; 70B 级别「对大多数笔记本来说太大」。
  • 表 2-2 开源模型清单(Llama 2 / GPT OSS / Mistral 7B / Qwen 7B、14B / Falcon 40B)。 这张表已经过时得离谱(见下面「缺口」)。
  • Warning(第 610–624 段)——这一段是全书少见的方法论,值得单独讲: 排行榜只能当筛选工具,不能当最终判据。 三条原因: ① 基准泄漏 / 数据污染——题和答案是公开的,可能已经(直接或间接)进了训练数据,把分刷高; ② 古德哈特定律 / 过度优化——基准一旦成为目标,厂商就会专门为它调,分涨了但通用价值没涨; ③ 和真实使用不匹配——你的提示模板、检索质量、工具调用、长上下文、多语言、量化、 延迟约束都会实质改变结果,「排行榜上赢的模型在你自己的 RAG 查询上照样可能更差」。

2.7 结构化输出(第 626–706 段)

  • 用 Pydantic BaseModel 定义 schema(LineItem / Invoice), SDK 校验模型输出并返回带类型的 Python 对象,不用手工解析 JSON。
  • 机制一句话(第 687 段):API 把模型的生成过程约束住,只允许产出符合你 schema 的合法 JSON。
  • 代价(第 701 段):增加延迟;而且 schema 不匹配时会压掉有用的回答—— 正确答案是「看情况」时硬逼它二选一,就丢掉了细节。

5. 到第 2 章为止已经能看出的「书的缺口」(待补外部来源)

  1. 模型清单与型号已经自相矛盾、且必然过时。 表 2-1(标 "January 2026")写 Gemini 3 Flash / Gemini 3 Pro / Claude Opus 4.5,而 2.4 的正文只讲 Gemini 2.5 Pro/Flash; 2.3 的示例代码写 model="gpt-5.2",2.7 写 gpt-5-mini,1.5 写 gpt-5-mini拆解不能照抄这些型号,要写成「档位」并注明具体型号会变。
  2. 表 2-2「流行开源模型」停在 2023–2025:Llama 2(2023-07)、Mistral 7B(2023-09)、 Qwen 7B/14B(2023-09)、Falcon 40B(2023-05),而 2.6 的示例代码里又拉了 qwen3:4bollama pull llama2 拉的是两年前的模型。需要补一句时代坐标。
  3. 代码错误(已确认两处,继续记):
    • 1.7:git clone …/RAG-with-Python-Cookbook.git 之后 cd rag-oreilly-book,目录名对不上;
    • 2.7:client.responses.parse(..., response_format=Invoice) —— OpenAI Responses API 的解析参数名是 text_format=,response_format= 是 Chat Completions 的写法; 且 {"type": "input_image", "image_url": "invoice.png"} 传本地文件名不成立, 要么是 URL 要么是 base64 data URI。这两条要上网/翻书架核一次再写。
  4. "Attention Is All You Need" 只被提了名字(Preface 第 4 段),没给年份之外的任何交代 —— 我们的读者需要知道它是什么。
  5. 「上下文窗口」全书当已知词用(第 1 章第 6 段首现),从没解释。
  6. 「hallucinate」也是当已知词用(第 1 章第 6 段),只说 "tend to hallucinate rather than admit uncertainty",没讲为什么会这样。

6. 第 3 章 Loading Data(text/05-ch03)—— 11 个配方

章导言(第 4–20 段)

  • 开场那个数(第 4 段):「企业信息约 80% 是非结构化的」,散在演示文稿、文档、邮件、 媒体文件里。书没给出处——这是行业里流传多年的说法,拆解要么补出处要么标成「书里没交代」。
  • 这一章的任务一句话(第 8 段):把各种格式变成一致的文本表示,因为 「大多数 RAG 检索器工作在文本嵌入上」。
  • Warning(第 16–20 段)——全书对框架最硬的一次表态: 本书从零搭核心组件是为了讲清概念;生产里若用 LangChain / LlamaIndex, 要「钉死依赖版本、跟着它们的升级指南、把框架相关代码隔离在小适配层后面」; 「最稳的地基是成熟的通用库」——pandas、NumPy、SQLAlchemy、Pydantic、Requests, 并让 RAG 逻辑在不耦合框架的前提下可测

3.1 Word(第 24–110 段)

两条路:python-docx(把所有段落拼成一个字符串,结构信息全丢) vs Unstructuredpartition_docx(每个元素带 category:Title / NarrativeText / ListItem, 带 element_idlast_modified)。

结构化加载能换来什么(第 96–98 段,这是「为什么值得多花这道工序」的正面回答):

  • 标题可以给更高的检索权重,或单独建索引做分层检索;
  • 列表项可以标出来抽 Q&A 对;
  • last_modified 让检索时能按时间过滤,挡住过时信息;
  • 文档有明确分区时(书的章、政策领域),先靠标题匹配锁定相关分区,再只在分区内搜 —— 「当用户查询能对应到具体文档片段时,这种两段式做法胜过平铺的全文搜索」。
  • 反面(第 100 段):文档短或同质时别这么干,元素级处理的开销划不来。

Note(第 104 段)——一条会影响整条流水线的工程主张: 「大多数加载库(Unstructured、Docling)在 PDF 上尤其成熟。面对多种格式时,先全部转成 PDF。」

3.2 PDF(第 112–176 段)

  • PyPDF2.PdfReader 逐页 extract_text(),顺带存 page.imagesreader.metadata
  • 边界写得清楚(第 164 段):PyPDF2 只能处理「数字生成的 PDF」—— 文字是可选中的字符;扫描件里文字是像素,它必然失败,那种要走 OCR(3.6)或多模态(3.10)。
  • 页级元数据的两个用处(第 166 段):① 语义搜索前先按日期/作者/章节过滤,省算力也提相关性; ② 溯源——用户能跳回原文档的具体那一页去核。

3.3 表格数据的三条路(第 178–297 段)—— 这一节是第 3 章最有讲头的一节

做法适合硬限制
① 行转文本每行改写成一句自然语言(列名变成语境标签)中等规模(几百到几千行)的单条查找:「Sarah 的职位是什么」做不了聚合、百分比、跨行比较
② 整表塞进提示把整张表用 Markdown 写进提示小表(<100 行)、需要全表语境:「这张表里谁工资最高」受上下文窗口限;表一大就贵;大数据上复杂查询表现差
③ text-to-SQL表传进 SQL 数据库;LLM 读 schema + 问题 → 生成 SQL → 再把结果解释成自然语言大数据集,要聚合/过滤/复杂分析:「工程师里多少比例年薪过 10 万」要建数据库、要生成查询逻辑,实现复杂
  • 跑例:Census Income 数据集,15 列 48000 行,存成 Excel(第 197 段)。 create_text_description_of_row 把一行拼成 "The candidate 39 years old is working in the … sector…"。
  • 一句关键提醒(第 230 段):行转文本适合查找,聚合查询(百分比、平均、计数)必须走 ③。
  • 书自己给的组合策略(第 287 段):生产系统常按表的大小与查询模式混用—— 小参考表(产品目录、员工名册)直接塞提示;中等查找型(客户档案、交易日志)行转文本; 大分析型(销售历史、传感器读数)必须 text-to-SQL。
  • Tip(第 291 段)提到论文 "TabLLM: Few-Shot Classification of Tabular Data with LLMs" (Stefan Hegselmann 等)——书里唯一一次把行转文本连到学术工作上。

3.4 PostgreSQL(第 299–353 段)

  • SQLAlchemy + psycopg2-binary,连接串 postgresql+psycopg2://user:pw@host:port/db
  • 值得讲的判断(第 343–347 段):PostgreSQL 在 RAG 里身兼二职—— 既存结构化的应用数据(会话、查询日志、反馈),又能靠 pgvector 扩展当向量库, 「省掉了为关系数据和向量数据各维护一套数据库的复杂度」。 什么时候该选它:查询需要把结构化过滤和向量搜索连接起来 (按账户等级筛客户嵌入、把产品描述和库存状态连起来、按关系表里的权限排除文档)。 反过来,纯相似度搜索、不需要关系连接时,Pinecone / Qdrant / Weaviate 性能更好、API 更简单。 这是第 6 章「选哪个向量库」的伏笔。

3.5 音频(第 355–447 段)

  • OpenAI Whisper,client.audio.transcriptions.create(model="whisper-1", …)
  • 三选一(第 383–395 段):Whisper API(80+ 语言、免运维)/ 本地 Whisper(数据不出门、量大更便宜)/ gpt-4o-transcribe(准确率更高、标点与格式更好、术语处理更好,单位分钟更贵)。
  • Whisper 缺的三样(第 397–403 段),需要专门服务时才换:实时流式(直播字幕、语音助手,亚秒延迟); ② 说话人分离 diarization(谁在什么时候说的,会议纪要和访谈分析的命门); ③ 自定义词表(医疗术语、法律用语、公司名、行话)。
  • 表 3-2 列了 Google / Amazon Transcribe / Azure Speech / Deepgram / AssemblyAI。

3.6 传统 OCR(第 449–629 段)

  • Tesseract + pdf2image(依赖 Poppler)+ Pillow;三步:转 PDF → 每页渲成图 → 逐页 OCR, 并把页码、图片路径、时间戳、PDF 引用一起存下来(第 466、515 段)。
  • 全章最有分量的一个数(第 521 段):多模态 API 每页的成本「通常是本地跑 Tesseract 的 100 倍」。 规模一大,这个差就实打实了。
  • 混合路由(第 523 段):简单的文字型文档走 OCR,复杂的(有表、有图、扫描质量差)走多模态; 用一个文档分类器或版面分析器自动分诊。
  • 表 3-4 决策表(按文档类型 × 量): 纯文字 <1000 份/月 → Tesseract;纯文字 >10000 份/月 → Tesseract 或 EasyOCR; 混合内容 <500 份/月 → 高效多模态;混合内容 >5000 份/月 → 混合路由; 复杂版面(技术图、手写、混排字体)任何量 → 旗舰多模态; 敏感数据不能出网 → 一律开源 OCR 本地跑。
  • 表 3-3 开源替代:EasyOCR(80+ 语言、GPU 加速)、PaddleOCR(版面分析、手写、可上生产)、 docTR(深度学习、模块化、PyTorch/TensorFlow 双支持)、TrOCR(Transformer 架构、擅长手写)。
  • Tip(第 621 段):要版面感知的抽取(表格/表单/阅读顺序)又不能出网, 就本地托管视觉/文档模型,如 Llama 3 Vision 或 Qwen-VL

3.7 多模态模型抽文字(第 631–723 段)

  • base64 编码图片 → {"type": "image_url", "image_url": {"url": "data:image/jpeg;base64,…"}}
  • 比 OCR 强在哪(第 697 段):能输出 Markdown,把标题、项目符号、表格、代码块的原结构保住。
  • 失效方式(第 715 段):在低质量扫描件上「可能编造数字或漏掉内容」。 补救:给出明确的抽取指令(「从每行抽取产品名、价格、重量」), 或关键文档用多个模型交叉验证

3.8 给图片生成文字描述(第 725–807 段)

  • 提示词很朴素:"You are an assistant for visually impaired users. Describe the image in detail." —— 给盲人描述图片,这个角色设定是整个方法的支点。
  • 存的是描述 + 元数据(原文档路径、图在第几页、指向图片周围文字段落的链接)(第 735–741 段)。
  • 重要的岔路(第 798 段):这条路是「图 → 文字 → 文本嵌入」; 另一条是用多模态嵌入模型(书点名 CLIP)让图和文直接进同一个向量空间, 能做图搜图、文搜图、图搜文,不必中转文字。指向 5.5

3.9 表格转摘要(第 809–915 段)

  • 这一节给了全书最好的一个对照例子(第 888–891 段),写拆解时可以直接用作走查:
    • 原始表格串 "Q1: 100, Q2: 120, Q3: 140" 的嵌入只抓到数字, 抓不到「这是每季度 20% 的增长」;
    • 摘要 "Revenue increased by 20% each quarter, rising from $100M in Q1 to $140M in Q3, indicating strong growth momentum" 抓住了模式、趋势、业务含义;
    • 用户问 "Which companies show consistent revenue growth?" 时, 摘要的嵌入能匹配上,原始表格串匹配不上。
  • 什么时候用(第 893–899 段):表里是数值、模式与趋势才是重点;用户问的是分析性问题; 这张表的洞察能用两三句话说清。
  • 什么时候别用(第 901–907 段):表本身就是查找型数据(员工名录、产品规格)——行转文本就够; 表是大参考数据集——该走 text-to-SQL;用户要的是精确的单元格值,不是摘要出来的洞察。
  • 权衡一句话(第 909 段):原始表格文本保住精确值但没有语境;摘要提供语境但可能漏细节、 甚至引入解读错误。

3.10 多模态 PDF(第 917–1044 段)

四步流水线(第 925–934 段):① 切分成 text / images / tables → ② 用多模态模型给图和表生成文字摘要 → ③ 给文字块、图摘要、表摘要统一算文本嵌入 → ④ 连同原路径与页码存进向量库。

  • partition_pdf(extract_images_in_pdf=True, extract_image_block_types=["Image","Table"], …), 并 os.environ["OCR_AGENT"] = "tesseract"
  • 机制句(第 1017–1019 段):文本嵌入模型只处理文本,所以视觉元素在语义搜索里是「隐形」的, 除非先转过来。解法叫「模态转换 modal conversion」。
  • 结果(第 1021 段):问「季度销售趋势」时,叙述段落和内嵌表格都能被命中, 因为两者都以可搜的文本嵌入形式存在。
  • 什么时候跳过(第 1031–1038 段):文档纯文字或视觉元素只是装饰; 视觉内容在周围文字里已经描述过了(表上面那段话已经把表说清了); 或者你能用保住表结构的专门解析走 text-to-SQL,而不是转成摘要。

3.11 视频(第 1046–1180 段)

五步(第 1057–1067 段):切段 → 每个断点存一帧并生成图摘要 → 抽两个断点之间的音频转写 → 两种文本各自算嵌入 → 存库。一个视频段至少产出两条文本:音频转写 + 首帧的图像理解。

  • 跑例:数据科学入门教程视频,讲者每张幻灯片大约讲 30 秒;代码按 10 秒切段。
  • 四种切法(第 1152–1168 段):
    • 固定间隔(10–30 秒):讲座、演示、节奏均匀的教程;实现简单,内容渐变时好用;
    • 场景检测(视觉分析):电影、纪录片、有明显视觉转场的培训片;自动识别剪切与淡入淡出;
    • 按转写文本切:会议、访谈、播客——话题转换发生在场景中间;
    • 章节标记(如果有):最准,「但实践中你很少碰到有这种标记的内容」。
  • 双模态互补(第 1170 段):幻灯演示更吃帧分析(信息在片子上);播客与访谈主要靠音频; 动作多的视频要密采帧。
  • 交付体验上的一条(第 1172 段):段级元数据让答案能带上精确的视频时间戳 ——用户拿到的不只是答案,还有一个跳到那一刻的深链。

7. 第 4 章 Data Preparation(text/06-ch04)—— 8 个配方

章导言(第 4–27 段)—— 全书的核心判据在这里

「这套做法最有效的前提是:每一块恰好装一条信息,而且能被独立看懂。 关键难点就是把块做成不依赖周围上下文也站得住。」(第 6 段)

三类预处理(第 8–22 段):文本准备(替换缩写、清洗)· 元数据收集(页码、来源、作者)· 文本切分(字符 / 递归 / 语义 / 代理式)。

4.1 元数据与元数据过滤(第 31–162 段)

三层元数据(第 39–47 段):① 文档里已有的(作者、标题、创建日期); ② 算出来的(路径、大小、页数、文本长度);③ 可选:让 LLM 从正文里生成

  • 跑例就是拿 attention_is_all_you_need_paper.pdf 做的,PDF 的 Author 字段是空的, 但作者名一定写在正文里 → 定义 Pydantic 的 AuthorContact → 结构化输出把它捞出来。
  • 机制句(第 135 段):元数据过滤是「在算语义相似度之前先把搜索空间缩小」—— 向量库先套元数据约束(author == "Smith"date >= 2023), 再只在过滤出来的子集上做语义搜索。 这样能挡掉「语义相近但语境无关」的假阳性。
  • 图 4-6 那个例子值得复用(第 148 段):「抗氧化剂」的资料在医学和环境工程里都成立, 两边语义都很近但语境完全不同,元数据过滤在语义搜索之前就把它们分开。
  • 四种典型场景(第 138–146 段):HR/工程/财务混在同一个索引里;按年份季度分; 手册/工单/邮件混在一起;同一个词在不同语境下指不同东西(「release」在产品文档和新闻稿里)。
  • 代价数(第 154 段):1000 份文档的语料,LLM 生成元数据要「几分钟 + 1–5 美元 API 费用」。

4.2 展开缩写与术语(第 164–324 段)

  • 两条路:正则表把 NLP 换成 Natural Language Processing (NLP); 或直接让 LLM 改写整段(提示里写「让一个十岁小孩也能看懂」)。
  • 为什么有用(第 249–251 段):RAG 是把块孤立地取出来的,嵌入模型只看得见块里面的词。 词一含糊或缩写,可用的语义信号就少。 「NLP」的信息量远小于「natural language processing」; 而且保留缩写 + 展开式两者并存,「NLP models」「language models」「text understanding」 三种查法都还能命中。
  • 什么时候别用(第 271–277 段):缩写本身就是标准形式(web 开发里的 HTTP); 语料太大预处理太贵;必须实时低延迟入库。
  • 表 4-1「提高块清晰度」的四条,可以直接当读者带得走的清单(第 291–319 段):
技术不要写要写
补上下文句它把效率提高了 40%新的索引算法把搜索效率提高了 40%
避开代词和隐含指代它是去年上线的客户反馈系统是去年上线的
保留实体全名Google 在 2019 年发布了它Google 在 2019 年发布了 BERT 语言模型
避开含糊的词系统现在更好用了系统的响应时间在优化后从 500 毫秒降到 200 毫秒

4.3 假设性问题(第 326–448 段)

  • 做法:入库时给每一块生成至少一个「这块能回答的问题」,给问题算嵌入存进库; 查询时拿用户的问题去比对这些假设性问题,命中后把问题链接回去的原始块交给 LLM (不是把问题交给 LLM)。
  • 为什么有用(第 399–408 段):它缩小的是「用户提问的写法」和「文档的写法」之间的落差。 查询的嵌入天然更接近问句形式的文本,而不是原始技术内容; 这把代码、表格这类难嵌入的东西拉进了和用户查询同一个语义空间。
  • 用在:文档里有代码、配置文件、数据库 schema;日志、转写稿、聊天记录; 表格等结构化格式;用户习惯问「怎么做…」「什么是…」。
  • 别用在:文档本身就是问答形式(FAQ);付不起入库时的额外 LLM 调用; 文本本来就是清楚的叙述性散文;必须实时入库。
  • Note(第 436–438 段)—— 明确的伏笔:这一招在「索引时」解决语义对齐; 「查询时」的对应做法叫 HyDE(假设性文档嵌入),在第 7 章。 HyDE 从用户的问题生成假设性文档,方向正好相反。两者可以只用一个,也可以都用。 See Also 里还补了一句判断(第 442 段):HyDE 更流行,因为它不需要预处理所有文档。

4.4–4.8 五种切法,从笨到聪明

配方切法怎么切什么时候用代价
4.4字符切分固定窗口滑过去,step = chunk_size - overlap;完全无视文档结构原始音视频转写、拼起来的日志、快速原型会把句子和词从中间撕开
4.5递归切分按优先级找分隔符:段落断 → 单个换行 → 标点 → 空格;到了大小上限就用当前可用的最高优先级分隔符格式未知时的默认选择;有清楚段落结构的文章、报告、书对 Markdown/LaTeX/HTML 不如 4.6
4.6文档感知切分用格式自身的语法:Markdown 的 #、Python 的函数与类定义、HTML 标签、LaTeX 的 section源码仓、Markdown 文档、网页、LaTeX 论文要先识别格式、要按格式配解析器
4.7语义切分先切成句 → 每句算嵌入 → 量相邻句的距离 → 相似就并进同一块,掉到阈值以下就断开完全没有格式的文本(转写稿、拼接邮件、聊天记录);话题边界和段落断不重合时每段文字要嵌入两遍(切的时候一遍、入库一遍)
4.8代理式切分让 LLM 把文本拆成命题 proposition:每条只讲一件事、且能脱离上下文被看懂;再把冗余的命题合并有大量交叉引用的法律合同、技术规格;满是代词和隐含指代的文档每块要 2–3 次 LLM 调用

几个必须记住的细节:

  • 4.5 的示例参数是 chunk_size=200, chunk_overlap=0;4.4 的示例是 chunk_size=100, overlap=20; 4.6 的示例是 chunk_size=500, chunk_overlap=50书从没解释这些数是怎么定的, 也没给「块该多大」的通用建议(这一版的第 4 章没有块大小配方)。→ 记进缺口。
  • 4.5 的 Note(第 585 段):默认分隔符表对中文、日文、泰文这类没有词边界的语言可能切错词, 要自定义分隔符表。
  • 4.5 的图 4-15(第 613 段)给了一个好例子:按固定 token 数切,可能把报纸同一版上的 政治、体育、医疗三篇文章切进同一块。
  • 4.7 的百分位阈值机制讲得很具体(第 792 段):先算出全文所有相邻句对的距离, 把阈值定在这些距离的第 90 百分位;只有当相似度的下降幅度超过全文 90% 的下降时才断开。 示例代码 breakpoint_threshold_type="percentile", breakpoint_threshold_amount=90
  • 4.7 的成本(第 820 段):按「每百万 token 0.02 美元」的典型嵌入价算,双倍嵌入「成本可测量」。
  • 4.7 的 Tip(第 824 段):生产别依赖 langchain_experimental, 照它的源码自己实现一份,免得实验性 API 破坏性变更。
  • 4.8 的命题提示词出自「最早描述这个概念的论文之一」(第 854 段), 完整版在 LangChain Hub 的 wfh/proposal-indexing。四条规则:拆复合句、 给带描述的命名实体单独立一条、把代词换成实体全名做去语境化、输出 JSON 字符串列表。
  • 4.8 的最小例子(第 962–970 段)可以直接当走查: 「Sarah bought a new book. She enjoys reading fantasy novels.」 → ①「Sarah bought a new book.」②「Sarah enjoys reading fantasy novels.」 —— "She" 变成了 "Sarah",这一块才真正独立。
  • 4.8 的合并法(第 972–982 段):给所有命题算嵌入 → 算两两相似度 → 余弦相似度 > 0.9 当作待合并候选 → 交给 LLM 生成最终的合并句。
  • 4.8 的成本数(第 988 段,全书最大的一个成本对照):500 块的文档, 用 GPT-4 级模型要「30–60 分钟、15–40 美元」,是语义切分的 10 到 20 倍。
  • 4.8 的定性区分(第 952–960 段),这几句是切分这条线的收束: 其他所有切法都是「在边界上切开已有的文本」,代理式切分是把文本变成新的独立陈述; 语义切分按话题分组,代理式切分做的是去语境化 + 原子化; 它是唯一一种主动消解指代和代词的方法。
  • 两处都指向 Greg Kamradt 的 "5 Levels of Text Splitting"(4.7 与 4.8 的 See Also) —— 五种切法这个框架的来源。书里没说它不是公认术语,要我们点明。

8. 第 5 章 Embeddings(text/07-ch05)—— 7 个配方

章导言(第 4–16 段)

四步检索流(第 6–14 段):建库时切块并嵌入 → 用同一个模型嵌入每个进来的查询 → 算查询向量与库中向量的距离 → 取最近的几块喂给 LLM 当上下文。 「距离越小,语义越相似」这句在第 4 段;它决定了什么能挤进 LLM 有限的上下文窗口。

5.1 文本 → 数(第 20–179 段)—— 全书唯一一次讲嵌入的内部原理

  • 表 5-1(第 35–81 段)四个模型的最大 token 数与维数:
模型出处最大 token维数
text-embedding-3-smallOpenAI8,1911,536
voyage-large-2 instructVoyage AI16,0001,536
text-embedding-005Google2,048768
all-MiniLM-L6-v2开源 / Hugging Face512384
  • 硬约束(第 33 段):文本块的最大长度不能超过嵌入模型支持的最大 token 长度。 —— 这是「块该多大」在全书唯一一个明确的上限依据。
  • OpenAI 返回的向量是归一化过的,单个值大致在 −1 到 1 之间(第 110 段)。
  • 对照讲法(第 135–139 段):拿独热编码 one-hot 当反面 —— 「学士」[0,1,0]、「硕士」[1,0,0]、「博士」[0,0,1], 这些随意指派的向量把语义信息全丢了:看不出博士通常先有学士学位, 也看不出这三者代表不同的教育层级。
  • Note(第 143 段)——书里对「向量」的定义句,可直接借用: 「向量就是一串数,用结构化的方式表示某个东西(像地图上的坐标,[5,3] 表示往北五个街区、往东三个街区)。」
  • 图 5-4 的三维玩具例子(第 147–161 段),是全书最好的一处走查素材: 三个维度 = 年龄 / 性别 / 王室地位。 king = [0.8, 0, 1](年长、男性、地位高),princess = [0.3, 1, 0.5](年轻、女性、地位低)。 向量加减法逐位做: king − man + woman = [0.8−0.7+0.6, 0−0+1, 1−0.6+0.5] = [0.7, 1, 0.9] ≈ queen。 「减 man 加 woman 改的是性别那一维,年龄和地位保持不变。」 第二个例子:France − Germany + Berlin = Paris注意:这几个数是书为了讲解编的,不是真实嵌入值——拆解里必须写明这一点。
  • 训练原理(第 165 段):嵌入模型是在大文本数据集上训的神经网络。 数据集里每个不同的词对应输入层的一个神经元;训练时网络对每个输入词预测下一个词 (输入「Google」这个神经元被激活,网络被训练去预测「is」); 训练完成后,隐藏层神经元的权重就成了嵌入模型。这其实是 word2vec 的图景,不是现代 transformer 嵌入模型的做法。书没有说明这个差别。 (记进缺口。)
  • 什么时候别用嵌入(第 171 段):需要精确字符串匹配时(用全文搜索); 面对高度结构化的数据时(用 SQL);可解释性关键时——「嵌入是不透明的盒子」。
  • 一条会影响选型的判断(第 173 段):嵌入模型的演进没有 LLM 那么剧烈, 几年前的模型今天大多仍然够用,所以选型「更多是基础设施约束(云还是本地)的问题, 不是精度差异的问题」。 第 5.4 的 Note(第 448 段)再说一遍: 很多几年前建的 RAG 系统还在用 text-embedding-ada-002,选型基本是一次性决定

5.2 降维可视化(第 181–281 段)

  • 用 scikit-learn 的 PCA 把 1536 维压到 2 维,Matplotlib 画散点。 跑例六句话,结果:食物饮料聚在右边,天气类在左下,花类在左上(第 260 段)。
  • 机制(第 268 段):PCA 找的是数据里方差最大的那些方向,拿它们当新坐标轴, 所以 1536 维里靠得近的块,在 2 维里通常仍然靠得近。
  • 边界很实在(第 272 段):别拿 2 维图当生产诊断工具——它把 1536 维压成 2 维, 展示不出全部关系。开发期用它验聚类,生产期改看 precision@k 这类检索指标。
  • 权衡(第 274 段):UMAP 和 t-SNE 比 PCA 更能保住局部结构,但算得更慢, 参数没调好还会画出误导人的图案;PCA 更快、而且是确定性的,代价是非线性关系抓得不好。

5.3 距离怎么算(第 283–383 段)

  • 余弦相似度公式 cos = A·B / (‖A‖‖B‖),用 NumPy 的 np.dotnorm
  • 跑例:三条史实 + 一个问题 "Tell me something interesting about diseases in history"; 黑死病那条的余弦相似度 0.31 最高(第 352、359 段)。 ⚠️ 0.31 这个数看着很低但它是相对最高——拆解要交代清楚,否则读者会误以为「0.31 = 不相似」。
  • 为什么 RAG 默认选余弦(第 365、371 段),这段讲得很好: 余弦量的是两个向量之间的夹角,不是绝对长度。 用户查询「reset password」很短,解释重置流程的段落很长, 两者指向同一个语义方向,所以拿到高分,尽管向量长度差很多。 「不做归一化的话,长文档会仅仅因为长度就压过排名,而不是因为相关。」
  • 什么时候用别的(第 373–375 段): 欧氏距离——向量长度本身携带语义时(现代嵌入里很少见); 点积——生产系统追速度时,索引时归一化一次、之后重复使用,省掉反复归一化; 曼哈顿距离——除非是高维稀疏向量,否则避开。 书自己下的量化判断:「余弦相似度仍然是 95% 以上基于 transformer 嵌入的 RAG 系统的默认选择」 ——这个 95% 没给出处,是作者的经验陈述。

5.4 选嵌入模型(第 385–454 段)—— 七步流程,可直接做成清单

  1. 查基准:Hugging Face 上的 MTEB(Massive Text Embedding Benchmark)排行榜; 多语言系统另查 MIRACL;
  2. 确定部署约束:能不能用商业云 API,还是因数据敏感/离线要求必须本地;
  3. 立基线:商业侧用 text-embedding-3-small,本地侧用 all-MiniLM-L6-v2;
  4. 拿真实数据测:从实际场景里取 50–100 条有代表性的查询跑端到端, 看正确文档有没有出现在最前面;
  5. 比替代:基线不够就换更大的(如 text-embedding-3-large)用同一组查询量提升幅度;
  6. 量性能:用向量库的查询端点测平均检索延迟,交互式应用要 <100 毫秒;
  7. 算钱:预期月查询量 × 每 token 单价,和基线比,确认预算装得下。
  • 表 5-2 选型准则:>100 万文档或 >1000 次查询/天 → 优先小模型的速度与成本; 精度关键(法律/医疗/金融)且 <10 万文档 → 用更大的模型; 实时面客 → 挑 <50 毫秒延迟的;批处理/离线索引 → 精度可以压过速度。
  • 最有分量的一条(第 442 段):「别把嵌入模型选大了。 如果 text-embedding-3-small 的基线测出 85% 检索准确率,换 text-embedding-3-large 是 87%, 这 2% 很少值得让成本和延迟翻倍。把优化力气花在切块策略和检索配置上。」
  • 主要风险(第 444 段):没在自己的真实数据上验证就定下模型。 MTEB 量的是跨任务的通用表现,你的具体领域(法律合同、病历、代码文档)行为可能不同。

5.5 CLIP:图和文进同一个空间(第 456–551 段)

  • openai/clip-vit-base-patch32,CLIPModel + CLIPProcessor, outputs.logits_per_textsoftmax(dim=1) 得到概率。
  • 演示的是分类(猫/狗),理由说得明白(第 480 段): 「图像分类器和 RAG 检索器工作方式一样——都是比对嵌入找最近的那个」。
  • 机制(第 531 段):CLIP 在互联网上数百万对「图—文」上训练, 学会把两种模态映射进同一个向量空间;关键在于语义相近的内容最后落在一起,不管它是图还是文。 一张狗的照片产生的向量靠近文本「a photo of a dog」,因为模型在训练时学到了这个关联。
  • 零样本的代价(第 525 段,书自己点破的陷阱):概率在所有类上加起来永远是 1, 所以如果图里是大象、而候选类只有猫和狗,它照样会给猫或狗一个很高的概率。
  • Warning(第 543 段):CLIP 处理不了图片里的文字。 一张「禁止停车」路牌的照片可能不会靠近文本 "no parking",因为模型把它当成一张普通路牌图。 解法:把文字单独抽出来另行嵌入。
  • 书自己的推荐(第 539 段)—— 这条很重要,和第 3 章 3.8 呼应: 「对多数 RAG 应用,更简单的做法反而更好:用多模态模型给图片生成文字描述, 再用标准文本嵌入模型嵌入这些描述。向量库里存文字描述,那一块被检索到时只把原图交给 LLM。 这样能吃到文本嵌入模型在语义文本匹配上的优化,而 CLIP 那种图文直连反倒增加了复杂度。」

5.6 用嵌入做分类路由(第 553–686 段)

  • 做法:切块 → 每块算嵌入 → 拿 1536 维嵌入当特征矩阵 X、标签当 y, 训一个 scikit-learn 的随机森林分类器;新查询进来先分类,再只在对应子集里做语义检索。
  • 跑例:两个 PDF(深度学习史 / 英超史),问 "What is the name of the top football league in England?" → 模型给英超类 94% 的置信度(第 649–660 段)。
  • 它解决的具体毛病(第 670 段,这个例子很好用): 问「market performance」时,嵌入模型觉得体育报道里的「team performance」语义很近, 会把体育文章捞上来而不是金融数据。分类路由先把查询送到对的领域,挡住这种混淆。
  • 收益(第 668 段):不用在全部 10 万份文档里搜,分类器把体育类查询路由到那 2 万份体育文档, 既降延迟又提精度。
  • 用的前提(第 672 段):类别清楚稳定(体育/政治/金融),每类至少 100–200 个已标注的块
  • 什么时候改用元数据过滤(第 674 段)——这条是两种做法的分界: 类别重叠严重、边界模糊时别用分类器。「体育博彩监管」同时属于体育和法律, 硬塞进一个类别会丢信息;这时该在入库时打上类别元数据字段, 查询时 category IN ['sports','legal'] 过滤,这样能保住多个相关类别的文档。
  • 什么时候根本不用(第 676 段):文档集小(<10000 份)或同质。 「先从纯语义搜索开始。只有当你在检索日志里观察到跨领域混淆时,才加分类。」
  • 代价(第 678 段):要标注数据、内容演化时要定期重训、额外 10–20 毫秒推理延迟; 而且分类器会犯错——路由错了就彻底丢掉相关文档,不像语义搜索至少还能返回次优但仍相关的结果。

5.7 混合检索(第 688–842 段)—— 注意:第 6 章 6.7 还有一份,做法不同

三件套(第 711–717 段):BM25 关键词排名 + 文本嵌入的语义排名 + 一种排名融合法

  • rank_bm25BM25Okapi,把文本按空格切词。
  • RRF(reciprocal rank fusion,倒数排名融合)的公式就在代码里(第 809–812 段): rrf_score = 1/(k + keyword_rank) + 1/(k + semantic_rank),示例 k = 60两个榜上都靠前的文档拿到最高的合并分。
  • 参数 k 的含义(第 836 段):k 控制多大程度上偏向榜首。 k 大(60–100)给靠后的名次更多权重;k 小(10–20)强烈偏向那些在两个检索器里都排得很高的项。
  • 该用的场景(第 827–829 段),全是「嵌入抓不住的精确串」: 产品 SKU(「SKU-4792」必须精确匹配)、法条编号(「article 230」和「section 230」不是一回事)、 医疗诊断码(ICD-10)、技术标识符(API 端点名、数据库表名); 以及用户把自然语言和精确术语混着问的时候 ——「How do I configure SSL for endpoint /api/v2/users?」既要理解 "configure SSL", 又要精确匹配那个端点路径。
  • 不该用的场景(第 831–833 段):纯知识检索、用户用自然语言提问、没有技术标识符。 「How do I reset my password?」不需要关键词匹配,加 BM25 只增加复杂度不改善结果。 「先从语义搜索开始。只有当你在检索日志或用户反馈里看到精确匹配失败时,才加混合检索。」

9. 第 6 章 Vector Databases and Similarity Searches(text/08-ch06)—— 7 个配方

章导言(第 4–14 段)

  • 时间线(第 6 段,是我们能拿来当时代坐标的一段):FAISS 2017 → Milvus 与 Weaviate 2019 → Vald 2020 → Pinecone 2021 → Chroma 2023;与此同时 PostgreSQL 和 Elasticsearch 在既有平台上加了向量搜索功能。 结论:「如果你现有的 SQL/NoSQL 数据库已经支持向量搜索, 你不一定非要往技术栈里再加一个专门的向量库。」
  • Note(第 14 段)—— 一处必须照搬的术语澄清: 「相似度搜索(similarity search)」指的是「找出向量空间里靠得近的向量」这个技术操作; 「语义搜索(semantic search)」指的是「按意思而不是按精确关键词来搜」这个面向用户的能力。 语义搜索是用相似度搜索实现的。

6.1 选向量库(第 18–206 段)—— 四步决策路径

第1步 向量搜索是不是活的线上应用的一部分?
不是(预处理/实验/离线流水线) → FAISS 或 Annoy;本地原型用 Chroma。完
是 → 第2步
第2步 嵌入需不需要和你的业务数据、用户、文档、权限待在一起?
要 → 用你已经在运维的那个数据库:PostgreSQL + pgvector / MongoDB 向量搜索 /
Redis 或 Cassandra 的向量支持。完
不要 → 第3步
第3步 会不会长到几百万甚至几千万条向量?需不需要过滤、访问控制、可预测的延迟?
要 → 自建选 Weaviate / Qdrant / Milvus;全托管选 Pinecone
不要 → Chroma 这种进程内的库可能还够
第4步 拿表 6-1 的四个现实问题再筛一遍

表 6-1 的四问(第 71–90 段):数据能不能发给第三方托管云 → Pinecone 或云托管 PostgreSQL; 已经在运维 PostgreSQL 或 MongoDB → pgvector 或 MongoDB 向量搜索; 需要关键词搜索 + 向量 → Elasticsearch / OpenSearch / Weaviate; 小团队要求配置最少 → Chroma 或托管的 Pinecone。

表 6-2 五个选项的利弊(第 94–173 段):

类型好处代价
FAISS向量库(library)相似度搜索非常快;支持 GPU;适合流水线不持久化;无过滤无访问控制;别的全得自己造
Chroma进程内嵌向量存储装上就能用;适合 notebook 和原型;Python 优先的 API不是生产级;扩展性与持久性有限
PostgreSQL(pgvector)带向量的关系库向量和业务数据待在一起;沿用现有备份与安全;连接和过滤都容易比专用引擎慢;大规模要调优
Weaviate向量数据库为大规模 RAG 而建;支持混合检索;过滤能力强新的一套运维栈;复杂度更高
Pinecone托管向量数据库没有基础设施要管;性能有保证;易扩展只有云;厂商锁定;成本随用量涨
  • 为什么需要专门的向量库(第 177 段):传统数据库没有为「高维向量的最近邻搜索」做优化。 现代嵌入模型产出几百到几千维的向量,暴力搜索太慢。
  • 最值得引的一句判断(第 181 段):「许多团队犯的错是照着基准选,而不是照着工作流选。 几千个块的 RAG 原型不需要分布式向量数据库;服务大量用户的生产助手才需要。」
  • 生态分四类(第 183–193 段):向量库(library)/ 内嵌存储 / 带向量扩展的数据库 / 专用向量引擎。 面向文本搜索的 Elasticsearch、OpenSearch 之所以在 RAG 里流行,是因为 「搜语义文本块的检索器本来就像一个专门的搜索引擎」。
  • 迁移成本那一句很关键(第 198 段):「如果你先用简单的向量库、后来需要完整的数据库, 迁移通常不难。你必须重建索引,但不需要重新生成嵌入——而嵌入才是贵的那部分。」这句话是「先简后繁」在向量库这一侧的正当性依据。

6.2 FAISS(第 208–306 段)

  • faiss.IndexFlatL2(dim) + index.add(...);查询 index.search(q.reshape(1,-1), k)FAISS 要 float32(第 255–256 段)。
  • 身份(第 285 段):Meta 2017 年发布的、用于稠密向量快速相似度搜索的库; 轻量、CPU/GPU 都支持、内存内向量操作性能极好。
  • 适用(第 289 段):不需要跨运行持久化、不需要元数据过滤、不需要按用户区分权限时。 典型是「一次性分析任务」——建索引、查、丢掉。 书给的例子:分析一份合同抽取当事方和日期,向量搜索只在那个处理作业期间需要。
  • 三级阶梯(第 295 段,可以直接做成一张表): FAISS = 内存里的向量索引(可以序列化到磁盘,但没有数据库级的持久化、元数据、并发控制) → Chroma 加上了轻量持久化和基本的元数据过滤 → PostgreSQL + pgvector 加上完整 SQL、事务、生产级访问控制,代价是要管数据库。 「批处理作业和离线索引选 FAISS,需要简单持久化的原型选 Chroma,生产系统选 PostgreSQL。」

6.3 Chroma(第 308–421 段)

  • 两种客户端(第 321–327 段):ephemeral(内存,应用一停数据就没了)persistent(落盘,重启能重新加载)
  • 一条工程建议很值得讲(第 329 段):Chroma 内置了自动分词与生成嵌入的功能(默认用 all-MiniLM-L6-v2),原型阶段用它能加快开发; 但如果你打算以后迁走,就在自己的代码里生成嵌入,只拿 Chroma 存和搜—— 这样换到 PostgreSQL 或 Pinecone 才顺手。
  • 边界(第 409 段):数据集超过几十万条向量、需要基于角色的访问控制、 需要和应用数据做 SQL 连接、或者规模上要求查询延迟低于 10 毫秒时,别用 Chroma。

6.4 PostgreSQL + pgvector(第 423–587 段)

  • Docker Compose 起 ankane/pgvector 镜像;CREATE EXTENSION IF NOT EXISTS vector; 然后 embedding vector(1536) 建列。 维数必须和嵌入模型对上——1536 是因为用了 text-embedding-3-small(第 499 段)。
  • Note(第 439 段)给 Docker 下了定义(把应用和依赖打包成容器,轻量、可移植、跨环境行为一致) ——这是全书为数不多主动解释基础概念的地方,我们可以照做。
  • 定位(第 570–572 段):向量成了原生列,相似度搜索成了 SQL 里的又一个操作符, 于是「用户表 + 文档元数据 + 嵌入」能在一条查询里连起来。 适合到几千万条向量的生产 RAG,尤其在需要 ACID 事务和基于角色的访问控制时。
  • 不适合(第 574 段):要在几亿到几十亿条向量上做个位数毫秒的延迟; 或者纯向量搜索、完全没有元数据过滤(那时 FAISS 或专用库更快)。

6.5 在 PostgreSQL 里做相似度搜索(第 589–692 段)

  • 四个操作符必须记住(第 631–639 段): <=> 余弦距离 · <-> 欧氏距离 · <#> 内积 · <+> 曼哈顿(taxicab)距离。 写法:SELECT 1 - (embedding <=> '<查询向量>') AS cosine_similarity … ORDER BY … DESC注意 1 - 距离 = 相似度 这个换算,书是靠代码表达的,没有用文字点破。
  • 真正的价值(第 676 段):把相似度搜索和 SQL 过滤组合起来。WHERE 先按元数据过滤再算相似度:只搜当前用户有权限看的文档、 只搜某个日期之后发布的、只搜打了特定标签的。 例:法务 RAG 只该搜用户有权查看的合同;新闻 RAG 可能只搜最近 30 天的文章。
  • 性能忠告(第 680 段):别在做相似度搜索的同一条查询里做复杂的多表连接,会拖慢。 先在子查询或 CTE 里过滤,再做相似度搜索。

6.6 索引:IVFFlat 与 HNSW(第 694–865 段)—— 第 6 章最硬的一节

  • 跑例:约 20 万条职位描述,查询 "I am looking for a job as a data scientist in Berlin."
  • 三组实测数(书里唯一一组带数字的性能对照,第 760、792、828 段):
    • 全表扫描(不用索引):Planning 13.669 ms / Execution 85.582 ms;
    • IVFFlat(lists = 30,ivfflat.probes = 3):「大约比全搜索快一倍」(书只给了这句话);
    • HNSW(m = 16, ef_construction = 200,hnsw.ef_search = 50): Planning 1.109 ms / Execution 13.927 ms。 → 85.582 → 13.927 毫秒,快了约六倍;这个对照是我们讲索引时最好的走查素材。
  • 什么时候才需要索引(第 832 段):不到 10 万行,全表扫描常常已经够快,跳过索引; 超过 100 万行,索引就是必需的。
  • 怎么选(第 834 段):查询速度关键且内存够 → HNSW(它在内存里放的数据更多); 内存吃紧、或者想让索引建得更快(代价是查询略慢)→ IVF。 「对多数有几百万条向量的生产 RAG 系统,HNSW 给出最好的查询性能。」
  • IVF 的机制(第 840–849 段),书讲得很清楚: IVF = inverted file(倒排文件)。建索引时把向量空间连同里面所有数据点分成若干个分区, 每个分区有一个质心 centroid,每个数据点同时只属于一个分区。 查询时先比对各个质心和查询向量的位置,找出最近的那个分区,再在这个分区内找最近邻。 失效方式说得很坦白:「最近的质心虽然是所有质心里最近的, 但真正的最近邻可能落在相邻的分区里、就在分界线附近。」 —— 「实践中这通常可以接受,因为精度损失相对小,而性能收益显著。」
  • HNSW 的机制(第 851–853 段):建一张多层图。 高层连接少但跨度长,充当抄近路的捷径;低层连接密,提供细粒度的导航。 书给的比方:像一张多层地图 —— 最高层是国道图,路很少但每条都很长; 一层层往下,路越来越多、越来越细;最底层才是每一条小街。
  • HNSW 的三个超参(第 796–803 段):
    • m —— 每层每个点最多连几个邻居;连接越多图越密,查询通常更快, 但建索引更久、更占内存;典型值 16–64;
    • ef_construction —— 建索引时动态候选表的大小,越大索引质量通常越好;
    • ef_search —— 搜索时考虑多少候选,越大结果越准、查询越慢
  • 索引操作符类:vector_cosine_ops(余弦)/ vector_ip_ops(内积)/ vector_l2_ops(欧氏)。
  • 别的库用什么(第 857 段):FAISS 支持乘积量化 PQ、IVF、HNSW;Annoy 用树; HNSWlib 只做 HNSW;Milvus 支持 HNSW、IVF、PQ。
  • Note(第 766 段)一个实操坑:为了确认索引真的被用上,可能需要强制 PostgreSQL 关掉顺序扫描 ——它有时会在不确定索引更划算时退回顺序扫描。

6.7 在 PostgreSQL 里做混合检索(第 867–975 段)

  • 做法和 5.7 完全不同:5.7 是在 Python 里跑两个检索器再用 RRF 融合; 6.7 是在一条 SQL 里同时算两个分数再加权求和to_tsvector(text_chunk) AS tsv 建全文检索列, ts_rank(tsv, plainto_tsquery(...)) 算关键词分,1 - (embedding <=> …) 算向量分, hybrid_score = text_score * 0.5 + vector_score * 0.5
  • 权重怎么调(第 965 段),这是可直接带走的一条: 先用 0.5 / 0.5。 用户抱怨「相关结果没出来,因为它们没有那个确切的词」→ 把向量权重提到 0.7; 结果「离字面查询漂太远」→ 把文本权重提到 0.7。 法律与技术类应用通常需要更高的文本权重,对话类应用通常需要更高的向量权重。
  • 三者的分工(第 967 段):混合检索——字面词和意思都重要时; SQL 过滤(6.5)——需要按元数据缩小范围时;纯语义搜索——只靠意思就够时。
  • ⚠️ 代码错误(第 938–945 段,这是全书目前最实质的一处): 用户查询是 "I am looking for a job as a data scientist in Berlin.", 但 SQL 里三处 plainto_tsquery('PostgreSQL') 全都硬编码成了字符串 'PostgreSQL', 关键词那一半根本没在搜用户的查询;而且 WHERE tsv @@ plainto_tsquery('PostgreSQL') 会先把结果过滤成「必须包含 PostgreSQL 这个词」的职位描述。 这个例子跑出来的东西和它声称在演示的东西不是一回事。

10. 第 7 章 Retrieval(text/09-ch07)—— 8 个配方,全书的技术高点

章导言(第 4–68 段)

开篇第一句就是全书最该被记住的一句(第 4 段): 「改进检索这一步,是提高 RAG 系统准确率与相关性最有效的办法。」

图 7-2 给的多步检索工作流(第 8 段)三段: ① 把复杂查询拆成聚焦的子查询 → ② 把每个子查询路由到合适的工具或数据源 → ③ 在各个答案上做推理,综合成一个完整回答。 这三段正好是 7.8(拆解)+ 7.4(路由)+ 7.7(重排)。

表 7-1 的八项技术,可以按「发生在哪一步」重排成三组(这是我们的归纳):

发生在哪技术配方
改查询(检索之前)HyDE(生成假设性文档)· 多查询(同一问题的多种问法)· 查询拆解(拆成不同的子问题)7.2 · 7.3 · 7.8
缩范围(检索的时候)元数据过滤 · 查询路由7.1 · 7.4
补上下文 / 挑结果(检索之后)自动合并检索器 · 句子窗口检索器 · 重排序7.5 · 7.6 · 7.7

7.1 元数据过滤(第 72–213 段)

  • 跑例极好用:体育网站的聊天机器人,涵盖足球、网球、篮球。 用户问「谁是史上最强球员?」——不知道是哪项运动就没法答。 已知 Jim 是利物浦的足球迷 → 过滤到足球内容再搜 → 命中梅西那条金球奖(第 81–87、187 段)。
  • 实现:PostgreSQL 表里加一列 metadata JSONB,查询时 WHERE metadata->>'topic' = %s
  • 一条延伸做法(第 191–193 段):也可以让 LLM 分析问题和库里有什么内容, 自己提出该用哪些元数据过滤条件。为了不增延迟,这一步用快而高效的小模型 ——「多数情况下用户要什么是明摆着的」;真正难的是分析复杂片段、得出好结论, 那一步才值得用更大的模型。
  • 一个不显眼但重要的收益(第 203 段):可测性大幅改善 ——按元数据分段之后,你能为每个主题各建一套评测数据集,更容易量性能、找弱点。

7.2 HyDE(第 215–310 段)

  • 定义句(第 223–226 段):HyDE = hypothetical document embeddings,假设性文档嵌入。 让 LLM 先按用户的问题编一段「可能包含答案」的文档,拿这段编出来的文档去做语义搜索。 「这些假设性文档并不追求事实正确。它们只是看起来像你库里真实文档的那种段落, 写成和真实文档相似的风格。」
  • 流程四步:用户提问 → LLM 生成假设性文档 → 嵌入它并搜库 → 拿检索回来的真实文档生成最终答案关键:假设性文档不会进最终提示,它们被检索回来的真实文档替换掉(第 287 段)。
  • 机制一句话(第 291 段):HyDE 把查询变成风格上和你的语料相匹配的文本。 如果你的文档是措辞正式的科学论文,HyDE 就生成正式的假设段落; 如果是随意的博客,它就生成随意的文本。 于是嵌入相似度变成了「风格 + 主题」,而不只是「主题」。
  • 用在哪(第 293 段):用户提问用词/风格和存的文档对不上时。 书给的例子:用户问 "How do I fix my car?",而你的文档写的是 "automotive repair procedures"。
  • 主要风险(第 297 段):生成的假设性文档看着合理但其实错了,把检索带偏。 「在模型缺少知识的专门领域里更容易发生。上生产前要充分测试。」
  • 和查询扩展的区别(第 301 段):查询扩展生成同一个问题的不同问法, HyDE 生成的是假设性的答案段落。

7.3 多查询检索(第 312–413 段)

  • 做法:把原问题变出两三个不同的问法,各搜一次,合并结果。 跑例 "What are the benefits of renewable energy?" 变出三条: 环境上的好处 / 怎么带动经济增长 / 可再生能源的缺点是什么。 三条 + 原问题 = 总共四次向量搜索(第 391 段)。
  • 书自己给了「召回率 recall」的定义(第 399 段),可以直接借用: 「召回率量的是,在你数据库里存在的所有相关文档中,检索器找回了多少。 对 RAG 来说,高召回意味着检索器成功找到了大部分有用信息, 即使它同时也捞回了一些之后会被过滤掉的无关内容。」
  • 代价(第 405 段):三次搜索 + 一次生成查询的 LLM 调用,大致让检索时间和算力成本翻三倍。 「复杂研究型查询划算,简单 FAQ 查找不划算。」
  • 和查询拆解的区别(第 407 段,这条要讲清楚,否则两节会读成一件事): 多查询生成的是同一个问题的语义相近的变体; 拆解是把复杂问题切成需要各自独立回答、之后再合并的不同子问题。

7.4 查询路由(第 415–546 段)

  • 直白的例子(第 424 段):问 87 × 99 该交给计算器工具; 问某年 Google 的营收该走向量搜索,因为答案多半写在某份财报里。
  • 实现:用 Pydantic 的 Literal[...] 把 LLM 的输出约束成三个工具名之一, 三条测试查询各自路由到足球知识库、网球知识库、最新赛果 SQL 库(第 479–506 段)。
  • 延迟数字很实在(第 518 段):基于 LLM 的路由每次请求增加 500 毫秒到 2 秒。 「2 秒不见得就是致命伤,但在用户期待快速响应的对话应用里会造成明显的延迟感。 路由更适合批处理、邮件自动化这类异步工作流。」
  • 两条更快的替代路(第 520–526 段):
    • 多数表决(majority vote):全部集合都搜一遍,路由到命中最多的那个; 延迟和单次搜索一样,但算力成本翻倍;
    • 嵌入分类器:训一个轻量模型从查询嵌入预测最合适的集合; 只增加 10 到 50 毫秒,算力开销极小(这就是 5.6 那一节)。
  • 推荐路径(第 528 段):先用 LLM 路由验证路由逻辑是否正确; 延迟成问题、且各集合主题清楚分明时,换嵌入分类器; 集合之间大量重叠时,LLM 路由可能是唯一可靠的选择,只能吃下那个延迟。
  • 和元数据过滤的区别(第 530 段):路由是在搜索之前从多个数据源里选一个; 过滤是在单个数据源之内缩小结果。 两者可以叠加。
  • Tip(第 538 段):框架都有现成的路由方案,但**「路由器往往只是一个写得好的提示词」**, 加框架依赖之前先想清楚需不需要。

7.5 自动合并检索器(第 548–672 段)

  • 要解决的两难(第 552 段):语义搜索想要小块(才够聚焦),生成想要大块(才有上下文)。
  • 做法:小的子块用来做向量搜索;检索之后数一数有多少个子块属于同一个父块, 超过某个阈值就把整个父块交给 LLM。
  • 跑例的具体参数(第 566 段):每个叶块约 250 字符,四个叶块合成一个父块 ≈ 1000 字符。 表里存 leaf_node_id / leaf_chunk / parent_node_id / parent_chunk / leaf_chunk_embedding; 查询后统计「哪个父节点下有超过两个叶节点被判为相关」,对这些父节点取整段父文本。
  • 图 7-13 的具体例子(第 558 段)可直接当走查: 叶节点 1、2、5 相关,其中 1 和 2 属于子节点 1 → 取子节点 1 的完整文本; 而子节点 2 那边只有叶节点 5 命中 → 只取叶节点 5。
  • 用在哪(第 660 段):文档有很强的层级结构(有章有节的书、有小节的论文); 信息横跨多个块导致检索质量下降时。

7.6 句子窗口检索器(第 674–730 段)

  • 做法:命中某一块之后,把它前后各 N 块一起送进提示。 实现的要点:入库时块 ID 必须反映原文顺序(第一段是 1、第二段是 2……), 之后才取得到「前一块」和「后一块」。
  • 跑例参数(第 710 段):每块约 250 token,命中后连前后各一块 → 送进去的窗口约 750 token。
  • 图 7-14 的例子讲得很好(第 682 段):问拜仁慕尼黑, 语义搜索可能命中「近期战绩与奖杯」那一段,却把「这家俱乐部是什么、名字从哪来、在哪里」 那段历史背景切掉了;带上相邻句子才能让这些背景进提示。
  • 代价(第 722 段):从每块 1000 token 扩到 3000 token,上下文长度翻三倍,成本和延迟都涨。
  • 和自动合并的区别(第 724 段,必须写清): 句子窗口是「固定的位置扩张」——不管内容,永远加相邻 N 块; 自动合并是「按相关性扩张」——只有当同一父块下有多个子块命中时才把父块加进来。 句子窗口更简单但更不会随机应变。

7.7 重排序(第 732–821 段)

  • 它解决的问题(第 741–743 段):HyDE 会生成多个假设性文档、跑多次向量搜索; 多查询会并行跑多个改写后的查询;agent 系统可能同时查 SQL、向量库、API、Python 计算。 这些做法都产出一个合并后的结果集,需要过滤。
  • 做法:用一个高效的 LLM 给每份检索到的文档按相关性打 1–5 分,只留前几名。 跑例查询「特斯拉还能不能保住电动车市场领先」,五段候选被重排成 5 → 4 → 2 → 1, 只取前三条进最终提示(第 795–799 段)。
  • 用在哪(第 805 段):从多个来源合并了 20 个以上候选时;初始检索噪声大、 很多结果只是勉强相关时;「重排之后取前五」比「每个来源各取前五」质量更好时。
  • 两阶段的道理(第 812 段,这是这一节的机制核心): 初始打分(余弦相似度、BM25)很快,但它是把查询和文档各自独立处理的; 重排用的是查询与候选之间的交叉注意力(cross-attention),能做更深的相关性判断。 两阶段的组合平衡了速度(初检)与精度(重排)。 重排是对合并结果的第二遍,不是用来替代快速初检的。
  • 收益(第 810 段):能放心地从 50+ 个候选里筛到 5–10 个高质量的。

7.8 查询拆解(第 823–927 段)

  • 提示词里就带了范例(第 863–878 段): 「微软和谷歌去年谁赚得多?」→ ①「微软去年赚了多少?」②「谷歌去年赚了多少?」 并且明确交代:「如果问题本来就简单,就原样留着。」
  • 跑例:「和化石燃料相比,可再生能源有什么好处?」被拆成五个子问题 (可再生能源的好处 / 化石燃料的好处 / 环境影响 / 成本 / 可获得性)(第 902 段)。
  • 代价(第 916 段):多两次 LLM 调用(一次拆、一次综合)外加多次检索。
  • 收益:能回答那些任何单次向量搜索都答不好的问题。
  • See Also 里点了 Demonstrate-Search-Predict 论文,说它「引入了面向检索系统的多跳问题拆解」。

11. 第 8 章 Agentic RAG —— 全书的落点

转码结构(必须记住,否则出处会引错): 章导言 + 8.1 的标题在 text/10-ch08-...txt; 之后每个配方的 Solution / Discussion / See Also 各是一个独立文件。

配方SolutionDiscussionSee Also
8.1 设计一个自定义工具11-fm-solution.txt1213
8.2 多 agent 系统的工作流模式141516
8.3 选一个 agent 框架171819
8.4 用函数调用搭 agent 系统202122
8.5 用 asyncio 给 agent 提速232425
8.6 用 OpenAI Agents SDK + Chroma 做议价 agent2627
8.7 (标题在转码里丢了) 用 MCP 接工具282930
8.8 用 LangGraph 搭 agent 系统313233

⚠️ 两处转码缺口,写出处时要注意:第 8 章所有配方的 Problem 段都没了(其他章都有); ② 配方 8.7 的标题整个丢了——它夹在 8.6 的 See Also 里,而 8.6 没有 See Also 文件。 从内容看它讲的是 MCP + Playwright,我们引用时只能写「第 8 章 · MCP 那一节」。

章导言(10-ch08 第 4–32 段)—— 全书的收束

  • agent 的定义句(第 4 段):「agent 是这样一类系统:LLM 在其中充当决策者。 模型观察当前情况,规划一串动作,并挑选工具去执行这些动作。 就像一个人在火车取消时会改行程,agent 在新信息出现时会调整自己的策略。」
  • 演化三段(第 8–10 段):早期 LLM 应用是孤立的功能(摘要、翻译)→ 长成多步工作流 → 最后成为能自己决定用哪些工具的全 agent 系统。
  • 三个部件(第 16 段):LLM 负责推理与规划 · 工具负责执行动作 · 短期与长期记忆负责让过去的结果影响将来的决定。
  • ReAct(第 20 段):最常用的规划模式。agent 在「想下一步做什么」和「调工具去做」 之间交替,拿结果去修正计划;循环直到达成目标或撞上预设上限(比如最多调几次工具)。 出处:2023 年论文 "ReAct: Synergizing Reasoning and Acting in Language Models", Shunyu Yao 等。 ——这是全书为数不多给了论文名和作者的地方,要保留。

8.1 自定义工具(1113)

  • 机制核心只有一句(11 第 3 段、12 第 5 段): 写一个带详细 docstring 的 Python 函数,docstring 说明用途、输入、输出; LLM 读这个 docstring 来判断该不该用这个工具、怎么调它。 跑例是调 open-meteo 天气 API 的 get_weather(latitude, longitude)
  • 设计原则(12 第 7–9 段):每个工具只承担一件事; 别为已经有现成方案的任务(网页搜索、代码执行)自己造工具; 每个工具都要测试、调试、并随 API 变化更新;调外部服务的工具要处理超时、限流、失败。

8.2 五种工作流模式(1416)—— 书明说这五种出自 Anthropic

模式怎么转书给的例子
提示链 prompt chaining拆成顺序的几步,每次 LLM 调用处理上一步的输出;因为有依赖,只能串行从 PDF 抽文字 → 翻译 → 改写成简单英语
路由 routing第一次 LLM 调用当路由器,分析请求、选最合适的工具或子流程收到邮件:有附件就触发分析/预处理/保存;简单邮件就直接用日历或向量库的信息作答。「大约 5 到 10 种情形就能覆盖处理邮件所需的大部分动作。」
并行任务 parallel tasks多个 LLM/工具调用并行跑各自那一份,最后有个汇总器合并一份长的扫描版合同逐页抽取到期日、解约条款:串行每页要 10–20 秒;10 到 20 个并行跑,把处理时间从几小时降到几分钟。主要约束是 API 端点的每分钟 token 上限。
编排者—工人 orchestrator-workers一个协调者 agent 手下有多个各自专精的工人 agent;最后有个综合者出一份答案三个工人:把请求翻成 SQL 去查库的 / 查 wiki 数据的(向量库 + 语义搜索)/ 用专门提示直接调 LLM 的
评估者—优化者 evaluator-optimizer一个 LLM 出初稿,另一个 LLM 评审;在起草与评估之间迭代,直到评审满意或到迭代上限写技术博客:一个产出段落草稿,评估者给反馈,直到通过

怎么选(15 第 3–13 段): 「可靠性比灵活性更重要时,选工作流模式;任务需要随机应变地做决定时,才选自主 agent ——后者是让 agent 在运行时决定用哪些工具,而不是走固定流程。」 反过来的忠告:别过度设计。 并行模式带来编排开销,只有子任务耗时较长时才划算; 路由模式要求类别清楚,分类含糊时别用;编排者模式因为多次 LLM 调用会拉高延迟。

8.3 选框架(1719)—— 三个抽象层

例子什么时候用
1 无代码(抽象极高)Microsoft Copilot Studio、n8n、Zapier、Make内部两到五步的流程、团队没有工程资源时;计价通常是订阅制而不是按 token
2 agent 框架(代码优先)OpenAI Agents SDK、LangGraph想比从零搭更快、又要保住代码级控制;轻量的那种(如 OpenAI Agents SDK)锁定更少,以后换框架或退回从零实现更容易
3 从零搭(控制最大)直接调模型 API,自己写编排需要最大灵活性或健壮性、且团队有能力自己扛全栈(含日志、追踪、护栏)

表 8-2 的五种情形(17 第 42–88 段):

  • 几周内要把 agentic RAG 推上生产 → 高抽象框架(LangGraph 内置功能多); 从零搭更久,但可能更健壮;
  • 很多中低经验的开发者在维护 → 轻量或高抽象框架; 「只有当团队经验很足、并且已经清楚 agent 该长什么样时,才从零搭。」
  • 服务几千到几百万用户,要健壮与可扩展 → 从零搭或轻量框架(依赖越少,能坏的部件越少);
  • 要一个高度灵活、为某个特定需求定制的 agent → 从零搭或轻量框架;
  • 要实时调试、可观测性、透明度、审计合规日志 → 三种都行: OpenAI Agents SDK 在 OpenAI 网站上有追踪视图;LangGraph 靠 LangSmith 提供类似能力; 从零搭就得自己实现日志与追踪。

Discussion 里最硬的三句(18 第 11–13 段):「早期避开重型框架。先从直接调 API 或轻量框架开始,把需求摸清楚。 每加一层抽象都会遮蔽行为、让调试更难。每加一个依赖都扩大你的安全攻击面。」「框架锁定会随时间复利。LangGraph 的状态管理强加了一种结构,很难迁走。」 ③ OpenAI Agents SDK 靠更简单的抽象把锁定降到最低。

8.4 函数调用(2022)—— 这一节是 agent 的最小内核,拆解里必须走查

  • 一句话机制(20 第 5 段):LLM 只负责决定,Python 负责执行。
  • 工具字典三件:函数名 + 详细描述 + 输入参数(及其 schema); 描述字段的省事做法:直接复用函数的 docstring(add_numbers.__doc__)(20 第 50 段)。
  • 最好的走查素材(20 第 90–118 段): 问 "What is the result of (5.0 + 3.0) * 2.0?" —— 一次调用只解决了括号里的第一步加法, 还没回答用户的问题,但那是正确的第一步。 所以必须放进循环: while finish_reason == "tool_calls": 反复调 LLM → 拿到 tool_calls → 执行 → 把工具结果以 {"role": "tool", "content": ..., "tool_call_id": ...} 追加进 messages → 再调,直到模型返回最终答案。 第二个跑例(20 第 174–206 段)混合了两类工具: 「我五天后到伦敦、待六天,告诉我伦敦现在的天气,并算一下我从今天起几天后回家」 → 第一次调用取天气,第二次调用算天数。
  • 为什么要有工具(21 第 3 段):「LLM 做算术不可靠,所以工具调用把计算委托给 Python 函数去精确执行。docstring 是模型和工具之间的合同。」
  • 副产品(21 第 5 段):因为每次调用都是显式的,所以容易检查、记录、调试。
  • 代价(21 第 9 段):每次工具调用都多一个来回;性能敏感的系统靠批处理或异步来压(→ 8.5)。

8.5 asyncio(2325)

  • 最小例子(23 第 7–31 段):两个协程分别 sleep 2 秒和 1 秒, await asyncio.gather(...)总时间从约 3 秒降到约 2 秒 ≈ 最长那个任务的时间。
  • 真实的一组数(23 第 133 段,全书第二组实测): 扫描版发票逐页抽实体,第 1 页 19 秒、第 2 页 34 秒、第 3 页 38 秒, 整个脚本共 49 秒;串行至少要 90 秒。
  • 机制(24 第 3 段):asyncio 在 I/O 等待期间挂起任务、切到别的就绪任务; 串行执行耗时是各段之和,用 asyncio 大致等于最长那一段。
  • 代价说得很坦白(24 第 9 段):代码复杂度。要懂协程、事件循环、await 语义; 调试更难,因为错误可能发生在同时跑的多个任务里,难以追出是哪个任务出的问题; 错误处理还必须考虑「部分失败」。
  • 并发上限怎么估(23 第 87 段):模型厂商通常会给出「某分辨率下图片的 token 用量怎么估」, 拿它加上每张图的处理时间,就能算出并发多少张不会超限。

8.6 议价 agent(2627)—— 多 agent 的完整跑例

  • 场景具体到能当走查用(26 第 137–143 段): 客户想买 15 台笔记本、每台不超过 950 欧元,零售价是 1299 欧元; 销售给了 12% 折扣;客户说不够,并称别家供应商能给到 1000 欧元。对话停在这里。
  • 三个 agent(26 第 5、168–178 段): 销售 agent(有一个查 Chroma 里 email_history 集合的工具, 库里存着往来邮件、零售价、常见制造成本、产品细节——这就是它的长期记忆)、 客户 agent主持人 agent(轮流触发另外两个)。
  • 关键手法(26 第 172 段、27 第 10 段):把 agent 本身包装成工具 (customer_agent.as_tool(tool_name=..., tool_description=...)), 这样主持人就能像调工具一样调它们——「实现分层委派」。 每个 agent 只暴露一个简单接口(输入文本、输出文本),因而可互换、可单独测试。
  • @function_tool 装饰器把普通 Python 函数变成工具;书顺手解释了什么是装饰器 (26 第 69 段:「装饰器是一种特殊的函数,它修改另一个函数的行为—— 套在原函数外面,给它加上额外的功能。」)。
  • 什么时候别用这个模式(27 第 14 段):延迟关键时。 「每一次 agent 交接都要一次完整的 LLM 调用,多轮对话的成本是线性累加的。 只是简单的任务委派、不需要对话的话,直接函数调用或路由模式更高效。」
  • 一条会过时的事实,但值得记(27 第 20 段):OpenAI 的 Swarm 已经被 OpenAI Agents SDK 完全取代,作者建议还在用 Swarm 的迁走。

8.7 MCP(2830)—— 标题在转码里丢了

  • 跑例:一个做饭助手 agent,用 Playwright MCP 服务器控制浏览器上网找巧克力饼干食谱, 再用 filesystem MCP 服务器把它存成 recipe.md
  • 定义句(29 第 3 段,写得很好,值得照讲:) 「Model Context Protocol(MCP)把「LLM 和工具怎么打交道」标准化了。 就像 HTTP 之于浏览器,MCP 让任何兼容 MCP 的 agent 都能用任何兼容 MCP 的工具, 不需要写定制的集成代码。它解决的是工具集成问题:没有 MCP 的话, 每个框架都要求自己的一套工具格式。」
  • 三个部件(29 第 5 段):客户端(你的 agent 应用)· 服务器(提供工具与数据源, 可以跑在本地也可以跑在远端)· 协议本身。
  • 什么时候用(29 第 9 段):工具要跨项目或跨团队共用时。 「一个数据库查询工具做成 MCP 服务器,任何支持 MCP 的 agent 框架都能用。 你只需要更新这一个服务器,不必维护多个框架专用的版本。」
  • 什么时候不用(29 第 11 段):简单的单一用途 agent、工具和 agent 逻辑紧耦合时。 「协议的开销只有在工具复用或跨框架兼容真的要紧时才划算。」
  • 两条代价,书写得很实在(29 第 13–15 段):MCP 服务器是独立进程,要部署、要监控、要处理网络层的错误; 你的 agent 必须处理「连不上、超时、返回畸形数据」; ② 安全性和本地函数调用不一样——MCP 服务器接受网络请求, 需要认证、授权、输入校验;每加一个 MCP 服务器都扩大你的攻击面。
  • 实操细节:npm search @modelcontextprotocol 列可用服务器; MCPServerStdio(params={"command":"npx","args":["@playwright/mcp@latest"]}, client_session_timeout_seconds=30) —— 默认超时只有 5 秒,书特意调到 30/60 秒。
  • Warning(28 第 7 段):有些 MCP 服务器在 Windows 上跑不顺,尤其在 Jupyter 里; 错误太多就换 Linux(Windows 上可以用 WSL,wsl --install)。

8.8 LangGraph(3133)

  • 三个核心部件(32 第 3–15 段): 状态 state——记录正在发生什么、存共享数据; 节点 nodes——干活的函数,拿当前状态、执行一个动作、返回更新后的状态; 边 edges——连接节点,决定下一个跑哪个。
  • 跑例:天气助手。状态是 AgentState(TypedDict), messages: Annotated[Sequence[BaseMessage], add_messages]; add_messages 是个 reducer:拿当前列表和新消息,返回合并后的列表。
  • 图只有两个节点:llmtools条件边 should_continue 检查最后一条消息里有没有 tool_calls—— 没有就 END,有就去 tools 节点;tools 之后用一条普通边接回 llm,形成循环。这正是 ReAct 循环的最小实现(32 第 17 段明说图 8-27 画的就是 ReAct)。
  • 能画出来(31 第 217–221 段):graph.get_graph().draw_mermaid_png()
  • 该用的理由(32 第 21–23 段):LangGraph 把 agent 的状态和控制流写成了显式的东西。 「边定义了合法的转移,从而防止未定义的状态或无限循环。」 图的可视化能显示所有可能的执行路径以及 agent 实际走了哪一条,便于调试意外的决定。
  • 不该用(32 第 29 段):简单的线性工作流。 「如果你的 agent 走的是固定顺序、没有分支逻辑也没有共享状态,图这层抽象只增加复杂度。」
  • 代价(32 第 31 段):框架耦合。「LangGraph 的状态管理与图执行会深深嵌进你的代码架构。 迁移意味着把整个状态流与转移逻辑重新实现一遍。」
  • 生态三件(32 第 25 段):LangGraph 管工作流 · LangChain 管编排 · LangSmith 管可观测性。

12. 第 9 章 Graph RAG(text/34-ch09)—— 5 个配方

章导言(第 4–38 段)

  • 它补的是什么(第 6 段):向量搜索「把每一块当成孤立的单元,不知道各块在更大的叙述里 是怎么连起来的」。信息分散在长文档的多个部分、或者要跨多个来源时,这种做法会漏掉相关语境 ——依赖关系、交叉引用、跨章节的关系全丢了。
  • Graph RAG 的做法(第 8 段):不只把文本存成嵌入,而是抽取实体、在它们之间建立显式的关系, 再把这张图的结构和向量索引结合起来。
  • 图 9-1 的对照(第 10 段)很清楚:基础 RAG 里是一池孤立的嵌入向量; graph RAG 里每一段文本都被锚在它周围的结构上——每条条款连着它的条款类型、所属公司、 地址、以及它出自哪份服务级别协议(SLA)。
  • 检索四步(第 16–24 段):初检:先用向量搜索或全文搜索找出相关节点,它们是锚点; ② 图扩张:从锚点出发遍历,通常走一到两跳,收集相关的节点和边; ③ (可选)过滤与排序:只保留真正增加语境的节点; ④ 组装上下文并生成:把原始锚点文本和它连着的语境一起交给 LLM。
  • Note(第 34 段)—— 诚实的代价陈述:「图带来比基础 RAG 更高的复杂度和成本。 它们需要仔细的数据建模、结构化的入库、更多的前期工作。」 什么时候值:「当关系本身要紧、而最终答案取决于实体之间怎么连接时。」
  • 全章用 Neo4j。

9.1 建第一张知识图谱(第 40–279 段)

  • 图的模式(第 48–58 段):Company(公司)—LOCATED_AT→ Address(地址); Company —HAS_SLA→ SLA;SLA —HAS_CLAUSE→ Clause(条款); 相邻条款之间 Clause —NEXT→ Clause(保住文档顺序); Clause —OF_TYPE→ ClauseType(把不同供应商里同类主题的条款归到一起)。
  • 建库前先建唯一性约束(第 83 段):「没有约束的话,MERGE 在匹配逻辑含糊时会创建重复节点。」
  • 解析 SLA 文档:按 Markdown 的二级标题 ^##\s+ 正则切开,每一节成为一个 Clause
  • 条款分类是靠关键词匹配的(第 204–219 段),这一点很重要: availability / uptime → Availability;support / response time / incident → Support; maintenance → Maintenance;data protection / gdpr / privacy → DataProtection; liability → Liability;termination → Termination;都不匹配就打成 Other这是一条脆弱的规则,书没有讨论它的失效率。(9.3 的 Discussion 只泛泛说了一句 「如果你的分类体系不完整或不一致,相关内容就会被漏掉」。)
  • 为什么值得建图(第 267 段):「图建模之所以有效,是因为它抓住了嵌入抓不住的关系—— 比如哪条条款属于哪份合同、哪条条款排在下一条。这让你能跨文档比较条款、 按阅读顺序往下走、或者核查一份合同是不是缺了某条必需的条款。」

9.2 用结构化数据扩充(第 281–433 段)

  • 从四个 CSV 导入主数据:公司(名称、国家、行业)、地址年度采购支出(spend_2024spend_category,写成 Company 节点的属性)、 SLA 元数据(生效日期、服务名、适用法律)。
  • 这一步换来的能力,一句问句就说清了(第 417、421 段): 「哪些高支出的供应商缺少解约条款?」「给我看德国医疗行业公司的数据保护条款。」 ——这两类问题需要合同正文和实体属性出现在同一条查询里。
  • 两层结构的说法值得引(第 427 段):9.1 只建了文档结构(SLA、条款、条款类型); 9.2 加上业务实体和它们的属性作为第二个维度。 「结果是一张把内容结构和业务语境结合起来的混合知识图谱—— 这正是企业级 graph RAG 与只有文档的图谱之间的区别。」
  • 代价(第 425 段):支出、地址、组织结构这些属性会随时间变化,必须和源系统保持同步。 数据陈旧会导致过滤错误——比如把一个其实不是低支出的供应商标成低支出。

9.3 第一条 Cypher 查询(第 435–561 段)

五个查询,从简到繁:

  1. 列出某份 SLA 的所有条款,按原顺序(ORDER BY cl.order);
  2. 跨所有 SLA 找出所有 Termination 类型的条款 —— 便于比较不同供应商怎么措辞同一个合同主题;
  3. high_spend_missing_termination(min_spend):找出支出超过阈值、但 SLA 里没有解约条款的公司。 写法值得记:OPTIONAL MATCH + count(cl) AS num_termination + WHERE num_termination = 0;
  4. 只看可用性条款,并按短语过滤(比如某个正常运行时间目标 99.9);
  5. 跨维度组合:先按地址国家筛出欧盟供应商,再取它们的数据保护条款。
  • 三种检索姿势(第 546–548 段,这是这一节的收束): 知道具体是哪份文档/哪个实体 → 直接查; 想跨很多文档比较同类内容 → 从 ClauseType 出发; 业务规则优先 → 先按属性过滤公司或合同,再去看正文。
  • 局限说得很直(第 550 段):「结构化查询又快又准,因为它只返回符合你的标签与过滤的节点。 它的局限是依赖图结构的质量。如果你的分类体系不完整或不一致,相关内容就会被漏掉。」 下一节就是补这个洞的。

9.4 在图上加语义搜索(第 563–708 段)

  • 三步:给每个 Clause 节点算嵌入写回图 → 建 Neo4j 向量索引 (CREATE VECTOR INDEX clause_embeddings … vector.dimensions: 1536, vector.similarity_function: "cosine")→ 用 db.index.vector.queryNodes 查。
  • 混合的关键写法(第 660–689 段):先 CALL db.index.vector.queryNodes(...) YIELD node, score, 再 MATCH (node)<-[:HAS_CLAUSE]-(s:SLA)<-[:HAS_SLA]-(c:Company) WHERE c.industry = $industry —— 即「语义搜出候选,再顺着图往回走到公司,按行业过滤」。
  • 什么时候用(第 698 段):查询里既有意思又有业务约束时。 「当 ClauseType 这类标签本身可靠时,一条纯 Cypher 查询更简单也更快。」
  • 和基础 RAG 的差别(第 702 段):「graph RAG 返回的不是孤立的块。 它把条款连同它相连的业务语境一起返回,这让答案更精确、也更能解释得清。」

9.5 优化知识图谱(第 710–804 段)

三种优化,都是「把工作从查询时挪到入库时」(第 784 段):

优化解决什么瓶颈具体做法
条款摘要检索回很多条款但只有几条相关用高效模型(书写的是 gpt-4o-mini)给每条条款生成 summary 属性,让模型能便宜地扫过很多条款再去读全文
聚合值老是在重复算同样的计数或标志例:每家公司每种条款类型有几条,SET c[t.name + '_count'] = num_clauses;还有「某类条款存不存在」的标志
SLA 级嵌入用户问的是整份合同而不是单条条款给 SLA 也建向量索引,不必逐条款搜
  • 两条纪律,写得很好:「别一上来就加这些优化。先从基线图开始,量它的表现。 如果查询够快、token 用量可接受、答案准确,更简单的模型往往就够了。」(第 788 段) ② 「最终答案永远要取回完整的条款正文。摘要和缓存值是用来过滤和排序的,不是用来替代原始数据的。」 (第 792 段)—— 这条和第 3 章 3.9「摘要负责被搜到、原件负责被读懂」是同一条纪律。
  • See Also 里点了两本/两篇外部资料: 《Essential GraphRAG》(Tomaž Bratanič、Oskar Hane,Manning) —— 我们书架上已经有这本(docs/essential-graphrag/),写拆解时可以互指; 以及微软的 graph RAG 研究论文

13. 第 10 章 Evaluating RAG Systems(text/35-ch10)—— 6 个配方,最长的一章

章导言(第 4–50 段)

  • 开篇第一句(第 4 段):「你无法改进你无法度量的东西。」 「没有评测,优化就成了猜。」
  • RAG 的评测为什么和传统机器学习不同(第 6 段),这一段是全章的地基: 传统机器学习是「从带标签的数据里学到模式,再测它能不能推广到没见过的样本」; RAG 系统不学习——它只检索和生成。基础模型已经能跨任务泛化了。 「问题不在于模型有没有学到模式,而在于它有没有检索到对的信息、有没有生成有用的答案。」
  • 由此推出测试题该怎么出(第 8 段): 测试问题要覆盖真实的用户意图,又不能逐字出现在系统的数据里,同时还得能从现有知识里答出来**。 「RAG 里的良好泛化,意思是能处理同一个底层问题的不同问法。意图比确切措辞更重要。 所以你是去改写真实查询,而不是去切分已有的带标签样本。」**
  • 图 10-1 的反例(第 10 段):拿哲学问题去问一个只懂足球规则的系统,得到的评测结果毫无意义。
  • 跑例架构(第 22–44 段):时装电商聊天机器人,两个完全不同的数据源 ——支撑网店的 SQL 库(用户、商品这类结构化应用数据)和装着时尚博客文本片段的向量库。 于是系统有四个可以各自独立出错的部件: 编排 agent(决定查哪个源)· text-to-SQL 转换步 · 语义检索器 · 生成模块。 「每个部件都可能独立失败,所以每一部分都需要评测指标,才找得到瓶颈。」
  • 好消息(第 46 段):检索这一步在概念上和传统搜索引擎、分类系统相似, 所以精确率、召回率这些成熟指标可以直接复用。

10.1 选指标(第 52–138 段)

先看你手上有什么标注数据,这决定了你能可靠地量哪一部分(第 65–72 段):

你手上有该用什么
参考问题 + 正确答案端到端评:语义相似度、基于 LLM 的正确性打分
标注过的检索数据(问题 + 应该被检索到的块)评检索:上下文精确率、召回率
两样都没有评生成的内在质量:忠实度 faithfulness、答案相关性 answer relevancy、连贯性
  • 一条通用纪律(第 74 段):不管哪种情况,都要选一小组、平衡的指标——通常两三个, 覆盖流水线的不同部分,至少一个检索指标 + 一个生成指标。 「这能防止你优化了一个部件、却在悄悄地让另一个变差。」
  • 三类指标(第 78–91 段):生成指标(忠实度、正确性、连贯性、流畅度、相关性, 以及涵盖有害内容或受保护材料的安全指标)· 检索指标(召回、精确、F1)· 通用指标(延迟、token 用量)。
  • 本章聚焦的三个核心指标(第 100–112 段),都是「没有参考答案也能算」的:
    • 上下文精确率 context precision:检索回来的块里有多大比例是真的相关 —— 管检索步,查搜索结果里的噪声(10.4);
    • 忠实度 faithfulness:生成的答案是不是严格落在给定的上下文里 —— 管生成步,抓模型编造信息(10.5);
    • 答案相关性 answer relevancy:生成的答案有没有直接回应用户的问题 —— 管生成步,抓「答案属实但对用户没用」这种跑偏(10.6)。
  • 为什么是这三个(第 122 段):「每个指标针对一种彼此独立的失效方式。 系统可以检索到精确的块却照样胡编;可以忠实度很高但答案相关性很低; 也可以答对了问题、但用的是无关的上下文。」
  • LLM-as-judge 为什么在这里成立(第 124 段):「法官不需要知道正确答案—— 它只判断主张有没有被上下文支持、答案有没有回应问题。」
  • 它的盲区(第 128 段):「LLM 当法官抓不住用户在乎的一切。 它在语气、品牌声音这类主观质量上很吃力,那些需要人来评。 它也会漏掉让用户恼火的问题:答案太啰嗦、措辞让人困惑、该反问澄清却没问。 用合成测试数据时这些局限还会更严重——合成数据倾向于产出比真实用户更容易的问题。」

10.2 人来评(第 140–194 段)

  • 三种做法(第 150–154 段): ① 成对比较——新旧两版回答同一组问题,让用户排名;这招在生产里也能用, 时不时给用户看两个候选答案问他更喜欢哪个; ② 单个问答对时的点赞/点踩; ③ 点踩后弹一个短表单:一句自由文本 + 几个可选的快速标签 + 提交。
  • 被动监控信号(第 162–174 段):时间戳(什么时候用得最多)· 用户标识(哪些群体在用)· 响应时间(找瓶颈)· 提示日志(追一条回答是怎么生成的)· 检索到的文档(评检索准确率)· LLM 的回答(估答案相关性、判断模型有没有正确理解给的上下文)。
  • 人评的价值(第 182 段):「点踩往往指向知识库里内容缺失或过时,而不是技术故障。 文字反馈尤其宝贵,因为它揭示的是指标探测不到的东西:缺口、措辞不清、期待没被满足。」
  • 一套可直接照做的节奏(第 186–188 段): 自动指标每次代码改动或部署都跑;人评每月一次、或每次重大系统更新之后; 每次评审抽 20–30 条回答,拉两三个团队成员或 beta 用户一起,以减少个人偏差; 同时记录量化打分(点赞点踩、1–5 分)和质性反馈(哪里错了、怎样会更好)。 「人评对持续监控来说太慢也太贵,而且不一致——同一个答案不同人打分往往不同。」

10.3 造合成测试数据(第 196–343 段)

  • 做法:从向量库里随机取文本块 → 让 LLM 生成「能用这些块回答的问题」和答案。 跑例用 Hugging Face 的 RAG mini Wikipedia 数据集(3200 条文本片段)。
  • 提示词的三条要求值得照抄(第 244–267 段):问题要 ① 直接、聚焦,针对上下文里某个具体的事实; ② 写成用户会输进搜索框的那种自然查询; ③ 独立于上下文——不许出现「根据这段文字」「在上文中」这类说法。
  • 它保证了什么(第 317 段):「LLM 生成的问题一定能从知识库里答出来,因为它们直接派生自存着的块。 这给系统改动提供了一个可靠的基线:如果你的 RAG 系统答不出合成问题,那就是坏了。」
  • 它保证不了什么(第 321–324 段),这一段是全章最诚实的一处: 「合成问题系统性地比真实查询更容易,因为它们派生自干净、聚焦的块。 真实用户提问含糊、会把多个意图混在一起、会引用对话上下文、会问边缘情况。 合成问题抓不住查询难度的分布——它们测的是理想条件,不是那条又长又含糊的尾巴。」 另外:别用合成数据评清晰度、语气、有用性这些体验维度;别用它评多轮对话 ——生成的问题都是单轮的。
  • 可直接带走的量(第 326–337 段):至少生成 50–100 对; 覆盖不同文档类型、不同问题复杂度(事实查找、比较、多跳推理)、知识边界附近的边缘情况; 人工抽查 10–20 对确认质量;知识库大改之后要重新生成测试数据。 「本配方代码里那五个例子够做原型,但不足以支撑可靠的评测。」

10.4 上下文精确率 Context Precision@k(第 345–587 段)

  • 四步(第 354–363 段):从测试集取一个问题 → 让 RAG 系统回答,记下答案和用到的那些块让 LLM 逐块判断「这一块对生成这个答案到底有没有帮上忙」 → 相关块数 ÷ 总检索块数。「四块进了提示,只有两块相关,精确率就是 0.5。」
  • 三条判定路线(第 375–381 段): ① 拿检索到的上下文比对参考上下文——需要一份黄金数据集,标明每个问题期待检索到什么; ② 拿检索到的上下文比对生成的答案——不需要黄金参考集,这是本配方用的; ③ 不用 LLM 的传统办法——字符串相似度、关键词重叠。
  • 教科书式的小例子(第 385–397 段):问「蒙娜丽莎是谁画的」, 上下文是「蒙娜丽莎是 16 世纪的油画,归于意大利博学者列奥纳多·达·芬奇」, 答案是「蒙娜丽莎由达·芬奇所画」——显然有帮助;如果某一块讲的是另一幅画或另一个画家, LLM 多半不会用它,那一块判 0。
  • 代码跑例(第 412–424 段)非常适合当走查:问「法国的首都是哪」,三块上下文 ——① 讲法国、提到首都巴黎;② 讲巴黎是法国首都;③ 讲亚马逊雨林(完全无关)LLM 给前两块判 1,给第三块判 0。
  • 表 10-1 的四格解读表,是这一章最实用的一张表(第 524–543 段):
上下文精确率 × 答案质量说明什么
高 + 高检索器工作正常
高 + 低是生成端的问题(胡编、推理差)
低 + 高第一块就够了,后面的块只是噪声——考虑把 k 调小
低 + 低检索失败。查嵌入模型、切块策略、或者知识库内容本身
  • 用它来干什么(第 520 段):调检索参数时(top-k、相似度阈值、重排策略); 以及为成本和延迟做优化时——「把 k 从 10 降到 3 能省 token,但只有当那三块含有关键信息时才划算。 调 k 的时候要盯着上下文精确率。」
  • 其他检索指标一览(第 545–575 段):Precision@k · Recall@k · F1@k · MRR(第一个相关结果出现得多早)· AP(兼顾相关性与排名位置)· DCG@k(相关结果出现得越早权重越高)· NDCG@k(归一化,便于跨查询比较)。 怎么挑:担心混进无关内容用精确率;担心漏信息用召回率; F1 平衡两者但不告诉你哪个问题占主导; 当结果顺序会影响生成质量时(模型主要用前几块),MRR 和 NDCG 才要紧。
  • Tip(第 579 段):ROUGE 和 BLEU(摘要和翻译里常用的)不适合评 RAG 的检索器 ——它们量的是 n-gram 重叠,抓不住语义相关性。

10.5 忠实度 Faithfulness(第 589–843 段)

  • 三步(第 598–604 段):用 LLM 把答案拆成一条条独立的主张(claim)→ 逐条查它有没有被检索到的上下文支持 → 被支持的主张数 ÷ 总主张数。
  • 拆解的示例极好(第 612–634 段): 问「巧克力饼干的主要原料有哪些」,答「通常包括面粉、糖、黄油、鸡蛋、巧克力豆。 有些配方还会用香草精或小苏打。」→ 拆成七条独立陈述 (「巧克力饼干通常包含面粉。」……「有些配方会用小苏打。」)。
  • 拆解提示词的要求(第 659–668 段):「把答案里的每个句子拆成一条或多条能被完全理解的陈述。 确保任何陈述里都不出现代词或含糊的指代。」注意:这和第 4 章 4.8 的「命题 proposition」是同一个手法,只是用在评测上。
  • 亚马逊雨林的跑例(第 693–707 段)拆出九条;上下文里没提「氧气产量被高估」这类内容, 所以那几条会判 0。
  • 阈值,是全书唯一一处给了明确数值判据的地方(第 813–815 段): 「生产系统的忠实度分数要争取在 80% 以上。低于 70% 就意味着可能有胡编的问题。 85% 以上说明扎得很牢;70%–80% 之间要调查,看失败案例是系统性的(比如在特定主题上胡编) 还是随机的;持续低于 70%,就考虑调整生成提示以强调「只用给定内容」、降低温度、 或者换一个指令遵循更好的模型。」
  • 三个指标各自独立地动(第 823 段):「改进检索并不保证忠实度变好,如果模型照样胡编; 而完美的忠实度也挡不住答案相关性偏低,如果模型盯错了上下文里的方面。」
  • 权衡(第 825 段):忠实度 vs. 信息量。「忠实度高的模型是保守的,只说上下文里明确写着的东西。 对多数 RAG 应用,优先保忠实度——用户期待的是可靠的信息检索。」
  • 一条方法建议(第 827–835 段):想弄清评测框架到底怎么实现忠实度, 最可靠的办法是读它的源码——在 VS Code 里按住 Ctrl 点进从 Ragas 导入的 faithfulness 函数, 一路翻下去会找到一个叫 StatementGeneratorPrompt 的类。

10.6 答案相关性 Answer Relevancy(第 845–1057 段)

  • 五步(第 854–865 段),这是一个「反向」的巧思: ① 让 RAG 系统回答用户的问题 → ② 让 LLM 从这个答案倒推出若干个「能引出这段内容的问题」 → ③ 给原问题和每个倒推出来的问题各算嵌入 → ④ 算每个倒推问题与原问题的余弦相似度 → ⑤ 取平均。 公式:answer relevancy = (1/N) Σ cos(E_gi, E_o),N 默认是 3。
  • 机制一句话(第 1045 段):「如果 LLM 从一个答案里倒推出问题, 那么这些问题应该像原问题——前提是这个答案确实回应了被问的东西。 语义相似度高说明对齐;相似度低就暴露了跑偏。」
  • 「不置可否 noncommittal」这一层(第 887–922 段): 一个问题被标为 noncommittal,意思是答案没有为它提供相关、有价值的信息noncommittal = 0 是好的。 表 10-2 的三个例子: 「法国的首都是哪」→「法国的首都是巴黎」→ 0(直接给了具体信息); 「你今天感觉怎么样」→「作为大语言模型我没有情绪」→ 1(承认了问题但没给相关信息); 「特斯拉股票现在多少钱」→「我无法提供实时股价」→ 1
  • 打分逻辑(第 1022–1034 段):只要有任何一个生成的问题被判为 noncommittal, 总分直接置 0——「这一刀切的做法把任何不置可否的回答都当成彻底失败」。 书也给了软一点的版本:score = cosine_sim_mean * (1 - noncommittal_ratio)
  • 它测不到什么(第 1049 段):「这个指标能发现答非所问,但发现不了答得不全。 一个多部分的问题只答了其中一部分,相关性分数照样可能很高。」
  • 两个指标的分工,一句话记住(第 1051 段): 忠实度问的是「答案有没有守住检索到的事实」;答案相关性问的是「答案有没有回应问题」。 「两个都必要:忠实度保可靠,答案相关性保有用。」

14. 第 11 章 RAG Web Apps(text/36-ch11)—— 5 个配方

章导言(第 4–18 段)

  • 开篇的现实判断(第 4 段):「大多数生成式 AI 项目都始于实验,而且大多数从未上生产。」
  • Streamlit 的机制与它的代价,一句话说透(第 8 段): 「Streamlit 在每一次用户交互时都把你的整个脚本重跑一遍。 这简化了开发,但带来的开销让它撑不过几十个并发用户。 对于「验证一个生成式 AI 应用有没有价值」这件事,这个取舍对你有利。」
  • 上生产的替代(第 10 段):Django(功能齐全,带认证和复杂工作流)· Flask(轻量灵活的地基)· FastAPI(为 API 端点优化)。
  • Tip(第 16 段)——和第 1 章第 21 段首尾呼应,是全书的一条暗线: 「RAG 早期,很多应用默认做成聊天机器人,哪怕更简单的界面本来更合适。 仪表盘和搜索界面往往更快也更直观。选聊天界面之前,先想清楚你的场景是不是真的从对话里获益。」

11.1 第一个 Streamlit 应用(第 20–69 段)

  • 五行代码一个应用;streamlit run streamlit_app.py,默认端口 8501
  • 量化的边界(第 57–63 段):「超过 50 个并发用户,这个模型就成了瓶颈。」 别用它做:面向几千用户的对外应用(FastAPI + React 扩展性更好)、 需要定制品牌外观的(Django UI 控制更多)、需要细粒度认证的(FastAPI 更自然)。 折中办法:把 Streamlit 当轻量前端,把认证、模型推理、数据库操作卸载给专门的 API 微服务 ——「这种混合做法能撑到 100–500 用户,再往上就得整个重写。」
  • 一句很实在的话(第 61 段):「你能在几小时内把一个能跑的 RAG 应用做出来原型, 但之后重构到生产基础设施要花几周。对于多半不会上生产的实验,Streamlit 是更好的选择, 因为它把前期投入降到最低。」

11.2 聊天机器人(第 71–349 段)

  • 跑例是天气 RAG:① 用 LLM 从用户问题里抽出城市和国家(要求返回 JSON)→ ② 用 GeoPy 的 Nominatim 把地名转成经纬度 → ③ 调 Open-Meteo 拿当前天气 → ④ 把天气 JSON 连同用户问题塞进提示交给 LLM 总结。 注意:这里的「检索器」就是两次 API 调用,不是向量库(第 161–166 段) —— 这是全书对「RAG 里的检索不一定是向量搜索」最直白的一次演示。
  • 会话状态:st.session_state.messages{"role", "content"} 的列表; if "messages" not in st.session_state 这个检查是必需的,否则 KeyError(第 333 段)。
  • 最值得记的一条坑(第 337 段):「主要风险是状态在用户之间泄漏。 Streamlit 为每个浏览器标签页创建一个会话,但只用单标签页测试会漏掉多用户的 bug。 如果用户 A 上传了一份文档、用户 B 刷新页面,B 会不会看到 A 的文档? 用两个浏览器窗口测,才能早点抓到这个问题。」
  • 工程纪律(第 339 段):Streamlit 脚本控制在 100 行以内, API 调用和提示组装挪到单独模块,重计算操作(嵌入、大模型调用)部署成 Lambda 或微服务。
  • 调试(第 341 段):print 语句在重跑时会乱序出现,所以要用 VS Code 调试器 ——配置成 module = streamlit,第一个参数 run,第二个参数是你的应用文件名。

11.3 PDF 分析(第 351–516 段)

  • 流程:PDF → 每页转成 PNG → 逐页交给多模态模型抽文字("You are an OCR assistant. Extract all text from the provided images. Do not summarize or skip any content.") → 把各页文本拼起来 → 在全文上跑一次实体抽取。
  • 为什么多模态比传统 OCR 强(第 497 段):「多模态模型通过视觉 transformer 处理图像, 同时识别文字、表格和版面。它们比传统 OCR 更能处理扫描件和表单, 因为它们理解空间关系——比如『这个数字在合计那一列里』 或者『这个签名出现在批准行的下面』。」
  • 统一成图片的代价(第 499 段):「把 PDF 转成图片创造了一条统一的处理流水线。 扫描件和数字 PDF 都变成图片,一条代码路径就够了。这简化了开发,但增加成本 ——你处理的是像素,不是文本层。」
  • 成本对照(第 501–504 段,全书最具体的一处成本数字): 纯文字 PDF 走多模态比 PyMuPDF 抽取贵 10 到 100 倍; 一份 100 页的文档,文本层抽取花 0.05 美元,多模态 OCR 要 0.50–2.00 美元。 「由 Word、LaTeX、浏览器生成的、只有段落和标题的数字原生文档,从多模态处理里得不到好处。」
  • 必配的护栏(第 506 段):「多模态模型偶尔会编造字段值,或者在页面旋转、质量差时跳过整段。 写进下游系统之前,永远要用 Pydantic 拿预期的 schema 去校验抽出来的数据。 生产系统需要字段存在性校验、取值范围检查,以及给低置信度抽取准备的人工复核队列。」

11.4 接 SQL 库:text-to-SQL(第 518–699 段)

  • Vanna AI 框架 + Chroma 当向量后端 + SQLite 当示例库(网上书店: 流派、客户、员工、配送、作者、图书、销售条目)。
  • 流程(第 528 段):先把所有数据库文档和数据模型嵌入进向量库; 用户提问时先做语义相似度搜索找出相关的那部分数据库文档, 用它让 LLM 生成合适的 SQL;执行 SQL;再把取回的数据连同提示交给生成模型。
  • 「训练」这个词的澄清(第 551 段):「这一步并不训练机器学习模型; 它只是把文档预处理之后存进向量库。」 —— 好的术语纠偏,值得照做。
  • 表 11-1 四类「训练」素材(第 555–583 段): DDL 语句(表、列、键的模式定义)· 文档字符串(人写的表/列含义、业务口径、约束、惯例)· SQL 语句示例(反映常见访问模式)· 问题—SQL 配对(「这是最有效的训练方式」)。
  • 三种可预测的失效方式(第 684 段),这一段很值钱:含糊问题上会猜错指标——问「给我看看趋势」,模型没有业务语境; ② 跨五张以上表的复杂连接经常超时或返回错误结果; ③ 文档不足会导致幻觉列名——「模型会编出 customer_lifetime_value 这种看着合理但不存在的字段, 而实际的列叫 clv」。
  • 什么时候别用(第 686 段):用户不懂 SQL 又问得含糊时(他们没法验证生成的查询); 需要数据库里没有的业务逻辑时;需要确定性行为以满足合规或审计留痕时(LLM 的输出会变)。
  • 书自己给出的更好的路(第 688–691 段),这一段把第 11 章接回了第 8 章: 「第 8 章的 agent 做法能更好地处理这些情况。把 10–20 条 SQL 查询模板定义成带参数的工具, 让模型选用哪个模板、填什么参数。这样你既有确定性的查询和清楚的审计留痕,又能用自然语言输入。」 「生产系统从 agent 做法开始。只有当用户明确要求、而且你的数据库文档扎实时, 才加上自由形式的 text-to-SQL。混合模型——常见查询走 agent 模板、临时探索走 text-to-SQL ——对分析团队很好用。」
  • 一处坦白(第 674 段):图 11-14 里 SQL 只返回两条结果,因为测试库里只有两条数据。

11.5 Docker + AWS 部署(第 701–807 段)

  • Dockerfile:FROM python:3.9-slimWORKDIR /app → 装 requirements → EXPOSE 8501ENTRYPOINT ["bash", "entrypoint.sh"]; docker build -t rag-streamlit-app:latest .,docker run -p 8501:8501 …
  • AWS 五步:装 AWS CLI → aws ecr create-repository → 推镜像 → 建 ECS 任务定义指向 ECR 镜像 → 部署到 ECS Fargate(无服务器容器运行时)。 Google Cloud 用 Cloud Run,Azure 用 Container Apps。
  • 成本警告(第 779 段):「Fargate 按 vCPU 小时和内存计费。 一个 1 vCPU、2 GB 内存的容器 7×24 跑着,每月大约 30 到 40 美元。 谨慎开自动扩缩容——流量突增时不加限制的扩容会产生意料之外的账单。 在 ECS 服务配置里设最大任务数上限。」
  • 上生产还缺的三层(第 785–797 段),这三段是全书结尾最有分量的内容:基础设施即代码(Terraform、AWS CDK)——「没有 IaC,排查生产问题就意味着 手动点遍控制台去回忆当初的配置。IaC 防止配置漂移:当你手动通过控制台改生产时, 你的开发环境会慢慢和它分岔。」 ② CI/CD 流水线——「手动部署会引入人为错误。你可能忘了跑测试、从错的分支构建、 或者跳过一个迁移脚本。CI/CD 让这些错误变得不可能发生。」 ③ 密钥管理——「别把 API 密钥硬编进 Docker 镜像。镜像存在镜像仓库里, 任何有访问权的人都看得到……如果镜像泄漏(仓库权限配错、或 CI/CD 凭据被攻破), 攻击者就拿到了你的 API 密钥。轮换凭据意味着重建并重新部署每一个镜像; 有了密钥管理,你在一个地方轮换就行。」
  • 收尾的分寸(第 799 段):「对原型来说,手动部署没问题。 当你确信这个应用会长期运行、当多个开发者需要部署、或者合规要求审计留痕时,再加这三层。」

15. 作者其人(text/38-fm-about-the-author.txt)

  • Dominik Polzer 是 Siemens Energy 的采购 AI 负责人(第 4 段), 「领导那些将改变采购部门明天怎么工作的机器学习与 agentic AI 项目」。
  • 近十年把机器学习方案推上生产的经验,做过制造、金融、采购(第 6 段)。
  • 靠 Medium 博客「触达了数百万读者」,讲的就是 RAG 架构、向量数据库、AI agent 框架(第 8 段)。 → 这解释了全书的文体:每一节都是一篇独立的博客文章的结构 (问题 → 方案 → 讨论「什么时候别用」→ 延伸阅读)。

第二部分:通读之后的判断(给写大纲的人)

A. 全书主线 —— 一条推理链

书自己在前言里说的组织法是「按建一套 RAG 系统的自然顺序:核心概念 → 生产部署」。 但通读之后,真正的推理链是这样一条,而书从没把它连起来说过一遍:

  1. 模型有三个结构性缺陷:拿不到你的私有数据 · 上下文窗口装不下 · 缺信息时会编。 一次治三个的办法只有一个:提问时先去外部资料里检索,再把检索到的东西喂给它(第 1 章)。
  2. 但该不该上 RAG 是有硬判据的:能写一条正则或一句 SQL 覆盖 95% 的情况,就别上。 RAG 拿简单性换灵活性,而规则式自动化的失败方式是可预测的(第 1 章 1.1)。
  3. 决定上了,第一个障碍是:资料不长成能喂的样子。 它们是 Word、PDF、Excel、 录音、会议录像、扫描件。所有非文字的东西只有一条出路:先变成文字(第 3 章)。 而干这件事的工具本身就是基础模型 —— 模型不只在回答时用,更多是在入库时用(第 2 章)。
  4. 变成文字还不够。检索是把块孤立地取出来的,嵌入模型只看得见块里面那些词。 所以每一块必须装恰好一条信息、并且脱离上下文也能被看懂(第 4 章)。 由此才有:补元数据、展开缩写、生成假设性问题、五种切法。
  5. 块准备好了,「意思相近」怎么变成能算的东西?把文本变成向量,用夹角量相似(第 5 章)。
  6. 向量多了要存、要搜、要快 → 向量库,以及 IVF / HNSW 这类索引(第 6 章)。
  7. 到这里 demo 能跑了,但答不准。而「改进检索这一步,是提高准确率与相关性最有效的办法」 —— 于是有八种进阶检索技术:改查询 / 缩范围 / 补上下文与挑结果(第 7 章)。
  8. 这八种技术每一种都要人事先决定「这次用哪个」。可问题千变万化,事先决定不了。 → 让模型在运行时自己决定调哪个工具、查哪个源(第 8 章)。 这一步兑现的是第 1 章开头就埋下的那句话(第 15、17 段): 「现代系统常常用多个检索器……模型自己决定该调哪个。」
  9. 但即便有了 agent,向量搜索还剩一类死穴:实体之间的关系。 孤立的块不知道「这条条款属于哪份合同、下一条是哪条、这家供应商花了多少钱」 → 知识图谱(第 9 章)。
  10. 到此为止,所有选择都只是「作者说这样更好」。怎么知道一次改动是让它变好了还是变坏了? → 三个不需要参考答案的指标(第 10 章)。
  11. 最后一步是让真实用户碰得到它 —— 而**「大多数生成式 AI 项目从未上生产」**, 所以原型阶段该优化的是前期投入而不是可扩展性(第 11 章)。

落点在哪:书的名义落点是第 11 章(部署),但思想上的落点是第 8 章。 证据有三条:① 第 1 章的架构图(图 1-2 的哈利波特多检索器)就是第 8 章的预告; ② 第 7 章的查询路由(7.4)已经是一个半成品 agent; ③ 最后一章的最后一节(11.4)在讲完 text-to-SQL 之后,自己说 「第 8 章的 agent 做法能更好地处理这些情况……生产系统从 agent 做法开始」 —— 全书的最后一个技术建议是指回第 8 章的。

三条贯穿全书、但书从没命名过的暗线

这三条是通读才看得出来的,也是我们的拆解最该做出增量的地方。

暗线一:「什么时候别用它」才是这本书的正文。 全书 60 多个配方,每一个的 Discussion 里都有一段 "Don't use … when"。 把这些反面判据收集起来,比正面做法更值钱,而且它们彼此不重复。 (几条最硬的:能写正则覆盖 95% 就别上 RAG · HTTP 这种缩写别展开 · 文档纯文字就别走多模态 · 类别边界模糊就别用分类器,改用元数据过滤 · 用户自然语言提问、没有技术标识符就别加 BM25 · 简单线性流程别用 LangGraph · 单一用途 agent 别上 MCP · 用户不懂 SQL 就别给他 text-to-SQL。)

暗线二:「先简后繁,而且方向不可逆」—— 书说了九遍,一次都没命名。

在哪说的原话的意思
1.6(第 596 段)从简单实现迁到框架容易,从框架退回简单实现「难得多」
6.1(第 198 段)从向量库迁到数据库不难:要重建索引,但不用重新生成嵌入,而嵌入才是贵的那部分
6.3(第 329 段)原型可以用 Chroma 内置的嵌入,但打算迁走就自己生成嵌入,只拿它存和搜
6.6(第 832 段)不到 10 万行就别建索引
5.6(第 676 段)先从纯语义搜索开始,只有在日志里看到跨领域混淆时才加分类
5.7(第 833 段)先从语义搜索开始,只有看到精确匹配失败时才加混合检索
7.4(第 528 段)先用 LLM 路由验证逻辑,延迟成问题时才换嵌入分类器
8.3(第 11 段)早期避开重型框架。每加一层抽象都遮蔽行为,每加一个依赖都扩大攻击面。框架锁定会复利
9.5(第 788 段)别一上来就加优化。先从基线图开始,量它的表现

暗线三:每一步都标了价,但全书没有一处汇总。

对照倍数或数值出处
多模态 API vs 本地 Tesseract(每页)100 倍3.6 第 521 段
多模态 OCR vs 文本层抽取(100 页文档)0.50–2.00 美元 vs 0.05 美元;书另说是 10–100 倍11.3 第 501–503 段
代理式切分 vs 语义切分10–20 倍;500 块要 30–60 分钟、15–40 美元4.8 第 988 段
LLM 生成元数据1000 份文档:几分钟 + 1–5 美元4.1 第 154 段
语义切分每段文字要嵌入两遍4.7 第 820 段
多查询检索检索时间与算力约三倍7.3 第 405 段
LLM 路由 vs 嵌入分类器500–2000 毫秒 vs 10–50 毫秒7.4 第 518–526 段
句子窗口(1 块 → 3 块)上下文长度三倍7.6 第 722 段
索引(20 万条向量)全扫 85.6 ms → HNSW 13.9 ms6.6 第 760、828 段
asyncio(三页并发)90 秒 → 49 秒8.5 第 133 段
输入 token 价(四档)0.05–0.20 / 0.25–0.60 / 1–5 / 2–30 美元每百万2.2 第 160–176 段
Fargate 常驻(1 vCPU / 2 GB)每月 30–40 美元11.5 第 779 段

B. 伏笔清单(不通读看不出来的)

埋在哪揭在哪说明
第 1 章 §导言 第 15、17 段:多个检索器,模型自己决定调哪个第 8 章整章全书最大的一处伏笔,而且书没有回过头点破
第 1 章 第 21 段:聊天界面往往不是最优解第 11 章 第 16 段,一模一样再说一遍全书唯一一处首尾呼应
第 2 章 §2.2 Tip(第 193 段):复杂工作流拆成小步,每步选合适的模型第 7 章 7.8 查询拆解 · 第 8 章 8.2 工作流模式
第 3 章 §3.4 Discussion:PostgreSQL 身兼二职,pgvector第 6 章 6.4–6.7 四个配方
第 3 章 §3.8 第 798 段:CLIP 这条岔路(图文进同一空间)第 5 章 5.5 —— 而 5.5 又反过来说「多数 RAG 别走 CLIP,走图→描述→文本嵌入」埋了又拆的伏笔,拆解里要把这一来一回讲完整
第 3 章 §3.3 option 3:text-to-SQL第 11 章 11.4 完整实现 → 11.4 又说「其实第 8 章的 agent 做法更好」三章接力
第 4 章 §4.3 Note(第 436–438 段):索引时对齐 vs 查询时对齐第 7 章 7.2 HyDE书明写的伏笔,是全书写得最清楚的一处前后呼应
第 4 章 §4.8:命题 proposition = 不带代词的独立陈述第 10 章 10.5 忠实度:把答案拆成不带代词的独立主张同一个手法用在两处,书自己没点破。我们要点破
第 5 章 §5.6 分类路由第 7 章 7.4 把它列为 LLM 路由的更快替代
第 3 章 §3.9:摘要负责被搜到,原件负责被读懂第 9 章 9.5 第 792 段:摘要只用于过滤和排序,最终答案永远取回完整正文同一条纪律,隔了六章,书没点破
第 6 章 §6.1 第 198 段:嵌入才是贵的那部分支撑全书「先简后繁」
第 2 章 §2.2 第 156 段:输出 token 更贵,因为要一个一个生成后面所有成本讨论的地基,但再没提过

C. 建议的章节切分(按新词密度切,不按字数)

原书 11 章切成 17 章 + 总纲。理由:原书第 3、6、7、8、10 章各自新词密度太高, 一章塞不下;而第 5、6、7 三章里「混合检索/元数据过滤」这同一件事被拆散在三处,该收拢。

我们的章讲什么对应原书这一章的主走查
index.md总纲(六节),含许诺兑现表
01为什么要检索 —— 模型的三个结构性缺陷、RAG 是什么、以及「什么时候不该用它」ch1 导言 + 1.1 + 1.6表 1-1 那八个场景,从 1 分走到 5 分
02一套 RAG 长什么样 —— 两条线、三个部件;基础模型在生成步与入库步各出现一次ch1 §1.5 + ch2 导言 + ch5 导言哈利波特问答从入库到出答案走一遍
03挑模型与写提示 —— 四个档位、输出为什么比输入贵、四条接入路、排行榜为什么不能当判据ch2 全章同一个采购问题在四档模型上的延迟/成本取舍
04把资料变成文字(上):文档、表格、数据库 —— 结构化加载换来什么、表格的三条路ch3 §3.1–3.4Census 那张 15 列 48000 行的表,三条路各走一遍
05把资料变成文字(下):图、音、视频 —— 100 倍成本与混合路由、模态转换、摘要+原件ch3 §3.5–3.11一份混排 PDF:文字/图/表三种元素各走一条路
06让每一块能独立被看懂 —— 元数据过滤、展开缩写、假设性问题ch4 §4.1–4.3「抗氧化剂」在医学与环境工程里的那个假阳性
07五种切法 —— 从数字符到让模型改写;成本阶梯ch4 §4.4–4.8Sarah 那两句话:五种切法各切一遍,看结果差在哪
08意思怎么变成数 —— 向量、独热的反面、余弦为什么是默认、选型七步ch5 §5.1–5.4king − man + woman(书给的三维玩具例子)
09CLIP 与跨模态检索,以及为什么多数时候别用它ch5 §5.5 + ch3 §3.8 回扣猫狗分类 + 「大象也会被判成猫或狗」
10存到哪儿、搜得多快 —— 四步决策路径、三级阶梯、IVF 与 HNSWch6 §6.1–6.620 万条职位描述:全扫 85.6 ms → HNSW 13.9 ms
11当「意思相近」不够用 —— 元数据过滤 · BM25 与 RRF · SQL 加权版 · 分类路由ch5 §5.6–5.7 + ch6 §6.5、6.7 + ch7 §7.1「谁是史上最强球员」——不知道是哪项运动就答不了
12改查询 —— HyDE、多查询、查询拆解;三者的区别必须一次讲清ch7 §7.2、7.3、7.8「和化石燃料相比可再生能源有什么好处」在三种做法下各变成什么
13补上下文与挑结果 —— 父子块、句子窗口、重排的两阶段道理ch7 §7.5–7.7图 7-13 那个「叶节点 1、2、5 命中」的具体例子
14让模型自己决定 —— 工具、函数调用循环、ReAct、五种工作流模式、asyncioch8 导言 + 8.1、8.2、8.4、8.5 + ch7 §7.4(5.0 + 3.0) * 2.0:第一次调用只解决了括号
15框架、MCP 与锁定的代价 —— 三个抽象层、议价 agent、MCP 三部件、LangGraph 的图ch8 §8.3、8.6、8.7、8.8议价:15 台笔记本、950 vs 1299 欧元、12% 折扣
16当关系本身就是答案 —— 知识图谱ch9 全章「哪些高支出供应商的 SLA 缺解约条款」
17怎么知道它变好了 —— 三个不需要参考答案的指标、合成数据的偏易、人评的节奏ch10 全章「法国的首都是哪」+ 那块讲亚马逊雨林的无关上下文
18上线,以及还缺的三层 —— Streamlit 的天花板、text-to-SQL 的三种失效、IaC/CI-CD/密钥ch11 全章天气 RAG:一次提问走完抽地名→查坐标→查天气→生成

若嫌多,可以合并的两处(推荐顺序):09(CLIP)并进 0508 —— 它只有一个配方的量; ② 1213 合成一章「第 7 章的八件武器」—— 但那一章会到 25k 字符以上,新词也多,不推荐。

不建议合并的三处: 04/05(原书第 3 章 11 个配方,49k 字符,一章塞不下)、 14/15(原书第 8 章连配方一起 66k 字符,是全书最大的一章)、 10/11(索引原理和混合检索是两件事,新词各自成堆)。

另外强烈建议加一章我们自己的(排在最后或作为总纲第 3 节的延伸): 「书说了九遍却从没命名的那条原则:先简后繁,而且方向不可逆」 —— 见上面「暗线二」的表。 外加一张全书成本对照总表(「暗线三」)。这两样书里都没有,是我们的增量。

D. 要补的外部缺口(先翻书架,再上网)

D-1 书架上就有的(不许上网,直接引)

缺口书架在哪
MCP 的规范本体与完整讲法(第 8 章只讲了三部件)ai-protocol-reference/docs/mcp-spec/ · ai-protocol-reference/aiRef/repos/mcp-spec/ · 本书架 docs/ai-agents-with-mcp/(12 章,已拆解)
Ragas 三个指标的真实实现(书自己说「读源码是最可靠的办法」,还点名 StatementGeneratorPrompt 类)ai-frontier-reference/aiRef/repos/ragas/最该做的一处核对
OpenAI Agents SDK 的 @function_toolas_tool(书正文写 @tool、代码写 @function_tool,自相矛盾)ai-frontier-reference/aiRef/repos/openai-agents-python/
LangGraph 的 add_messages / StateGraph / 条件边ai-frontier-reference/aiRef/repos/langgraph/
LangChain 的文本切分器与 langchain_experimental 的 SemanticChunker 现状ai-frontier-reference/aiRef/repos/langchain/
all-MiniLM-L6-v2 与 Sentence Transformers 的实际做法(用来纠正书里的 word2vec 图景)ai-frontier-reference/aiRef/repos/sentence-transformers/
微软的 GraphRAGai-frontier-reference/aiRef/repos/graphrag/
《Essential GraphRAG》(书自己在 9.5 See Also 里点名)本书架 docs/essential-graphrag/(12 章,已拆解)
Docling(书在 3.1 Note 里点名)ai-agent-reference/aiRef/repos/docling/
DeepEval / Phoenix(书在 10.6、10.4 See Also 里点名)ai-agent-reference/aiRef/repos/deepeval/ · ai-frontier-reference/aiRef/repos/phoenix/
DSPy(用来核 Demonstrate-Search-Predict 的血缘)ai-frontier-reference/aiRef/repos/dspy/
同题材的对照书(拆解时可互指,也可用来判断我们的增量在哪)docs/hands-on-rag-for-production-design/(16 章,Vectara 创始人写的生产级 RAG)· docs/unlocking-data-genai-rag/(10 章)· docs/learning-langchain/ · docs/rag-ready-patterns-for-data-platforms/

⚠️ 给写大纲的人的提醒:docs/hands-on-rag-for-production-design/ 覆盖的地盘和这本书高度重合 (解析切块、嵌入、向量检索、混合与重排、评测、agent、多模态、知识图谱)。 我们这一本的差异化必须写清楚:那一本是「怎么设计一套生产系统」, 这一本是「每一个具体做法的参数、代价、和什么时候别用它」。

D-2 必须上网核的(一次只抓一个,同一地址失败两次立刻换源)

#要核什么为什么优先级
1ReAct 论文的年份 —— 书说「2023 年论文 "ReAct: Synergizing Reasoning and Acting in Language Models", Shunyu Yao 等」。实际 arXiv 首发在 2022 年 10 月,ICLR 2023 才发表书给了确切年份,而这个年份多半不准
2HyDE 原论文 —— 书只写 "the original HyDE paper",没给题名作者我们的读者需要知道是谁、哪一年
3「命题 proposition」出自哪篇 —— 书只说「最早描述这个概念的论文之一」,提示词在 LangChain Hub 的 wfh/proposal-indexing书自己承认这是别人的东西却没给名字
4Anthropic 的五种工作流模式出处 —— 书说 "five common workflow patterns defined by Anthropic",See Also 写 "Anthropic's guide to building effective agents"整节建立在别人的框架上,必须给出处
5Greg Kamradt 的 "5 Levels of Text Splitting" —— 五种切法这个框架的来源;「代理式切分 agentic chunking」这个叫法就出自这里,不是公认术语红线:作者自造/他人自造的词必须点明
6"Attention Is All You Need" —— 前言只提了名字和 2017我们的读者什么都不知道中(先翻书架,docs/what-is-chatgpt-doing/ 等多本讲过)
7PyPDF2 的现状 —— 全书用 PyPDF2,而它早已停止维护、并入 pypdf(书自己在表 1-2 里两个都列了)读者照着装会踩坑
8MoviePy 的 API —— 3.11 的代码 from moviepy import VideoFileClip(2.x 写法)搭配 clip.subclip(...)(1.x 方法名,2.x 已改成 subclipped)。两者不能同时成立同上
9openai-python 的当前 API —— 书里 client.beta.chat.completions.parse(ch4、ch7)与 client.chat.completions.parse(ch10)并存;client.responses.parse(..., response_format=...)(ch2.7)参数名可疑;openai.Audio.transcribe(ch8.8)是 0.x 的旧写法同一本书里三套 API 混用
10ankane/pgvector 这个 Docker 镜像是否仍是推荐镜像(现在通常用 pgvector/pgvector)影响可跑性
11MTEB 与 MIRACL —— 只给了全称,没说是谁做的、怎么读读者要照着选模型
12TabLLM 论文(3.3 Tip,Stefan Hegselmann 等)的年份与出处书给了作者但没给年份
13「企业信息约 80% 是非结构化」这个数的出处(ch3 第 4 段)书完全没给。这是业界流传多年、来源可疑的说法。查不到就照实写「书里没交代,而且这个数长期没有可靠出处」
14Vanna AI 的现状(ch11.4 整节建立在它上面)小众框架,可能已经变了
15Swarm 已被 OpenAI Agents SDK 取代 —— 书自己说的,可用书架的 openai-agents-python 仓库交叉确认

D-3 书里讲错或自相矛盾、拆解里必须纠正的

这些不是「书没讲」,是「书讲错了」。写正文时要标成「书里这么说,但……」。

#在哪问题
110.4 第 363 段 vs 第 369 段与代码文字说「四块里两块相关,精确率 0.5」(普通精确率),而公式和代码算的是 Average Precision(带排名加权)。两者不是同一个东西。 代码注释 # e.g., score = 0.835 for [1,0,1] 印证了是 AP。这是全书最实质的一处概念错误,而且在最技术的一章。
26.7 第 938–945 段混合检索的 SQL 里三处 plainto_tsquery('PostgreSQL') 硬编码,而用户查询是「我在找柏林的数据科学家工作」。关键词那一半根本没在搜用户的问题;WHERE tsv @@ plainto_tsquery('PostgreSQL') 还会先把结果过滤成必须含 "PostgreSQL" 的行。这个例子跑出来的东西和它声称在演示的不是一回事。
35.1 第 165 段讲嵌入模型的训练用的是 word2vec(2013)的图景(每个词一个输入神经元、预测下一个词、隐藏层权重就是嵌入),而全书实际用的 text-embedding-3-small 是基于 transformer 的。书没有说明这个差别。
4表 2-1 vs §2.4 正文表里(标 "as of January 2026")写 Gemini 3 Flash / Gemini 3 Pro / Claude Opus 4.5,而 2.4 的正文只讲 Gemini 2.5 Pro/Flash。同一本书里两代型号并存。 代码里 gpt-5.2 / gpt-5-mini / gpt-4o-mini 也混用。
5表 2-2「流行的开源模型」停在 2023–2025(Llama 2、Mistral 7B、Qwen 7B/14B、Falcon 40B),ollama pull llama2 拉的是两年多前的模型;而同一节的示例代码又拉 qwen3:4b
610.5 第 807–808 段代码注释写 # 7# 6(来自前面的饼干例子),但紧邻的跑例(亚马逊雨林)第 707 段说抽出了九条陈述。数对不上。
71.7 第 611–612 段git clone …/RAG-with-Python-Cookbook.git 之后 cd rag-oreilly-book,目录名对不上
811.4 第 534 段pip install streamlit openai vanna chromadb sqlite3 —— sqlite3 是 Python 标准库,不能也不需要 pip 装,这条命令会失败。
911.3 第 468–475 段先定义了 uploaded_pdf = st.file_uploader(...) 却从没用它,后面改从 prompt.files 拿文件;而 st.chat_input 没有开 accept_file,prompt.files 拿不到东西。两套上传路径,都不完整。
108.6 26-fm-solution 第 67 段 vs 第 74 行正文说「加 @tool 装饰器」,代码写的是 @function_tool,而且 @function_tool 没有 import。
118.8 31-fm-solution 第 94 行openai.Audio.transcribe(...) 是 openai-python 0.x 的旧 API;而同一本书的 2.3 和 3.5 用的是 1.x 的 client.audio.transcriptions.create(...)
129.5 第 780 行SET c[t.name + '_count'] = num_clauses —— 标准 Cypher 是否支持这种动态属性名待核(通常要 apoc.create.setProperty)。
139.1 第 204–219 段条款分类完全靠关键词匹配(availability/uptime → Availability……),匹配不上就打成 Other。整章的检索质量建立在这条规则上,而书从没讨论它的失效率。 9.3 的 Discussion 只泛泛说了一句「分类体系不完整就会漏」。
14转码缺口(不是书的错,是我们这一版的)第 8 章所有配方的 Problem 段都丢了;配方 8.7 的标题整个丢了(它夹在 8.6 的 See Also 里,而 8.6 没有 See Also 文件)。引用时只能写「第 8 章 · MCP 那一节」。

E. 书不覆盖什么(通读确认,不是猜)

  1. 微调 fine-tuning —— 图 10-2 提了一句「通过微调或提示工程改进生成模型」, 全书没有任何一节讲微调;
  2. 长上下文能不能替代 RAG —— 完全没有讨论。第 1 章只说「上下文窗口有上限」, 而 2.4 又说 Gemini 支持一百万 token,两者之间的张力书没碰;
  3. 安全 —— 提示注入、越权检索、数据泄漏。只有 MCP 那一节一句「每加一个服务器扩大攻击面」 和 11.5 的密钥管理,没有系统讨论;
  4. 多轮对话与对话记忆 —— 第 8 章提到 short/long-term memory 但没有配方; 10.3 明说「合成数据抓不住多轮对话动态」;
  5. 缓存(语义缓存、提示缓存)—— 一个字没有;
  6. 增量更新与索引新鲜度 —— 文档改了怎么重新入库、怎么删除、怎么保证不搜到已删内容,全书没讲;
  7. 权限与多租户 —— 6.5 提了「只搜用户有权看的文档」一句,没有实现;
  8. 重叠 chunk overlap 该设多少 —— 示例里出现了 0、20、50、200 四个不同的值 (4.5 用 0、4.4 用 20、4.6 用 50、1.5 用 200),书既没解释这个差别,也没说该怎么定;
  9. 块该多大 —— 除了「不能超过嵌入模型的 token 上限」之外没有通用建议; 7.5 的 250 字符、7.6 的 250 token 都只是示例参数;
  10. top-k 取几 —— 10.4 说「调 k 时盯着上下文精确率」,但没给起点值;
  11. 成本总账 —— 分散在十几处,没有一处汇总(这是我们该补的);
  12. 中文与多语言 —— 只有 4.5 的一句 Note(中、日、泰缺词边界)和 MIRACL 一个名字;
  13. 对照实验 —— 全书没有一次 A/B 或基准对照。所有「这样更好」都是作者的从业经验。 唯二的实测数字是 6.6 的索引耗时(85.6 → 13.9 ms)和 8.5 的 asyncio 耗时(90 → 49 秒)。