跳到主要内容

解析与切块 — 摄入流的头两站

这一章讲三件事: 为什么「把 PDF 里的字拿出来」比想象中难得多; 为什么抽出来的文字还不能直接入库,要先切成小块; 以及切块这件事为什么没有「最优切法」。 读完你会拿到摄入流前半段的完整图景,和书里一个反直觉的评测结论。

1. 顶层全景:摄入流的四站

第 01 章那张图里,摄入流只有一行字。把它放大,是四站1:

你的文件(PDF / DOCX / 网页 / 数据库)

▼ ① 解析(parsing):把「为眼睛设计」的格式还原成文字、表格、图片
▼ ② 切块(chunking):把长文字切成小段
▼ ③ 嵌入(embedding):把每段转成一串数字 —— 第 04 章
▼ ④ 索引(indexing):把数字存进能快速找「最相似」的库 —— 第 05 章

图说:前两站管「文字对不对」,后两站管「找不找得到」。
这一章的主走查:一份假想的公司《差旅报销制度.pdf》(为演示编的),
走完前两站。

作者给这四站配了一句全书反复回响的话:解析阶段的错误会污染整个知识库, 这一站的准确性至高无上2。垃圾进、垃圾出——后面几章再精巧的检索与生成, 也救不回一份被解析坏的文档。

2. 核心原理(一):解析难在哪

PDF 不是「一段文字」,是「一页坐标」

直觉里,一个 PDF 就是一段排好版的文字。其实不是。 PDF 的祖先是 PostScript ——一种「告诉打印机把墨水点在哪」的语言。它存的是逐字符 + 每个字符的二维坐标, 完全没有「段落」「列」这样的逻辑结构3

所以「提取文字」实际是一道推理题:把坐标相近的字符拼成词、把词拼成行, 再判断行与行怎么接。双栏排版是最经典的翻车点:不做聚类(按坐标把邻近字符归成一堆的分析), 提取器会把左右两栏的文字按行交错串在一起,读起来像乱码4。 扫描件更糟——它是一张照片,要先靠 OCR(optical character recognition, 光学字符识别:从图像里认出文字)才能得到文字,而准确率取决于图像质量与字体5

主走查第 ① 步:报销制度 PDF 被拆开

我们的演示文件《差旅报销制度.pdf》有三样东西:正文、一张报销标准表、一张流程图。 用书里演示的 PyMuPDF 库逐层提取6:

page.get_text() → 得到「文字」,但表格的单元格和正文混成一锅
get_text("blocks") → 按「块」输出,但仍分不清哪块是表、哪块是正文
find_tables() → 单独抽出表:返回二维列表,行是子列表
正文 = 全部文字 − 表格区域的文字(按表的坐标框裁剪后逐行减去)
get_images() → 只给图片的引用号(xref),要再调 extract_image() 才拿到图本身

图说:解析 PDF 不是「读出来」,是「按坐标把三类东西分拣开」。

DOCX 和网页(HTML)这类格式好办得多——它们内部是 XML 家族的标签结构, 「这是一级标题」「这是加粗」都明写着7。但好办不等于可以闭眼丢标签, 这就是下一件事。

标签不能乱丢:报销制度里的「谁说的」

书里给了一个本节最重要的例子。一段聊天记录长这样:

<div class="AI">退款周期已改为 10 个工作日。</div>
<div class="user">那老合同怎么办?</div>

粗暴地丢掉全部标签,剩下的文字看不出哪句是 AI 说的、哪句是用户说的。 将来有人问「根据 AI 的说法……」,检索就答不了了。 书里的解法:把丢掉的标签转成元数据(metadata——附在文本段上的结构化标注, 比如 {"who": "AI"}),随文本一起存进库里8

元数据在查询期的用法像数据库查询里的 WHERE 条件:先按 who=AI 过滤, 再在过滤结果里做语义搜索。它与「按意思找」完全无关,是「按标注筛」9。 这个「筛 + 搜」的组合,到第 05 章(元数据过滤)和第 09 章(权限)会一再出现。

3. 核心原理(二):VLM 能不能包办解析

VLM(vision-language model,视觉-语言模型)是能同时看图和读文字的模型, 把一页 PDF 当图片丢给它,它直接「读」给你听——听起来解析问题就此终结10

