一、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"等(由布局分析引擎输出)。
第二步:预处理 — 冒号标题提升
make_colon_as_title(sections)# book.py:163遍历所有 section,如果一行文本以:或:结尾,且冒号后面有足够多的内容(≥32 字符),就把冒号前面的部分插入为一个"title"类型的 section。
例子:
之前: [("绪论:本文主要研究...", "text")] 之后: [("绪论", "title"), ("本文主要研究...", "text")]第三步:检测编号模式
bull=bullets_category([tfort,_insections])# book.py:164bullets_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 |