犀牛鸟 2026 研究笔记 estelledc.github.io

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 架构深度剖析——犀牛鸟竞赛核心项目