书里明确踩了刹车:VLM 是生成式(自己生成内容而非只做判断的)模型,会幻觉;常常无法复现文档里的图像; 对大规模企业数据又贵又慢。它的正确位置是:留给难而高价值的文档, 或者当经典解析器全部失败时的兜底。作者给这条策略起了名字:「cheapest successful parser-first」——先用最便宜能成功的解析器(PDF 库 → HTML 解析器 → OCR), 全都不行才上 VLM11

4. 核心原理(三):为什么必须切块

解析完,我们手里是《差旅报销制度》的完整正文。为什么不能整个入库、整个发给模型? 书里给了四个理由,每个都带数字——这是本章数字最密的一节,逐个看。

理由一:模型的「工作台」有固定大小

上下文窗口是模型一次能看到的文字总量。写作本书时的顶级窗口: GPT(OpenAI 的模型系列)-5.1 是 40 万词元,Claude Sonnet 4.5 与 Gemini 3 是 100 万12。 听起来巨大,书里当场把它戳破:人说话约每分钟 120 词,英文一词约 1.3 词元, 40 万词元 ≈ 42 小时连续语音——而想把「所有相关财报电话会」的记录全放进去问 「市场对某主题的共识」,42 小时远远不够13

理由二:输入越长,等得越久,花得越多

书里引了 Nvidia 的实测:70B 参数的模型跑在 2 张 H100 显卡上, 首词延迟(TTFT,time to first token——从发问到大模型吐出第一个词的时间) 随输入长度暴涨14:

输入长度首词延迟
200 词元31 毫秒
1,000 词元82 毫秒
10,000 词元1,833 毫秒

输入长 50 倍,延迟涨了快 60 倍——原因是这种模型的计算量随上下文长度大约平方级 增长15。而上面这套 2×H100 的配置,按云厂商市价超过每月一万美元, 约为硅谷工程师半个月底薪16切块就是直接把输入长度砍下来, 延迟和钱一起省。

理由三:嵌入模型的工作台更小

下一站要做嵌入(把文字变成数字),而嵌入模型的窗口比生成用的 LLM 小得多: OpenAI 与 Google 的主流嵌入模型都只有 8,000 词元17。 块切得比这大,超出的部分会在转换时被静默截断——不报错,直接丢18

理由四:块越小,意思越「纯」

一个跨了三个主题的大块,转成数字后每个主题都只被弱弱地代表, 检索时哪个主题都匹配不好;小块聚焦单一主题,检索和生成都更准。 此外还有书中提到的一个著名现象:模型的推理能力随上下文变长而下降, 关键信息埋在长文中间时尤其容易丢——这个现象有专门的名字,叫「lost in the middle」(迷失在中间),Anthropic 发布百万词元窗口时的图表就显示了三家模型 都有这条下降曲线19

5. 核心原理(四):五种切法,与一个反直觉的结论

五种切法

书里的对照表(Table 2-4)列了五种策略,按「对文本结构的尊重程度」从粗到细20。 先说表里的一个行话:递归——一层切不开就换更细的一层、逐层往下拆的做法, 表里最后一行那个 RecursiveCharacterTextSplitter,名字里就是这个意思:

