Lazy loaded image
Agent丨RAG 系统中的文档分块策略如何设计?chunk 大小、父子分块和不同文档格式要怎么取舍?
Words 7035Read Time 18 min
2026-4-15
type
Post
status
Published
date
Apr 15, 2026
slug
agent9
summary
tags
Agent
category
icon
password
核心结论:RAG 分块不是“把文本切小一点”这么简单,而是在 检索精度、上下文完整性、索引成本、引用可追溯性、文档结构保真度 之间做工程权衡。
最稳妥的默认路线是:先按文档结构解析,再按语义边界切块,最后用父子分块或窗口召回补上下文

1. 为什么 chunk 设计会直接决定 RAG 上限?

在一个典型 RAG 系统中,文档会经历:
很多 RAG 问题并不是模型能力不够,而是 chunk 设计把信息破坏了:
问题 1:chunk 太小,检索很准,但回答时缺少上下文。
问题 2:chunk 太大,包含上下文,但 embedding 被过多主题稀释,召回不准。
问题 3:表格、标题、代码块、图片说明被切散,导致答案有片段但没有语义。
问题 4:PDF 解析顺序错了,双栏文档、页眉页脚、目录、脚注混进正文,chunk 再合理也没用。
这也是为什么成熟项目都在从“简单长度切分”走向“结构感知 + 层级召回”。LangChain 文档建议多数场景先从 RecursiveCharacterTextSplitter 开始,因为它会优先保持段落等较大语义单元,超过大小限制后再退到句子、词等更细粒度;LangChain 同时提供 Markdown、HTML、JSON、代码等结构化 splitter。(LangChain 文档)

2. 先看成熟 RAG 项目的分块设计

项目 / 平台
分块核心思想
适合场景
关键取舍
LangChain
递归字符切分、结构化 splitter、ParentDocumentRetriever
通用 RAG、快速工程落地
简单稳定,但复杂文档需要额外 parser
LlamaIndex
NodeParser、SentenceSplitter、HierarchicalNodeParser、AutoMergingRetriever
长文档、技术文档、研究论文
节点关系丰富,适合做层级召回
Haystack
DocumentSplitter 支持 word / sentence / passage / page / line / function
生产 pipeline、可观测索引流程
参数清晰,适合企业工程化
Dify
General / Parent-child / Q&A 三种 chunk mode
应用平台、知识库搭建
父子模式易用,但知识库创建后结构模式不可随意变更
RAGFlow
深度文档理解、模板化 chunk、复杂格式解析
PDF、扫描件、表格、企业知识库
parser 很重,但适合复杂非结构化文档
Unstructured
先 partition 成文档元素,再按元素组合成 chunk
PDF、HTML、Office、多格式 ETL
强调结构元素保真,而不是裸文本切分
Amazon Bedrock KB
fixed / no chunking / hierarchical / semantic chunking
云上托管知识库
标准化能力强,适合托管型 RAG
Haystack 的 DocumentSplitter 明确支持 split_by="word" | "sentence" | "passage" | "page" | "line" | "function",并用 split_lengthsplit_overlap 控制大小和重叠,还会保留 source_idpage_number 等元数据,方便回溯原文。(docs.haystack.deepset.ai) Dify 的知识库管道支持 General、Parent-child、Q&A 三种 chunk 结构,并建议首次创建知识库时优先考虑 Parent-child 模式;其 Parent-child 模式使用 child chunk 做匹配、parent chunk 提供上下文。(Dify 文档) RAGFlow 则更强调“深度文档理解”,包括复杂格式的非结构化数据抽取、模板化 chunk、文本分块可视化与可追溯引用。(GitHub)

3. chunk 大小到底怎么选?

3.1 先不要问“多大最好”,先问“检索单元是什么”

