
一、RAGFlow 分块策略概览
RAGFlow 一共有 15 种分块策略 ,定义在 common/constants.py 的 ParserType 枚举中,通过 rag/svr/task_executor.py 的 FACTORY 字典分发到各自的实现模块。
整体架构
┌─────────────────────────────────────────────────────────────────┐
│ 用户上传文档 │
│ │ │
│ parser_id 选择 │
│ │ │
│ ┌───────────┼───────────┐ │
│ ▼ ▼ ▼ │
│ ┌────────┐ ┌────────┐ ┌────────┐ │
│ │ naive │ │ paper │ │ book │ ...共15种 │
│ │(通用) │ │(论文) │ │(书籍) │ │
│ └───┬────┘ └────────┘ └────────┘ │
│ │ │
│ ┌─────┼──────────┐ (naive 的二级分发) │
│ ▼ ▼ ▼ │
│ deepdoc mineru docling ... (PDF解析引擎) │
└─────────────────────────────────────────────────────────────────┘
15 种策略一览
| # | 策略 | 实现文件 | 适用文件类型 | 核心思路 |
|---|---|---|---|---|
| 1 | naive(通用) | rag/app/naive.py |
docx, pdf, excel, txt, md, html, json 等 | 用分隔符切分文本,再按 token 数上限合并。支持 overlap、子分隔符、嵌入文件提取。默认策略 |
| 2 | manual(手册) | rag/app/manual.py |
pdf, docx | 针对 FAQ 型手册,识别层级标题结构,构建问答树 |
| 3 | qa(问答) | rag/app/qa.py |
xlsx, csv, txt, pdf, docx | 提取问答对。Excel 第一列=问题,第二列=答案;PDF/Docx 用编号模式匹配 |
| 4 | table(表格) | rag/app/table.py |
xlsx, xls, txt, csv | 每行一个 chunk,支持列头的语义扩展(同义词、枚举值) |
| 5 | paper(论文) | rag/app/paper.py |
检测双栏布局,识别摘要/关键词,确保摘要完整保留 | |
| 6 | book(书籍) | rag/app/book.py |
docx, pdf, txt, html | 层级合并,检测冒号标题,去除目录页,适合长文档 |
| 7 | laws(法律) | rag/app/laws.py |
docx, pdf, txt | 识别条款编号的层级结构(条、款),树形合并保留法律层级 |
| 8 | resume(简历) | rag/app/resume.py |
pdf, docx | YOLOv10 布局检测 + LLM 并行提取(基本信息、工作经历、教育) |
| 9 | picture(图片) | rag/app/picture.py |
图片, 视频 | OCR 提取文本 + VLM 生成语义描述。每张图/视频一个 chunk |
| 10 | one(整篇) | rag/app/one.py |
docx, pdf, xlsx, txt 等 | 整个文档作为一个 chunk,不切分 |
| 11 | audio(音频) | rag/app/audio.py |
wav, mp3, aac 等 | 语音转文字(ASR),转写结果作为一个 chunk |
| 12 | email(邮件) | rag/app/email.py |
eml | 解析邮件头和正文,递归处理附件 |
| 13 | tag(标签) | rag/app/tag.py |
xlsx, csv, txt | 两列格式:内容 + 标签,用于构建标签知识库 |
| 14 | presentation(演示) | rag/app/presentation.py |
pdf, pptx | 每张幻灯片一个 chunk,按位置排序文本/表格/图片 |
| 15 | knowledge_graph | 委托给 naive | 同 naive | 实际的图谱抽取是后处理阶段(graphrag 任务类型) |
数据流全貌
文档上传
│
▼
Redis 队列 (te.0.common / te.1.common)
│
▼
Worker 取任务 (collect)
│
▼
FACTORY[parser_id].chunk() ──→ 得到 chunk 列表
│
▼
后处理增强:
├─ 自动关键词 (LLM生成)
├─ 自动问题 (LLM生成)
├─ 元数据提取 (LLM生成)
└─ 标签匹配 (tag知识库)
│
▼
Embedding 向量化
│
▼
写入 ES / Infinity (insert_chunks)
│
▼
更新文档统计 (chunk数, token消耗)
几个有意思的设计点
- naive 是万能底座 ---
knowledge_graph直接复用 naive,email内部也调用naive_chunk()处理附件 - 二级分发 --- naive 内部还有
PARSERS字典,根据layout_recognizer配置选择 PDF 解析引擎(deepdoc / mineru / docling / paddleocr 等) - 领域特化程度不同 ---
laws和paper是结构化很强的领域策略;naive则是通用瑞士军刀;one几乎不做什么
二、Book 策略完整机制解析
第一步:解析文档,获取 sections
Book 支持多种文件格式,每种格式的解析路径不同:
┌─────────────────────────────────────────────────────────────────────┐
│ 文档输入 │
│ │
│ .docx ──→ naive.Docx() ──→ [(text, image, html), ...] │
│ │
│ .pdf ──→ PARSERS[layout_recognizer] ──→ (sections, tables) │
│ 支持: deepdoc / mineru / docling / paddleocr 等 │
│ │
│ .txt ──→ 按行切分 ──→ [(line, ""), ...] │
│ │
│ .html ──→ HtmlParser() ──→ [(line, ""), ...] │
│ │
│ .doc ──→ tika 解析 ──→ [(line, ""), ...] │
└─────────────────────────────────────────────────────────────────────┘
每个 section 是一个 (text, layout_tag) 元组。对于 PDF,layout_tag 可能是 "title"、"text" 等(由布局分析引擎输出)。
第二步:预处理 --- 冒号标题提升
python
make_colon_as_title(sections) # book.py:163
遍历所有 section,如果一行文本以 : 或 : 结尾,且冒号后面有足够多的内容(≥32 字符),就把冒号前面的部分插入为一个 "title" 类型的 section。
例子:
之前: [("绪论:本文主要研究...", "text")]
之后: [("绪论", "title"), ("本文主要研究...", "text")]
第三步:检测编号模式
python
bull = bullets_category([t for t, _ in sections]) # book.py:164
bullets_category 把所有 section 的文本拿去匹配 5 种编号模式组:
模式组 0 (中式法律): 第一编/第一章/第一节/第一条/(一)
模式组 1 (阿拉伯): 第1章/第1节/1./1.1/1.1.1/1.1.1.1
模式组 2 (混合): 第一章/第一节/一、/(一)/(1)
模式组 3 (英文): PART ONE/Chapter I/Section 1/Article 1
模式组 4 (Markdown): #/##/###/####/##### /######
返回命中数最多的 模式组索引(0~4),如果都没有命中返回 -1。
第四步:分块 --- 两条路径
sections
│
make_colon_as_title() ← 冒号行提升为标题
│
bullets_category() ← 检测编号模式
│
┌───────┴───────┐
▼ ▼
bull >= 0 bull < 0
(有编号模式) (无编号模式)
│ │
hierarchical_merge() naive_merge()
(层级树形合并) (按 token 数顺序合并)
路径 A:有编号模式 → hierarchical_merge
depth=5 硬编码,核心算法分为两步:层级构建 和 二次合并。
层级构建:从叶节点向上找爹
算法首先把每个 section 按匹配到的编号正则分组:
假设文档内容,检测到模式组 1(阿拉伯编号):
BULLET_PATTERN[1] = ["第N章", "第N节", "N.", "N.N", "N.N.N", "N.N.N.N"]
输入 sections:
┌───┬──────────────────────┬───────┐
│ i │ text │ level │ ← 匹配到的 BULLET_PATTERN 索引
├───┼──────────────────────┼───────┤
│ 0 │ "第1章 绪论" │ 0 │ ← "第N章" = pattern[0]
│ 1 │ "1.1 研究背景" │ 2 │ ← "N." = pattern[2]
│ 2 │ "背景详细内容..." │ 7 │ ← 正文(无匹配)
│ 3 │ "1.2 研究目标" │ 2 │
│ 4 │ "目标详细内容..." │ 7 │
│ 5 │ "第2章 方法" │ 0 │ ← 新章
│ 6 │ "2.1 实验设计" │ 2 │
│ 7 │ "实验内容..." │ 7 │
└───┴──────────────────────┴───────┘
levels 数组(按 level 分组索引):
Level 0 ("第N章"): [0, 5]
Level 1 ("第N节"): []
Level 2 ("N."): [1, 3, 6]
Level 3~5: []
Level 6 (title布局): []
Level 7 (正文): [2, 4, 7]
然后 levels = levels[::-1] 反转整个数组,从最底层(正文)开始遍历:
反转后:
[0] = Level 7 (正文): [2, 4, 7] ← 最底层
[1] = Level 6 (title布局): []
[2] = Level 5 (N.N.N.N): []
[3] = Level 4 (N.N.N): []
[4] = Level 3 (N.N): []
[5] = Level 2 (N.): [1, 3, 6]
[6] = Level 1 (第N节): []
[7] = Level 0 (第N章): [0, 5] ← 最顶层
算法从每个叶节点(正文段落)出发,通过二分搜索在上级 levels 数组中找到最近的祖先标题,构建层级链:
j=2 (section "背景内容...")
→ 从 Level 2 找到 1.1 (index=1)
→ 从 Level 0 找到 第1章 (index=0)
→ 链: [第1章 绪论, 1.1 研究背景, 背景内容...]
j=4 (section "目标内容...")
→ 从 Level 2 找到 1.2 (index=3)
→ 从 Level 0 找到 第1章 (index=0)
→ 链: [第1章 绪论, 1.2 研究目标, 目标内容...]
j=7 (section "实验内容...")
→ 从 Level 2 找到 2.1 (index=6)
→ 从 Level 0 找到 第2章 (index=5)
→ 链: [第2章 方法, 2.1 实验设计, 实验内容...]
二次合并:token 上限 218
构建完层级链后,还有一个二次合并步骤。如果单条链(通常是纯正文段落)的 token 数 < 218,会被拼到前一个 chunk 里,可能跨越标题边界。
路径 B:无编号模式 → naive_merge
退化为顺序拼接,不考虑任何标题结构,纯粹按 chunk_token_num 配置的 token 数切分。
三、Book 策略中"标题"的真实角色
核心结论:标题是上下文,不是边界
Book 策略确实检测标题、使用标题 ,但标题在其中的角色是补充上下文 ,不是切分边界。
理想中的"按标题分块"
┌─ 第1章 绪论 ──────────┐ ← 在这里切开
│ 1.1 背景 │
│ 背景内容... │
│ 1.2 目标 │
│ 目标内容... │
└────────────────────────┘ ← 在这里切开
┌─ 第2章 方法 ──────────┐
│ 2.1 实验设计 │
│ 实验内容... │
└────────────────────────┘
Book 实际的分块
┌─ [第1章 绪论 · 1.1 研究背景 · 背景内容...] ──┐ ← 以叶节点为单位
└───────────────────────────────────────────────┘
┌─ [第1章 绪论 · 1.2 研究目标 · 目标内容...] ──┐ ← 同一章被拆成多个 chunk
└───────────────────────────────────────────────┘
┌─ [第2章 方法 · 2.1 实验设计 · 实验内容...] ──┐
└───────────────────────────────────────────────┘
| 维度 | 按标题分块 | Book 实际行为 |
|---|---|---|
| 分块单位 | 一级标题下的所有内容 | 每个叶节点段落 |
| 标题的角色 | 切分锚点(边界) | 附加上下文(前缀) |
| 同一章的内容 | 在同一个 chunk 里 | 被拆成多个 chunk |
| chunk 粒度 | 粗(章级别) | 细(段落级别) |
Book 的 hierarchical_merge 做的是:给每个正文段落挂上它的祖先标题链 作为上下文,但切分点不在标题处,而在每个正文段落处。
四、Book 策略 vs 直接按段落切分的本质差异
具体对比
假设一份文档:
第1章 绪论
1.1 研究背景
深度学习在自然语言处理领域取得了显著进展。
1.2 研究目标
本文旨在提出一种新的注意力机制。
第2章 相关工作
2.1 Transformer架构
Vaswani等人提出的Transformer架构改变了NLP的范式。
2.2 预训练模型
BERT通过双向编码器实现了突破性效果。
直接按段落切(naive 的做法)
| Chunk # | 内容 |
|---|---|
| 1 | 深度学习在自然语言处理领域取得了显著进展。 |
| 2 | 本文旨在提出一种新的注意力机制。 |
| 3 | Vaswani等人提出的Transformer架构改变了NLP的范式。 |
| 4 | BERT通过双向编码器实现了突破性效果。 |
Book 的做法(段落 + 祖先标题链)
| Chunk # | 内容 |
|---|---|
| 1 | 第1章 绪论 · 1.1 研究背景 · 深度学习在自然语言处理领域取得了显著进展。 |
| 2 | 第1章 绪论 · 1.2 研究目标 · 本文旨在提出一种新的注意力机制。 |
| 3 | 第2章 相关工作 · 2.1 Transformer架构 · Vaswani等人提出的Transformer架构改变了NLP的范式。 |
| 4 | 第2章 相关工作 · 2.2 预训练模型 · BERT通过双向编码器实现了突破性效果。 |
差异体现在 RAG 检索环节
RAG 的流程是:用户提问 → 检索相关 chunk → 喂给 LLM 生成回答。差异就在检索环节。
场景 1:用户问 "绪论部分的研究背景是什么?"
直接按段落切 --- 检索命中的 chunk:
❌ "深度学习在自然语言处理领域取得了显著进展。"
这个 chunk 里**没有"绪论"、没有"研究背景"**这些关键词。向量检索和全文检索都会困难------因为"深度学习"、"自然语言处理"跟用户问题的语义距离很远。
Book 的做法 --- 检索命中的 chunk:
✅ "第1章 绪论 · 1.1 研究背景 · 深度学习在自然语言处理领域取得了显著进展。"
这个 chunk 里包含"绪论"、"研究背景",跟用户问题高度匹配。检索命中率大幅提升。
场景 2:用户问 "Transformer 相关的工作在哪一章?"
直接按段落切:
❌ "Vaswani等人提出的Transformer架构改变了NLP的范式。"
没有章节信息,LLM 拿到这个 chunk 后不知道它属于哪一章,无法回答"在哪一章"。
Book 的做法:
✅ "第2章 相关工作 · 2.1 Transformer架构 · Vaswani等人提出的..."
LLM 能看到 "第2章 相关工作",能直接回答。
总结
┌─────────────────────────────────────────────────────┐
│ │
│ 直接按段落切:每个 chunk 只有 "WHAT"(内容) │
│ │
│ Book 策略: 每个 chunk 有 "WHERE" + "WHAT" │
│ (位置上下文 + 内容) │
│ │
│ 标题链的作用:给每个段落 chunk 带上它所在的 │
│ "章节路径",解决检索时 "这段话属于哪、 │
│ 在什么上下文中" 的问题 │
│ │
└─────────────────────────────────────────────────────┘
Book 策略的核心价值不是"怎么切",而是"切完之后每个 chunk 携带什么上下文"。 它把层级标题作为前缀注入每个 chunk,提升检索命中率。
五、Paper 策略与 Book 策略的对比
Paper 策略同样利用标题信息,但机制不同:
| 维度 | Paper | Book |
|---|---|---|
| 标题检测 | 依赖 PDF 布局分析(layoutno 含 "title")+ BULLET_PATTERN |
同左 + 额外的 make_colon_as_title(冒号行提升为标题) |
| 分块依据 | "出现频率最高的标题级别"作为切分锚点 | 完整的层级树构建,自底向上合并 |
| 层级深度 | 扁平------只看 most_level 这一层 | depth=5,最多支持 5 级嵌套 |
| 无标题退化 | 不退化,强制用 title_frequency |
退化到 naive_merge(纯按 token 数) |
| 特殊处理 | 摘要独占 chunk;双栏布局检测 | 目录页移除(remove_contents_table) |
六、关键源码索引
| 模块 | 文件路径 |
|---|---|
| Book 策略入口 | rag/app/book.py |
| Paper 策略入口 | rag/app/paper.py |
| Laws 策略入口 | rag/app/laws.py |
编号模式定义 BULLET_PATTERN |
rag/nlp/__init__.py:169-201 |
编号模式检测 bullets_category |
rag/nlp/__init__.py:216-233 |
层级合并 hierarchical_merge |
rag/nlp/__init__.py:980-1067 |
树形合并 tree_merge |
rag/nlp/__init__.py:931-978 |
标题频率 title_frequency |
rag/nlp/__init__.py:901-920 |
冒号标题提升 make_colon_as_title |
rag/nlp/__init__.py:879-898 |
顺序合并 naive_merge |
rag/nlp/__init__.py:1070-1126 |
策略枚举 ParserType |
common/constants.py:106-122 |
工厂分发 FACTORY |
rag/svr/task_executor.py:114-131 |