策略怎么切代价
定长(fixed-size)按字符/词/词元数硬切会切断句子;缓解办法是重叠(把上一块结尾重复到下一块开头)
内容感知(content-aware)按句界、段界切;句子边界检测有缩写、期号等边缘情况,要用专门工具(spaCy/Stanza/NLTK 的 sentencizer,分句器)实现更复杂
递归(recursive)按「段落→句子→子句」的分隔符(用来划开边界的符号,如换行)层级逐级切代表实现是 LangChain 的 RecursiveCharacterTextSplitter
文档结构(document-structure)按标题层级(# → ## → ###)切,保住逻辑结构依赖文档本身结构良好
语义(semantic)按语义相似度给句子聚类,块内语义连贯计算量最大

主走查第 ② 步:报销制度被切成块

《差旅报销制度.pdf》正文,约 6,000 词元(为演示编的)

▼ 递归切块:先按章节(「一、适用范围」「二、报销标准」…)切,
│ 超长的节再按段落切,目标每块约 500 词元、相邻块重叠 50

得到 14 块:块1=「一、适用范围」全文,块2=「二、报销标准」前半 + 块1 的尾巴……

图说:切完后每块都是一个「意思完整、模型吃得下」的小段。
块数与大小是演示值,真实参数要按你的嵌入模型窗口与文档结构调。

反直觉的结论:切法可能没那么重要

书里埋了一颗炸弹,值得单独成段。作者合作者的一篇 EMNLP(NLP——自然语言处理——领域的顶会)2024 论文 (《Is Semantic Chunking Worth the Computational Cost?》)在两个公开基准上测下来: 定长切块和语义切块的效果没有显著差异21

判断(我们的,不是书里的): 这个结论的正确读法是「在现有评测基准上没差异」。 作者自己也点了破:这些基准是 RAG 时代之前为短文本设计的, 长文档、强结构文档上结论完全可能翻转。工程上的稳妥做法是: 先用最便宜的递归切块上线,等第 11 章的评测体系告诉你检索确实不行,再升级切法。 如果错,会错在: 如果你的语料是长合同、长手册这类强结构文档, 「没差异」的结论可能本来就不覆盖你——书里没有这类语料的实验数据。

6. 边界与局限

  • 解析库本身会失败。 书里只给了一个演示(第 14 章会看到连专用工具 也会抽出空表);生产上的态度是「永不要假设解析 100% 准」,这句话在第 14 章 会变成铁律。
  • 切块的所有数字都是写作时点(2026 年)的。 窗口、价格、模型名单都会过时; 不过时的结构是:窗口、延迟、嵌入窗口、质量这四条理由各自独立存在。
  • VLM 解析发展极快。「cheapest successful parser-first」的成本排序, 会随着 VLM 降价而逐季移动。
  • 切块效果可以在检索段或生成段分别评测,评测用的基准(BEIR)与指标 (precision/recall 等)这里只点名,全部留给第 11 章讲透22

7. 可带走的

  1. PDF 存的是坐标不是文字——解析是按坐标把正文/表/图分拣开的推理题,双栏和扫描件是两个经典翻车点;
  2. 解析错误会污染整个知识库——这一站的准确性至高无上;
  3. 丢标签之前先想「这个标签将来能不能当筛选条件」——能就转成元数据;
  4. VLM 解析是兜底不是主力:cheapest successful parser-first;
  5. 切块的四个理由:模型窗口装不下、输入越长越慢越贵(约平方增长)、嵌入窗口只有 8,000 词元、小块语义更纯;
  6. 五种切法没有普适最优;定长与语义切块在现有基准上打平——先用便宜的,让评测说话;
  7. 重叠是定长切块的救命绳:块尾重复到下一块开头,保住边界处的上下文。

8. 原文地图

主题原书章原文位置
摄入流四步The Ingestion Flowtext/09-fm-the-ingestion-flow.txt:7(搜「parsing, chunking, embedding」)
解析错误污染知识库The Ingestion Flowtext/10-fm-the-query-flow.txt:67(搜「paramount」)
PDF 源自 PostScript、双栏Extracting Text from Various File Formatstext/11-fm-extracting-text-from-various-file-formats.txt:7(搜「PostScript」) · :10(搜「columns」)
解析库名单Extracting Text from Various File Formatstext/11-fm-extracting-text-from-various-file-formats.txt:13(搜「pypdf」)
标签→元数据、chat log 例Extracting Text from Various File Formatstext/11-fm-extracting-text-from-various-file-formats.txt:34(搜「According to AI」) · :37(搜「metadata」)
VLM 解析的谨慎Document Parsing with Vision–Language Modelstext/12-fm-document-parsing-with-vision-language-models.txt:7(搜「hallucination」) · :10(搜「cheapest successful parser-first」)
PyMuPDF 表格提取Code Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:123(搜「extract_unstructured_text」) · :83(搜「find_tables」)
上下文窗口与 42 小时Code Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:365(搜「400k」) · :383(搜「42 hours」)
TTFT 实测Code Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:392(搜「TTFT」)
平方增长与 H100 价格Code Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:432(搜「quadratically」) · :432(搜「$6.88/hour」)
嵌入窗口 8kCode Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:435(搜「8k」)
质量理由、lost in the middleCode Example: Parsing Filestext/13-fm-code-example-parsing-files.txt:438(搜「degrade their retrieval abilities」)
五种切块策略Chunking Strategiestext/14-fm-chunking-strategies.txt:7(搜「Fixed-size」) · :16(搜「sentencizer」) · :22(搜「RecursiveCharacterTextSplitter」) · :28(搜「heading levels」) · :34(搜「Semantic chunking」)
语义切块评测结论Chunking Strategiestext/14-fm-chunking-strategies.txt:91(搜「Is Semantic Chunking Worth the Computational Cost」)

Footnotes

  1. 出处:「The Ingestion Flow」第 7 段(text/09-fm-the-ingestion-flow.txt:7,搜「parsing, chunking, embedding」)。原文还指出生产上这四步是异步编排、带重试的。

  2. 出处:「The Query Flow」内 Document Parsing 节,第 67 段(text/10-fm-the-query-flow.txt:67,搜「paramount」)。原文:「accuracy at this stage is paramount」,任何摄入阶段的错误都会损害知识库的完整性与可靠性。

  3. 出处:「Extracting Text from Various File Formats」第 7 段(text/11-fm-extracting-text-from-various-file-formats.txt:7,搜「PostScript」)。

  4. 出处:「Extracting Text from Various File Formats」第 10 段(text/11-fm-extracting-text-from-various-file-formats.txt:10,搜「columns」)。

  5. 出处:「The Ingestion Flow」第 20 段(text/09-fm-the-ingestion-flow.txt:20,搜「OCR」)。

  6. 出处:「Code Example: Parsing Files」第 14 段(text/13-fm-code-example-parsing-files.txt:14,搜「proximity」)、第 83 段(同文件,搜「find_tables」)、第 122 段(同文件,搜「extract_unstructured_text」)、第 186 段(同文件,搜「get_images」)。

  7. 出处:「Extracting Text from Various File Formats」第 19 段(text/11-fm-extracting-text-from-various-file-formats.txt:19,搜「XML」)。

  8. 出处:「Extracting Text from Various File Formats」第 25-37 段(text/11-fm-extracting-text-from-various-file-formats.txt:34,搜「According to AI」;:37,搜「metadata」)。示例中的 class 是网页标签上标记「这一段是什么」的属性。

  9. 出处:「Extracting Text from Various File Formats」第 69 段(text/11-fm-extracting-text-from-various-file-formats.txt:69,搜「where」)。

  10. 出处:「Document Parsing with Vision–Language Models」第 4 段(text/12-fm-document-parsing-with-vision-language-models.txt:4,搜「VLM」)。

  11. 出处:「Document Parsing with Vision–Language Models」第 7 段(text/12-fm-document-parsing-with-vision-language-models.txt:7,搜「hallucination」)与第 10 段(同文件,搜「cheapest successful parser-first」)。

  12. 出处:「Code Example: Parsing Files」第 365-377 段(text/13-fm-code-example-parsing-files.txt:365,搜「400k」)。

  13. 出处:「Code Example: Parsing Files」第 383 段(text/13-fm-code-example-parsing-files.txt:383,搜「42 hours」)。

  14. 出处:「Code Example: Parsing Files」第 392 段(text/13-fm-code-example-parsing-files.txt:392,搜「TTFT」)。测试配置:Llama 3.3 70B、2×H100、FP8 精度(FP8=用 8 位浮点数表示参数,省显存提速的量化手段,第 09 章会再遇到)。

  15. 出处:「Code Example: Parsing Files」第 432 段(text/13-fm-code-example-parsing-files.txt:432,搜「quadratically」)。

  16. 出处:「Code Example: Parsing Files」第 432 段(text/13-fm-code-example-parsing-files.txt:432,搜「$6.88/hour」)。单张 H100 按需实例 $6.88/小时 ≈ $5,022/月。

  17. 出处:「Code Example: Parsing Files」第 435 段(text/13-fm-code-example-parsing-files.txt:435,搜「8k」)。

  18. 出处:「Practical Tips and Considerations」第 4 段(text/18-fm-practical-tips-and-considerations.txt:4,搜「silently truncate」)。

  19. 出处:「Code Example: Parsing Files」第 438 段(text/13-fm-code-example-parsing-files.txt:438,搜「degrade their retrieval abilities」)。「lost in the middle」是这个现象的通用名字(补充:不在本句原文里,是行业术语;原书在图表标题处使用)。

  20. 出处:「Chunking Strategies」第 7 段(text/14-fm-chunking-strategies.txt:7,搜「Fixed-size」)、第 16 段(同文件,搜「sentencizer」)、第 22 段(同文件,搜「RecursiveCharacterTextSplitter」)、第 28 段(同文件,搜「heading levels」)、第 34 段(同文件,搜「Semantic chunking」);五法对照表为原书 Table 2-4(第 43-80 段)。

  21. 出处:「Chunking Strategies」第 91 段(text/14-fm-chunking-strategies.txt:91,搜「Is Semantic Chunking Worth the Computational Cost」)。基准为 BEIR 与 RAGBench;作者注:这些基准是 pre-RAG 时代的短文本。

  22. 出处:「Chunking Strategies」第 88 段(text/14-fm-chunking-strategies.txt:88,搜「BEIR」)。