chunk size 的本质不是一个固定数字,而是:
一个 chunk 应该刚好承载“用户问题能命中的最小可回答语义单元”。
不同问答类型需要不同粒度:
问答类型
推荐 child chunk
推荐 parent chunk
原因
FAQ、术语解释、政策条款
128-300 tokens
300-800 tokens
问题短、答案集中,过大反而干扰
技术文档、API 文档
200-500 tokens
800-1500 tokens
需要命中函数/参数,也要保留上下文
研究论文、行业报告
300-800 tokens
1500-3000 tokens
答案常跨段落、跨小节
法律合同、制度文档
200-600 tokens
按条款/章节
引用必须精确,不能把条款切断
表格/财报/Excel
行组、表格块、sheet 区域
表标题 + 解释段 + 表格
单纯 token 长度不够,要保留表头和单位
代码库
函数/类/文件片段
类/模块/文件
检索命中函数,但回答需要调用关系
2025 年一篇多数据集分析指出,不同数据集和 embedding 模型对 chunk size 的敏感性不同:事实型短答案更偏向较小 chunk,而需要更宽上下文的问题更适合较大 chunk;这说明 chunk size 不应靠经验一次拍死,而应该结合任务集评测。(arXiv)

3.2 chunk_size、chunk_overlap、top_k 是联动参数

很多人只调 chunk_size,但真实效果由三个参数一起决定:
小 chunk + 大 top_k:适合事实检索,但 rerank 压力大。
大 chunk + 小 top_k:适合报告总结,但细粒度命中差。
小 child + 大 parent:适合大多数企业知识库,是当前最通用的折中。
经验上可以从下面三组配置起步:
场景
child chunk
parent chunk
overlap
普通知识库
300-500 tokens
1000-1500 tokens
child 10%-15%
技术/代码文档
200-400 tokens
800-1200 tokens
按函数/标题边界优先
长报告/论文
500-800 tokens
2000-3000 tokens
100-200 tokens
LangChain 的 RecursiveCharacterTextSplitter 中,chunk_size 是每个 chunk 的最大大小,大小由 length_function 决定;chunk_overlap 用于减少上下文被边界切断带来的信息损失。(LangChain 文档)

4. 固定长度、递归分块、语义分块怎么取舍?

4.1 固定长度分块:简单,但最容易破坏结构

固定长度切分类似:
适合:
纯文本、日志、短文档、结构不重要的批处理。
不适合:
法律条款、Markdown、HTML、代码、表格、PDF 报告。
因为它不知道标题、段落、表头、函数边界是什么。

4.2 递归分块:工程默认解

LangChain 的递归思路可以简化成:
这和 LangChain 文档中的描述一致:优先保留段落等较大单元,超过限制后再退到更细粒度;源码中 split_text() 会把文本交给 _split_text(text, self._separators) 递归处理,from_language() 还能按编程语言选择特定 separator。(LangChain 文档)
中文、日文、泰文等没有空格分词边界的语言,不能直接依赖英文默认 separator。LangChain 文档建议为这类语言补充分隔符,例如中文/日文句号、全角逗号、顿号、零宽空格等,以减少词语或短语被切断。(LangChain 文档)

4.3 语义分块:更智能,但成本更高

语义分块不是按字符,而是按句子 embedding 的语义变化找断点:
伪代码如下:
LlamaIndex 的 SemanticSplitterNodeParser 就是把文档切成语义相关的句子组,并通过 buffer_sizebreakpoint_percentile_threshold 等参数控制断点灵敏度;阈值越低,通常会产生更多节点。(Developer Documentation) Amazon Bedrock Knowledge Bases 也提供 semantic chunking,其中可配置最大 tokens 和 buffer size,buffer 会把当前句附近的句子一起用于边界判断。(AWS 文档)
语义分块适合:长报告、论文、知识文章、客服知识库。
不适合:高吞吐低成本场景、强结构文档、表格、代码。
原因:语义分块需要额外 embedding 或模型判断,成本高;而代码/表格的边界不是自然语言语义边界。

5. 父子分块:为什么是企业 RAG 的常用折中?

5.1 父子分块解决的核心矛盾

