Ch05: 文档解析与分块——输入质量决定一切
Part 2: RAG 核心技术 前置章节:Ch04: 检索三路军——BM25、向量、知识图谱的协奏 后续章节:Ch06: WeKnora 架构深度剖析——犀牛鸟竞赛核心项目
一、开场:”垃圾进垃圾出”——为什么解析质量是 RAG 的天花板
结论先行:RAG 系统的质量上限不由检索算法决定,而由文档解析质量决定。再精密的三路混合检索,如果上游把表格数据切碎了、把图片里的文字丢了,召回的就是残缺信息——LLM 基于残缺信息生成的答案只会更差。
这个道理在计算机科学领域有个经典表述:GIGO(Garbage In, Garbage Out),垃圾进垃圾出。RAGFlow 项目把它提炼为一个更积极的设计原则——”向上游要质量”。
日常类比:想象你是一家餐厅的主厨。你可以选择用新鲜食材(从菜市场精挑细选的当天蔬菜、当天宰杀的鸡),也可以选择用冷冻速食半成品。即使你的烹饪技术再高超(检索算法再精密),用冷冻速食做出的菜也不可能超越新鲜食材的味道上限。RAG 的文档解析就是”采购环节”——你把原始文档变成 chunk 的质量,决定了后续检索和生成的天花板。
具体到 RAG 场景,”垃圾输入”的典型表现包括:一份年报 PDF 里的财务表格被当成纯文本按 500 字切分,”营收”在一个 chunk,”23.5 亿”在另一个 chunk,检索时答案被撕裂;一份扫描合同 50 页全是图片,纯文本提取拿到的是空白或 OCR 乱码;一篇学术论文的数学公式变成一堆无意义的符号序列。这些都是解析质量不足导致的系统性失败——无论下游的向量检索、BM25、RRF 融合做得多精细,被撕裂的信息无法在检索阶段被”拼回来”。
RAGFlow 的设计哲学是:与其在检索和 Rerank 阶段做复杂补救,不如在解析阶段就把文档结构理清楚。这和 WeKnora “多路检索互补”的思路正好互补——RAGFlow 在文档摄入管线做极致深度解析,保证每条 chunk 都是结构完整的语义单元;WeKnora 在检索管线做三路混合 + RRF + 复合 Rerank,保证每条查询都能找到最相关的 chunk。两种思路不矛盾,理想的 RAG 系统应该在两端同时用力。
二、文档解析能力矩阵——五大项目横向对比
不是所有 RAG 系统都在文档解析上投入相同的资源。有的项目把解析做到极致(RAGFlow),有的项目把重心放在检索深度(WeKnora),有的项目把重心放在平台生态(Dify)。了解各项目在解析能力上的差异,有助于在不同场景下做出正确选型。
2.1 格式支持能力对比
| 格式类型 | RAGFlow | WeKnora | Dify | FastGPT | AnythingLLM |
|---|---|---|---|---|---|
| 纯文本 PDF | 极深(DeepDoc 版面分析) | 深(IMA 引擎) | 中(PyMuPDF/pdfplumber) | 基础(第三方服务) | 基础(文本层提取) |
| 含表格 PDF | 极深(行列结构恢复+跨行跨列) | 深(IMA 表格识别) | 基础(丢失行列关系) | 基础(展平为文本) | 不支持 |
| 扫描件 PDF/图片 | 极深(深度学习 OCR+印章去除) | 深(IMA OCR) | 插件式(依赖外部 OCR) | 基础(外部 OCR 服务) | 不支持 |
| Word 文档 | 深(嵌套表格+图片提取) | 深(XML 树遍历) | 中(python-docx) | 基础 | 基础 |
| Excel/CSV | 深(列名保留+按行分块) | 中 | 中 | 基础 | 基础 |
| PPT | 深(每页 OCR+演讲者备注) | 中 | 基础 | 基础 | 基础 |
| 网页/HTML | 中 | 中 | 中 | 中 | 中 |
| Markdown | 好(直接解析,不走 DeepDoc) | 好 | 好 | 好 | 好 |
| 支持格式总数 | 20+ | 15+ | 10+ | 10+ | 8+ |
2.2 解析深度的三个层次
文档解析不是一个二元问题(能解析/不能解析),而是有明确的深度梯度:
Level 1:文本提取(能不能把字读出来)。大多数 RAG 系统都能做到。对”原生 PDF”(由 Word/LaTeX 生成的),直接读取内嵌文本层即可。但对扫描件,OCR 错误率可能高达 5-15%。这个层次的项目包括 AnythingLLM、FastGPT 的基础模式、Dify 的默认配置。
Level 2:结构理解(能不能区分标题、段落、表格、图片说明)。只有 RAGFlow DeepDoc 和 WeKnora IMA 能做到。核心技术是版面分析——给定一个 PDF 页面图像,识别出每个区域的类型和边界。这需要训练专门的深度学习目标检测模型,不是简单的规则匹配能做到的。
Level 3:语义理解(能不能理解”这段表格说明了什么”)。目前没有项目能可靠做到。这需要多模态 LLM 参与——把 PDF 的每一页作为图像送入 GPT-4V / Qwen-VL,让模型同时理解视觉布局和文本语义。成本极高、速度慢,处于前沿探索阶段。
2.3 解析引擎定位差异
RAGFlow 和 WeKnora 虽然都达到了 Level 2,但侧重点不同。RAGFlow 的 DeepDoc 更强调”还原文档的视觉结构”——它把 PDF 页面当成图像来”看”,用目标检测模型识别每个区域的类型和边界,然后恢复表格的行列关系、提取图片中的文字。WeKnora 的 IMA 引擎更强调”从文档中抽取知识实体”——它对中文文档的优化更深入(中文字符的 OCR、中文排版的版面分析),且与后续的知识图谱构建紧密耦合。
Dify、FastGPT、AnythingLLM 基本停留在 Level 1,对复杂排版的处理依赖第三方库(Unstructured / PyPDF2 / pdfplumber),质量参差不齐。它们的竞争维度不在文档解析,而在平台能力(Dify 的可视化 Workflow)、企业特性(FastGPT 的多租户)、或部署便利性(AnythingLLM 的单文件部署)。
三、RAGFlow DeepDoc 引擎详解——如何像人一样”读”文档
3.1 为什么需要”深度文档理解”
日常类比:普通 OCR 扫描仪就像一个只会逐字抄写的抄录员——他面前放着一份报纸,他从左上角开始一个字一个字地抄,不管这个字是标题、正文、广告还是表格,全部混在一起变成一段长文本。而 DeepDoc 更像一个真正”会读报纸”的人——他看一眼就知道”这是标题要大号字体”、”这是表格要保留行列结构”、”这是广告可以跳过”、”这两栏的阅读顺序是先左后右”。DeepDoc 试图让计算机拥有这种”一眼看排版”的认知能力。
传统 RAG 系统对文档的处理流程是:读取文本 - 按固定长度切分 - 向量化。这个流程对结构简单的纯文本(如 Markdown 笔记)够用,但对企业场景中的复杂文档存在四个系统性问题:表格数据被撕裂(表头和数据行分属不同 chunk);图片文字丢失(扫描件中的文字以图片形式存在);版面信息缺失(无法区分标题和正文);公式无法识别(数学公式变成乱码符号)。DeepDoc 就是为了系统性地解决这四个问题。
3.2 DeepDoc 核心处理管线
DeepDoc 的处理是一条五步管线,每一步的输出是下一步的输入:
文档输入(PDF / Word / Excel / PPT / 图片...)
|
+-- Step 1: 版面分析(Layout Analysis)
| 输入: PDF 页面图像
| 输出: 每个区域的类型标注 + 边界框坐标
| 模型: 基于深度学习的目标检测(类 YOLO/Mask R-CNN)
| 检测类型: 文本/标题/表格/图片/页眉/页脚/脚注/列表
|
+-- Step 2: 文本提取(Text Extraction)
| 对文本区域: 直接提取 PDF 内嵌文本(快,毫秒级)
| 对图片/扫描区域: 调用 OCR 引擎(慢,需推理)
|
+-- Step 3: 表格结构恢复(Table Structure Recognition)
| 输入: 版面分析识别出的表格区域图像
| 输出: 行列关系 + 跨行跨列信息
| 输出格式: HTML <table> 标签保留完整结构
|
+-- Step 4: 公式提取(Formula Extraction)
| 输入: 公式区域图像
| 输出: LaTeX 字符串
| 用途: 让 LLM 能理解数学公式的含义
|
+-- Step 5: 结构化内容组装
按版面顺序组装各区域内容
保留区域间层级关系(标题-段落-子段落)
输出: 结构化文档对象,供后续分块使用
3.3 版面分析——最关键的第一步
版面分析决定了后续所有处理步骤的输入质量。它的核心任务是:给定一个 PDF 页面渲染出的图像,识别出页面中每个区域的类型和精确边界。
RAGFlow 使用基于深度学习的目标检测模型来做版面分析。模型在大量标注了区域类型的文档数据集上训练,能够识别不同排版风格下的区域边界。这和物体检测(YOLO 检测猫狗汽车)是同样的技术原理——只是检测目标从”猫”变成了”表格”、从”汽车”变成了”标题”。
版面分析检测的区域类型及其处理方式:
| 区域类型 | 说明 | 后续处理 |
|---|---|---|
| 文本(Text) | 正文段落 | 直接提取文本,按段落分块 |
| 标题(Title) | h1/h2/h3 层级 | 作为分块边界标记 |
| 表格(Table) | 包含行列结构的数据区 | 送入表格结构恢复模块 |
| 图片(Figure) | 插图、图表 | OCR 提取文字 + 图片描述 |
| 页眉/页脚 | 页码、版权信息 | 过滤掉,不进入 chunk |
| 脚注(Footnote) | 底部注释 | 保留,关联到引用位置 |
| 列表(List) | 有序/无序列表 | 保持列表完整性,不从中间切断 |
版面分析的三大难点:第一是复杂排版——双栏排版中左栏末尾和右栏开头在空间上不相邻,但在语义上是连续的,模型需要正确推断阅读顺序。第二是无边框表格——只靠对齐和间距来暗示行列结构,没有明显的线条可供检测。第三是跨页元素——一个表格可能横跨两页,版面分析需要把两个页面上的表格片段关联起来。
3.4 表格结构识别——DeepDoc 最有价值的模块
表格处理是 DeepDoc 与简单文本提取差距最大的环节。
传统方法(pdfplumber、Camelot)基于线条检测——寻找水平线和垂直线来推断行列边界。但在无边框表格、虚线边框、跨行跨列、合并单元格等场景完全失效。RAGFlow 使用深度学习模型直接从图像预测表格结构,不依赖线条检测。
一个具体的例子能说明差距。原始 PDF 中有一个含跨列的财务表格:
+----------+----------+-----------+-----------+
| 指标 | 2024 年 | 2023 年 |
+----------+----------+-----------+-----------+
| | | 上半年 | 下半年 |
+----------+----------+-----------+-----------+
| 营收 | 100 亿 | 40 亿 | 60 亿 |
| 净利润 | 20 亿 | 8 亿 | 12 亿 |
+----------+----------+-----------+-----------+
简单文本提取的结果:”指标 2024年 2023年 上半年 下半年 营收 100亿 40亿 60亿 净利润 20亿 8亿 12亿”——所有文字混在一起,行列关系完全丢失。如果按 500 字切分,”营收”和”100 亿”可能分属不同 chunk。
DeepDoc 表格结构恢复后:输出一个完整的 HTML table,保留了 colspan=”2” 表示”2023 年”跨了两列,保留了每行每列的精确对应关系。整个表格作为一个 chunk 存入向量库——检索”2024 年营收是多少”时,召回的是完整表格,LLM 可以精确回答”100 亿”。
表格识别的核心难点包括:跨行跨列的预测(模型需要判断某个单元格是否跨了多行或多列)、嵌套表格的处理(表格内再嵌套子表格)、以及表格内图片(某些单元格包含小图标或 sparkline 迷你图表)。
3.5 OCR 管线——让扫描件”活过来”
当版面分析识别出图片区域或判定当前页面是扫描件时,DeepDoc 会启动 OCR 管线。这个管线分四步:图像预处理(去噪、倾斜校正、二值化)、文本检测(定位文字区域边界框,使用 DBNet/PSENet 等模型)、文本识别(识别每个区域的具体文字,使用 CRNN/SVTR/TrOCR 等模型,支持中英文混合)、后处理(拼写纠错、阅读顺序排列、置信度过滤)。
OCR 在中文企业场景有几个特殊挑战。第一是中英文混排——合同中经常出现中英文混合表达,OCR 需要在中英文之间正确切换识别模式。第二是印章遮挡——中国合同文档中常见红色印章覆盖在文字上方,DeepDoc 有印章检测和去除的预处理步骤。第三是低质量扫描件——旧文档的扫描件可能模糊、有噪点、墨迹不均匀,极端情况下 OCR 仍然力不从心。
OCR 与简单文本提取的本质区别:简单文本提取(如 PyPDF2)只读取 PDF 内嵌的文本层。对于”原生 PDF”效果不错,但对”扫描件 PDF”,内嵌文本层可能完全为空(纯图片扫描件)、包含低质量 OCR 文本(扫描仪自带 OCR 的结果)、或文本顺序混乱(PDF 内嵌文本的排列可能与视觉阅读顺序不一致)。DeepDoc 的 OCR 在这些场景中自动接管,确保即使是扫描件也能获得可用的文本。
3.6 公式提取——学术场景的关键能力
学术论文和技术文档中的数学公式,在纯文本提取后会变成一堆无意义的符号序列——因为 PDF 渲染公式时使用的是字体映射而非 Unicode 文本。DeepDoc 的公式提取模块先由版面分析识别出公式区域,然后用专门的公式识别模型将图像转为 LaTeX 表示。这使得 RAGFlow 在学术论文场景下有显著优势——用户问”傅里叶变换的公式是什么”,能召回包含完整 LaTeX 公式的 chunk,而不是一堆乱码。
3.7 DeepDoc vs 简单文本提取:全维度对比
| 维度 | 简单文本提取(PyPDF2 等) | DeepDoc 深度解析 |
|---|---|---|
| 表格 | 文字堆叠,行列关系丢失 | 保留完整行列结构(HTML table) |
| 图片文字 | 完全丢失 | OCR 提取 |
| 扫描件 | 空白或乱码 | 深度学习 OCR |
| 版面信息 | 丢失 | 区域类型标注(标题/段落/表格/图片) |
| 公式 | 乱码符号 | LaTeX 表示 |
| 跨页表格 | 截断 | 自动关联 |
| 页眉页脚 | 混入正文 | 自动过滤 |
| 处理速度 | 极快(毫秒级/页) | 慢(秒到分钟级/页) |
| 资源需求 | CPU 即可 | 需要 GPU(版面分析+OCR 推理) |
| 适用场景 | 简单纯文本文档、Markdown | 企业复杂文档、PDF、扫描件 |
DeepDoc 的代价是显而易见的:一份含表格的 10 页 PDF 需要约 30 秒处理(简单提取不到 1 秒),一份 50 页的复杂年报可能需要 5 分钟以上。RAGFlow 的应对策略是异步处理——文档上传后放入任务队列,后台解析完成后通知用户。对于实时索引更新的场景,这是需要权衡的设计取舍。
四、六种分块策略详解——没有”银弹”,只有”匹配度”
分块(Chunking)是 RAG 系统中”最不起眼但影响最大”的环节。一个 768 维向量要承载 chunk 的全部语义信息——chunk 太大,向量被”稀释”,搜索不精确;chunk 太小,上下文丢失,LLM 看到的是碎片。更根本的矛盾是:最优分块是查询依赖的概念——同一篇文档面对不同 query,最优分块方式不同——但分块发生在索引阶段(离线),query 发生在检索阶段(在线),你不可能为每个 query 重新分块。
日常类比:把一本书拆成便于检索的卡片。按页拆?一页太长搜不准。按段拆?有些段落只有一句话,脱离上下文看不懂。按句拆?句与句之间的逻辑关系丢了。最理想的是按”知识点”拆——但”什么算一个知识点”本身就是主观判断。
以下六种策略代表了当前 RAG 领域的主流方案,它们在粒度、智能程度、计算成本上形成了一个完整的梯度。
4.1 固定长度分块(Fixed-size Chunking)
描述:按固定 token 数(如 500)硬切,相邻 chunk 之间有一定重叠(overlap,如 50 tokens)。这是最简单也最古老的分块方式。
chunk_size = 500 tokens
chunk_overlap = 50 tokens
文档: [token_0...token_499] [token_450...token_949] [token_900...token_1399]
chunk_1 chunk_2 chunk_3
适用场景:知识库规模大但对检索质量要求不高的快速原型;内容结构简单的纯文本文档。
优点:实现极简单(10 行代码即可),chunk 大小可预测(方便计算存储和 token 消耗),对任何格式的文档都能工作。
缺点:完全不感知语义边界——可能把”营收”和”23.5 亿”切到两个 chunk;可能把一个函数定义从第 25 行切断;可能把问题和答案分到不同 chunk。overlap 只能部分缓解问题(50 tokens 的重叠可能不够覆盖跨 chunk 的语义单元)。
使用该策略的项目:AnythingLLM(默认且唯一策略)、Dify(默认配置)、FastGPT(基础模式)。
4.2 按标题分块(Header-based Chunking)
描述:以文档的标题层级(h1/h2/h3)作为天然的分块边界。每个标题和它下属的内容构成一个 chunk。
# 第一章 概述 <-- 分块边界
这是第一章的内容...
## 1.1 背景 <-- 分块边界
这是 1.1 的内容...
## 1.2 目标 <-- 分块边界
这是 1.2 的内容...
适用场景:结构良好的 Markdown 文档、技术手册、产品文档——任何有清晰标题层级的文档。
优点:利用了文档作者已经定义好的语义结构——标题之间就是天然的话题切换点。不需要任何机器学习模型,只需要检测标题格式。分块粒度与文档逻辑结构一致。
缺点:依赖文档有明确标题——扫描件、纯文本邮件、对话记录等无结构文档无法使用。如果某个章节特别长(5000+ tokens),整个章节作为一个 chunk 可能太大。如果某个章节只有一句话,chunk 太小缺少上下文。
使用该策略的项目:WeKnora 4 级降级链中的第一级(heading)、Dify 的”自动分块”模式。
4.3 按段落分块(Paragraph-based Chunking)
描述:以自然段落(通常由空行分隔)作为分块单位。每个段落是一个 chunk,或将若干短段落合并为一个 chunk 直到达到目标大小。
适用场景:叙述性文档(报告、散文、新闻稿)——段落本身是作者组织思想的最小单元。
优点:段落是自然的语义单元——一个段落通常围绕一个中心思想展开,比固定长度切分更尊重语义边界。不需要文档有标题结构。
缺点:段落长度差异巨大——有的段落只有一句话(如过渡句),有的段落有 500+ 字(如详细描述)。过短的段落缺少自足的上下文。依赖段落检测的准确性——PDF 中段落的检测需要版面分析(回到 DeepDoc 的话题)。
使用该策略的项目:RAGFlow 通用模板的默认逻辑、Dify 的部分配置。
4.4 QA 对拆分(Q&A Pair Splitting)
描述:先让 LLM 把文档内容改写成”问题-答案”对,再以每个 QA 对作为一个不可分割的 chunk。这是 FastGPT 的标志性策略。
原文:"FastGPT 的工作流引擎支持条件分支、循环和变量传递。
它使用 DAG 有向无环图来组织节点间的数据流。"
| LLM 调用
v
QA 对 1:
Q: FastGPT 工作流引擎支持哪些控制结构?
A: 支持条件分支、循环和变量传递。
QA 对 2:
Q: FastGPT 工作流使用什么架构来组织节点?
A: 使用 DAG 有向无环图来组织节点间的数据流。
适用场景:FAQ 文档、客服话术库、产品帮助中心——任何天然就是”问题到答案”结构的知识库。也适合对检索精度要求极高的场景。
优点:Q 字段预判了用户的提问方式,向量检索可以直接匹配——这比在原文中搜索更精确。Q 和 A 绑定为一体,不会出现”召回了问题没召回答案”的撕裂问题。检索精度在 FAQ 类场景下显著高于其他策略。
缺点:每段文本都需要调用 LLM 生成 QA 对,摄入成本高(一篇 10,000 字的文档可能产出 50-100 个 QA 对,每个需要一次 LLM 调用)。LLM 改写可能遗漏细节或引入理解偏差——原文的 nuance 在 QA 化过程中丢失。不适合叙述性文档(故事、论文、报告),因为叙述性信息是线性递进的,不是可以独立提取的知识点。
使用项目:FastGPT(核心特色功能)。
4.5 模板驱动分块(Template-driven Chunking)
描述:为不同文档类型预置专门的分块模板,每种模板定义不同的分块逻辑。学术论文按 Abstract/Method/Experiment/Conclusion 切分,法律文书按”条款”切分,FAQ 文档按问答对切分,PPT 按每页切分。
RAGFlow 内置了 9 种模板:
| 模板名称 | 适用文档类型 | 分块逻辑 | 特殊处理 |
|---|---|---|---|
| 通用(General) | 混合文档 | 按段落 + 语义边界 | 无特殊处理 |
| 问答(Q&A) | FAQ、客服话术 | 每个问答对为一个 chunk | Q 和 A 绑定不拆分 |
| 手册(Manual) | 产品手册 | 按章节层级(h1/h2/h3) | 保留操作步骤完整性 |
| 论文(Paper) | 学术论文 | 按 Abstract/Intro/Method/Exp/Conclusion | 公式保留 LaTeX |
| 法律(Law) | 法律法规 | 按条款切分 | 条款编号与正文绑定 |
| 书籍(Book) | 长篇书籍 | 按章节 + 小节 | 章节摘要自动生成 |
| 演示(Presentation) | PPT/Keynote | 每页幻灯片为一个 chunk | OCR + 演讲者备注 |
| 表格(Table) | Excel/CSV | 每行/每条记录为一个 chunk | 列名作为上下文保留 |
| 图片(Picture) | 图片集合 | 每张图片 OCR 为一个 chunk | 图片描述生成 |
适用场景:企业知识库中文档类型明确且多样化的场景——同一个系统可能既有法律合同又有技术手册又有 FAQ。
优点:每种模板针对特定文档结构优化,分块质量远高于”一刀切”。法律模板将条款编号与正文绑定、论文模板保留公式 LaTeX、Q&A 模板保证问答对不拆分。模板可扩展——用户可以为自己的文档类型创建新模板。
缺点:需要选择或创建模板——用户必须知道自己的文档属于哪种类型。模板覆盖不到的文档类型只能用通用模板兜底。模板的维护本身也是工程成本。
使用项目:RAGFlow(9 种内置模板)。
法律模板的具体示例:
输入法律文档:
第三十二条 有下列情形之一的,用人单位可以解除劳动合同:
(一)在试用期间被证明不符合录用条件的;
(二)严重违反用人单位的规章制度的;
(三)严重失职,营私舞弊,给用人单位造成重大损害的;
法律模板分块输出:
Chunk: {
"article": "第三十二条",
"content": "有下列情形之一的,用人单位可以解除劳动合同:...",
"references": ["第十五条"] // 自动识别引用关系
}
条款编号与正文绑定——检索”第三十二条”时,整条法规完整出现。引用关系被提取出来,可用于后续知识图谱构建。
4.6 四级自适应分块(4-level Adaptive Chunking)
描述:不固定使用某一种策略,而是根据文档结构质量自动选择最合适的分块方式,形成降级链。
auto(自动检测文档结构)
| 如果有明确的标题层次结构
v
heading(按标题分块)
| 如果标题结构不清晰或不均匀
v
heuristic(启发式——段落间距 / 空行 / 缩进模式)
| 如果启发式失败
v
recursive(递归字符分割——按 chunk_size / chunk_overlap 硬切)
| 如果 recursive 也出问题
v
legacy(最原始的固定长度切割)
适用场景:文档输入质量不可控的场景——知识库中既有结构良好的 Markdown 又有 OCR 出来的纯文本,系统需要自动适应不同质量的输入。
优点:全自动,不需要用户选择策略。对高质量文档用高质量策略(heading),对低质量文档自动降级到兜底策略(recursive/legacy),确保不会因为”用错策略”而产生更差的结果。可选的语义级分块——计算相邻句子的 embedding 余弦相似度,在语义”断崖”处切分(相似度低于阈值就切)。
缺点:降级判断逻辑可能误判文档结构——把有标题的文档误判为无结构,或把无标题的文档强行按”假标题”切分。降级链本身也是文档维度的自适应,不是查询维度的——同一篇文档面对不同 query 最优分块可能不同,但只能在索引时做一次决策。
使用项目:WeKnora(核心特色功能)。
语义级分块的工作原理:
1. 把文档分成句子序列: [s1, s2, s3, ..., sn]
2. 对每个句子做 embedding: [e1, e2, e3, ..., en]
3. 计算相邻句子的余弦相似度: sim(ei, ei+1)
4. 当 sim(ei, ei+1) < 阈值(0.5-0.7) 时,在 si 和 si+1 之间切分
5. 日常类比:连续几页都在讲同一个主题,突然话题急转——
语义相似度在切分点出现"断崖"
分块策略全景对比表
| 策略 | 粒度控制 | 智能程度 | 摄入成本 | 检索精度 | 使用项目 |
|---|---|---|---|---|---|
| 固定长度 | 字符/token 数硬切 | 无智能 | 极低 | 低 | AnythingLLM, Dify 默认 |
| 按标题 | 文档结构驱动 | 中 | 低 | 中 | Dify, WeKnora heading 级 |
| 按段落 | 自然段落边界 | 中 | 低 | 中 | 通用方案 |
| QA 对拆分 | 知识点粒度 | 高(LLM 理解) | 高(LLM 调用) | 高(FAQ 场景) | FastGPT |
| 模板驱动 | 文档类型专属逻辑 | 高(领域知识) | 中 | 高(匹配场景) | RAGFlow |
| 4 级自适应 | 自动选择最优粒度 | 最高(自动降级) | 低-中 | 中-高 | WeKnora |
五、代码示例:PDF 从上传到 chunk 的完整管线
以下用 Python 伪代码展示一个 PDF 文档从上传到最终存入向量数据库的完整处理流程。这个管线融合了 DeepDoc 风格的深度解析思想:
import numpy as np
from typing import List, Dict
def pdf_to_chunks_pipeline(pdf_path: str) -> List[Dict]:
"""PDF 从上传到 chunk 的完整管线(伪代码)"""
# Step 1: 加载 PDF,逐页转为图像
pages = load_pdf_as_images(pdf_path) # 每页 -> PIL Image
# Step 2: 版面分析——识别每页中的区域类型
all_regions = []
for page_img in pages:
regions = layout_detector.detect(page_img)
# regions: [{"type": "title"|"text"|"table"|"figure", "bbox": [...]}]
all_regions.append(regions)
# Step 3: 按区域类型分别处理
structured_content = []
for page_regions in all_regions:
for region in page_regions:
if region["type"] == "text":
text = extract_embedded_text(region) # PDF 内嵌文本
if not text: # 扫描件:内嵌文本为空
text = ocr_engine.recognize(region["image"])
structured_content.append({"type": "text", "content": text})
elif region["type"] == "table":
# 表格结构恢复:输出 HTML table
html_table = table_recognizer.recognize(region["image"])
structured_content.append({"type": "table", "content": html_table})
elif region["type"] == "title":
title_text = extract_embedded_text(region)
structured_content.append({"type": "title", "content": title_text})
# 页眉/页脚:跳过不处理
elif region["type"] in ("header", "footer"):
continue
# Step 4: 基于结构的智能分块(标题作为边界,表格不拆分)
chunks = []
current_chunk = ""
for item in structured_content:
if item["type"] == "title": # 标题 = 分块边界
if current_chunk.strip():
chunks.append(current_chunk.strip())
current_chunk = item["content"] + "\n"
elif item["type"] == "table": # 表格 = 独立 chunk
if current_chunk.strip():
chunks.append(current_chunk.strip())
current_chunk = ""
chunks.append(item["content"]) # 整个表格不拆分
else:
current_chunk += item["content"] + "\n"
if current_chunk.strip():
chunks.append(current_chunk.strip())
# Step 5: 向量化
results = []
for i, chunk_text in enumerate(chunks):
embedding = embedding_model.encode(chunk_text) # -> 768-dim vector
results.append({
"id": f"chunk_{i}",
"text": chunk_text,
"embedding": embedding.tolist(),
"metadata": {"source": pdf_path, "chunk_index": i}
})
# Step 6: 写入向量数据库
vector_db.upsert(results)
return results
这段伪代码展示了核心思路:先理解文档结构(版面分析),再基于结构做智能分块(标题边界、表格整体保留),而不是对原始文本做暴力的固定长度切割。真实的 RAGFlow DeepDoc 实现远比这个复杂——包含公式识别、跨页表格关联、页眉页脚过滤、多种模板选择等——但底层逻辑一致。
六、中文分词的特殊挑战
在 RAG 系统中,中文分词问题主要影响 BM25 关键词检索的质量。英文天然以空格分隔单词,BM25 直接按空格切分就能工作。中文没有这个天然边界——”知识图谱构建”是一个词还是”知识”+”图谱”+”构建”三个词?答案取决于语境和领域。
6.1 分词错误如何影响检索质量
考虑一个医疗知识库的场景。用户搜索”充血性心力衰竭”,jieba 默认词典可能将其切分为”充血”+”性”+”心力”+”衰竭”。而文档中出现的是完整术语”充血性心力衰竭”,被切分为”充血性”+”心力衰竭”。两种切分方式不一致,BM25 用”心力”去匹配”心力衰竭”时得分较低——因为倒排索引中存的是”心力衰竭”整体,不是”心力”单独。最终的检索结果可能遗漏了最相关的文档。
这个问题在通用领域不太明显(jieba 对常见词切分准确率很高),但在专业领域(医疗、法律、金融、技术)中是真实痛点。每个领域都有大量的专业术语,通用分词器的词典不覆盖这些术语。
日常类比:英文像乐高积木——每个词是独立的积木块,拼接方式一目了然。中文像面团——”知识图谱”是一块还是”知识”+”图谱”两块?看语境。如果切错了,BM25 就是在用错误的关键词去索引中查找,自然找不到正确的文档。
6.2 自定义词典的作用与维护成本
解决方案是维护自定义词典——把领域专有术语加入分词器的词典,确保它们被当作整体。WeKnora 和 FastGPT 都支持自定义词典功能。
但词典维护本身是持续工程。新术语不断涌现(”大模型”“RLHF”“MoE”在两年前还不是常见术语),旧术语可能演变。谁来负责持续更新词典?频率是多少?错误添加了不该作为整体的词怎么办?这些都是实际部署中的运维问题。
6.3 中英混合的双重困难
中国技术用户习惯中英混合表达——”怎么 fine-tune 大模型”“配置 Kubernetes 的 namespace”。分词器面临两个困难:识别中英文边界(”fine-tune”是一个词还是两个?),以及英文术语和中文同义词的对齐(”namespace”和”命名空间”是不是同一回事?)。
向量检索在中英混合场景下反而表现更好——多语言 Embedding 模型(如 bge-m3)能把中文”知识图谱”和英文”Knowledge Graph”映射到相近的向量空间位置,不依赖分词质量。这也是为什么混合检索(BM25 + 向量)比纯 BM25 更鲁棒——向量路可以弥补 BM25 因分词错误导致的漏检。
各项目的中文处理对比:
| 项目 | 中文处理方式 | 不足 |
|---|---|---|
| WeKnora | 自定义中文分词器 + IMA 引擎 | 词典需要领域专家维护 |
| FastGPT | jieba 分词 + QA 对拆分 | jieba 通用词典对专业领域效果差 |
| RAGFlow | DeepDoc 对中文排版优化 | 检索层面的中文特化较弱 |
| Dify | 基础中文分词 | 依赖用户选择中文优化的 Embedding 模型 |
| AnythingLLM | 无特化 | 完全依赖 Embedding 模型的中文能力 |
七、本章小结
本章的核心结论可以浓缩为一句话:文档解析和分块是 RAG 系统的地基,地基歪了,上面建什么都会倒。
具体来说,我们建立了以下认知:
第一,”垃圾进垃圾出”不是空话。解析质量直接决定了 RAG 系统的质量天花板——再精妙的检索算法、再强大的 Rerank 模型,都无法从残缺的 chunk 中还原出完整的答案。RAGFlow “向上游要质量”的设计哲学值得所有 RAG 系统学习。
第二,文档解析存在三个层次的能力阶梯:文本提取(Level 1)到 结构理解(Level 2)到 语义理解(Level 3)。目前最强的 DeepDoc 做到了 Level 2 的极致,Level 3 仍然是开放问题。
第三,没有万能的分块策略。固定长度简单但粗暴,QA 对精确但昂贵,模板驱动领域精准但覆盖有限,自适应降级全面但可能误判。选择哪种策略取决于文档类型、查询模式和成本预算。
第四,中文 RAG 面临分词这个英文世界不存在的额外挑战。自定义词典和多语言 Embedding 是当前的主要应对手段,但都不是完美解决方案。
第五,RAGFlow 的 DeepDoc 和 WeKnora 的三路检索构成了理想的互补:前者保证输入质量(从源头减少”垃圾”),后者保证检索质量(在已有 chunk 中找到最相关的)。犀牛鸟竞赛中,将 DeepDoc 的文档解析思想迁移到 WeKnora 的 Plugin 体系,是一个有明确价值的贡献方向。
在下一章 Ch06: WeKnora 架构深度剖析——犀牛鸟竞赛核心项目 中,我们将深入 WeKnora 的完整架构,看它如何在检索管线上做到”三路检索 + RRF + 复合 Rerank”的极致设计,以及它的 Plugin 管线如何为我们的竞赛贡献提供切入点。
导读目录:README.md 上一章:Ch04: 检索三路军——BM25、向量、知识图谱的协奏 下一章:Ch06: WeKnora 架构深度剖析——犀牛鸟竞赛核心项目