child chunk 用来“找得准”。
parent chunk 用来“答得全”。
这比单纯调大 chunk_size 更稳。
Amazon Bedrock 对 hierarchical chunking 的定义也是:组织 parent / child 嵌套结构,检索时先命中 child,再替换为更大的 parent,以平衡小 embedding 的精确性和回答时所需的完整上下文。(AWS 文档) Dify 的 Parent-child 模式同样是较小 child chunks 用于匹配,较大 parent chunks 用于作为检索结果返回,从而平衡“小块精准但缺上下文”和“大块上下文丰富但召回精度低”的矛盾。(Dify 文档)

5.2 LangChain / LlamaIndex 的父子思想对比

LangChain 的 ParentDocumentRetriever 一般是:
LlamaIndex 的层级节点更像多层树:
LlamaIndex 文档显示,HierarchicalNodeParser 会输出从粗到细的节点层级,例如默认层级为 2048、512、128,并且 leaf nodes 会进入向量索引,其他 nodes 放入 docstore;AutoMergingRetriever 可以在命中多个子节点时自动替换/合并为父节点,为 LLM 提供更完整上下文。(Developer Documentation) 其源码注释也说明 HierarchicalNodeParser 会把文档切成递归层级节点,返回结果中包含 parent 大块与 child 小块之间的重叠和父子关系。(GitHub)

5.3 父子分块的工程存储模型

推荐的元数据结构:
向量库中存 child:
docstore 中存 parent:
检索伪代码:
实战建议:不要只取 child 命中的第一个 parent。
更稳的做法是对 parent 聚合打分:一个 parent 下多个 child 同时命中时,应提高该 parent 的优先级。

6. 不同文档格式应该怎么切?

6.1 Markdown:优先按标题层级切

Markdown 的天然结构是标题树:
推荐策略:
parent = 一个二级或三级标题下的完整章节。
child = 章节内的段落、列表、代码块说明。
metadata = header path,例如 产品手册/权限/RBAC
LlamaIndex 的 MarkdownNodeParser 会基于 Markdown header 切分,每个 node 包含文本内容以及通向该内容的 header path;它还提供 MarkdownElementNodeParser,可把 Markdown 中的表格等嵌入对象拆成 Text Nodes 和 Index Nodes。(Developer Documentation)
示例实现:

6.2 HTML / Web 文档:按 DOM 结构切,不要直接 strip 成纯文本

HTML 文档里,正文、导航、侧边栏、footer、代码块、表格都混在 DOM 中。推荐流程:
LlamaIndex 提供 HTML node parser,可按自定义 HTML 逻辑拆分节点;LangChain 也有 HTMLHeaderTextSplitter、HTMLSectionSplitter、HTMLSemanticPreservingSplitter 等 HTML splitter。(Developer Documentation)
取舍:
如果是帮助中心、文档站:按 h1/h2/h3 切。
如果是网页文章:先去 boilerplate,再按段落/小标题切。
如果是 API reference:按 endpoint、method、参数表切。

6.3 PDF:先解决解析,再谈 chunk

PDF 是 RAG 最容易翻车的格式,因为 PDF 不是语义文档,而是版式文档。常见问题:
双栏顺序错乱。
页眉页脚混入正文。
表格被拆成多行文本。
图片、公式、脚注丢失。
扫描件需要 OCR。
标题层级无法直接识别。
RAGFlow 的 parser 文档专门提供了多栏检测,用于把双栏或报纸式页面重排为逻辑顺序;其 PDF parser 中的 DeepDoc 默认会执行 OCR、TSR 表格结构识别、DLR 文档布局识别,适合复杂 PDF,但会更耗时。(RAGFlow)
PDF 推荐策略:
PDF 类型
parser 策略
chunk 策略
纯文本 PDF
PyMuPDF / pypdf 类解析
按页 + 标题 + 段落递归切
扫描 PDF
OCR parser
按版面块 + 段落切
双栏论文
layout-aware parser
按 section / subsection 切
财报 PDF
表格识别 + 文本块
表格独立 chunk,并挂接标题和说明
法律合同 PDF
条款识别
按条款/章节作为 parent,条款内句子作为 child
一个更工程化的 PDF chunk 数据结构:

6.4 表格:不要把表格当普通文本切

表格 chunk 的关键不是长度,而是:
表头、单位、caption、上一段解释、下一段结论必须和表格建立关系。
错误切法:
如果没有标题和单位,LLM 不知道这是什么数字。
推荐切法:
LlamaIndex 的 MarkdownElementNodeParserUnstructuredElementNodeParserLlamaParseJsonNodeParser 都体现了一个趋势:把表格等 embedded objects 当成特殊节点,而不是简单并入普通文本。(Developer Documentation) Unstructured 的 chunking 也强调,先通过 partition 把文档解析成语义元素,再组合元素为 chunk;只有单个元素超过最大长度时才切开该元素,这比裸文本切分更能保留语义单元。(docs.unstructured.io)

6.5 Word / DOCX:按标题样式和段落结构切

DOCX 往往比 PDF 更容易解析结构,因为有标题样式、段落、表格、列表。
推荐:
Heading 1 / Heading 2 / Heading 3 形成 section path。
表格单独抽取,并挂到最近标题。
页眉页脚、目录、修订记录、批注按业务决定是否保留。
parent 按章节,child 按段落/列表项/表格说明。

6.6 Excel / CSV:按 sheet、区域、业务实体切

Excel 不应该简单“每 N 行切一块”。更好的方式是:
数据形态
推荐 chunk
交易流水
按时间窗口、客户、订单批次
财务报表
按 sheet + 表格区域
产品清单
每个产品或产品组
指标宽表
按指标类别 + 时间范围
FAQ CSV
question 做索引,answer 做返回
Dify 的 Q&A Mode 就适合结构化问答数据:它将问题和答案配对,并基于 question 部分建立索引,用查询相似度召回答案。(Dify 文档)

6.7 代码文档:按函数、类、模块切

代码 chunk 不应按字符硬切,而应按 AST 或语言分隔符切:
推荐:
child = 函数签名 + docstring + 关键 body。
parent = class 或 module。
metadata = repo、branch、file_path、symbol、language、imports。
LangChain 文档明确把代码列为 document structure-based splitting 的一种,适合按 functions、classes 或 logical blocks 切。(LangChain 文档) Haystack 的 DocumentSplitter 也支持 split_by="function",适合代码或函数级分块场景。(docs.haystack.deepset.ai)

7. 一个可落地的 Chunker Router 设计

真正的生产系统不要一个 splitter 打天下,而应该按格式路由:
示例代码:

8. 父子分块并不是总是最好

父子分块适合大多数复杂知识库,但不是银弹。

8.1 适合父子分块的场景

技术手册、产品文档、制度规范、论文、长报告、法律合同、FAQ + 解释混合文档。
这些场景中,问题通常命中一个细节,但回答需要同一章节的上下文。

8.2 不适合父子分块的场景

极短 FAQ:问题和答案本来就是完整单元。
大量结构化 Q&A:直接用 question 做索引更好。
表格问答:应使用表格结构检索、SQL、DataFrame agent 或 table-aware chunk。
超小文档:parent = full doc 会导致返回上下文过大。
强权限隔离文档:parent 过大可能把用户无权访问的内容带出来。
Dify 文档也提醒,Parent-child 模式中 Full Doc 会把整个文档作为 parent,适合小而连贯的文档;但 Full Doc 模式只处理前 10,000 tokens,超出内容会被截断,并且 parent chunk 创建后不能直接编辑。(Dify 文档)

9. 高质量 chunk 应该满足哪些标准?

一个好 chunk 至少满足 6 个条件:
自包含:离开上下文后仍能大致理解。可引用:能定位到原文页码、标题、段落或表格区域。不混主题:一个 chunk 不要包含多个明显无关主题。不断实体:标题、表头、单位、代码签名、条款编号不能丢。可聚合:child 能映射回 parent、section、doc。可评测:chunk 参数能通过召回率、MRR、nDCG、答案准确率对比。
建议在元数据中至少保留:
Haystack 的源码和文档都体现了这个思路:切分后不仅返回文本,还要在 metadata 里保留 source_idpage_number,其他元数据也从原文档复制过来。(GitHub)

10. 一个推荐的生产级默认方案

对于大多数企业知识库,我建议默认这样设计:

默认参数

参数
推荐起点
child_size
300-500 tokens
child_overlap
40-80 tokens
parent_size
1000-1800 tokens
parent_overlap
100-200 tokens
top_k_child
20-50
top_k_parent
3-8
rerank
建议开启
metadata filter
强烈建议支持
citation
必须精确到 page / section / block

默认策略

Markdown / HTML:按标题结构生成 parent,再递归生成 child。
PDF:先 layout-aware parse,再按标题/页面/版面块生成 parent。
表格:表格独立 chunk,但必须绑定 caption、单位、上下文。
代码:函数是 child,类/文件是 parent。
FAQ:Q&A mode,不必父子分块。
长报告:层级 chunk + parent-child retrieval + rerank。
多语言文档:补充分隔符,避免中文/日文被英文空格逻辑切碎。

11. 分块策略如何评测?

不要只看“切得好不好看”,要看检索和回答效果。

11.1 离线评测集

构造一批问题:

11.2 检索指标

指标
说明
Recall@k
正确 chunk 是否出现在前 k 个结果
MRR
正确结果排名是否靠前
nDCG
多个相关 chunk 的排序质量
Parent Hit Rate
child 命中后,正确 parent 是否被返回
Citation Accuracy
引用页码/章节/表格是否正确

11.3 生成指标

指标
说明
Answer Correctness
答案是否正确
Faithfulness
是否严格基于检索上下文
Context Precision
塞给 LLM 的上下文是否有用
Context Recall
必要证据是否都被召回
Latency / Cost
embedding、检索、rerank、LLM 成本
最小实验矩阵:
方案 A:固定 500 tokens。
方案 B:递归 500 tokens + overlap 80。
方案 C:child 350 + parent 1200。
方案 D:结构化 parser + parent-child + rerank。
用同一批问题比较 Recall@k、答案准确率和平均上下文 tokens。

12. 常见错误与修正

错误 1:直接按 1000 字符切所有文档

修正:
文本可以递归切,Markdown 按标题切,HTML 按 DOM 切,PDF 先 layout parse,表格按结构切,代码按函数切。

错误 2:chunk 越大越好

修正:
大 chunk 会让 embedding 表示多个主题,召回变钝。
更好的做法是小 child 检索,大 parent 返回。

错误 3:overlap 设太大

修正:
overlap 太大会造成索引膨胀、重复召回、rerank 噪声。
一般先从 10%-15% 试起,再用评测集调。

错误 4:PDF 不做版面解析

修正:
PDF 的第一步是 parser 质量,不是 chunk size。
双栏、表格、扫描件必须使用 layout-aware / OCR / table recognition。

错误 5:表格和标题分离

修正:
表格 chunk 必须带 caption、单位、section path、前后解释段。

错误 6:没有 chunker_version

修正:
chunk 策略一变,索引就应该可追踪、可回滚、可灰度。
元数据中保留 parser_version、chunker_version、embedding_model_version。

13. 最终决策树


14. 一句话总结

RAG 分块的最佳实践不是“固定 500 tokens”,而是:
按文档格式解析结构,按语义边界生成 child,按章节/页面/逻辑单元生成 parent,用 child 做精准检索,用 parent 提供完整上下文,再通过评测集持续调参。
如果只想要一个默认方案:
child_size = 300-500 tokens
parent_size = 1000-1800 tokens
overlap = 10%-15%
Markdown/HTML/DOCX 按标题切
PDF 先做 layout-aware parse
表格独立 chunk 并绑定 caption/单位/上下文
代码按函数/类切
所有 chunk 必须保留 source_id、page、section_path、parent_id
回到首页