按字数、按段落、按标题、按语义、父子分块、混合分块,再加上参数推荐、结果预览、版本管理。我想解决的不是“把文档切开”,而是“怎么把内容切成更适合检索和生成的知识块”。
上一篇我写了文档加载模块,核心观点是:RAG 的第一步不是 Embedding,而是高质量文档解析。
这一篇继续往下走,聊第二个特别容易被低估、但实际影响非常大的环节:文档分块。
很多 RAG 项目在分块这一步都很直接,甚至可以说有点粗暴:
- 设一个
chunk_size - 再加一个
overlap - 然后开始切
这样当然能跑,但问题也非常明显:能跑不等于切得对。
因为分块这件事,表面看是在“切文本”,本质上做的是一件更关键的事:
决定什么内容会一起进入检索,什么上下文会一起进入生成。
如果这一层切得不好,后面再好的 Embedding、再复杂的 Hybrid Retrieval、再强的 LLM,也很难把结果救回来。
项目地址:
- GitHub:
https://github.com/qingni/rag-pipeline-hub
为什么说分块质量会直接决定检索质量
RAG 里很多问题,最后看起来像“检索不准”,但往前追一层,根因其实常常出在分块。
比如下面这些情况,你应该都见过:
- 一个定义在上半段,解释在下半段,结果被切开了
- 一个章节很完整,但被机械地切成很多碎片
- 一个段落很短,本来应该和上下文一起看,却被独立当成一个 chunk
- 一个长章节直接整个进库,结果召回虽然命中了,但上下文太大太散
- 文档里有表格、代码、图片说明,但被当作普通文本粗暴拼接
这些问题最终会直接影响下游几个核心指标:
- 召回命中率
- 召回覆盖率
- 上下文完整性
- 冗余率
- 最终回答稳定性
所以我越来越觉得,分块不是“预处理步骤”,而是检索质量治理的第一现场。
为什么 chunk_size 不够
很多教程讲分块,最后都会落到两个参数:
chunk_sizeoverlap
这两个参数当然重要,但它们解决的是“块有多大”和“块之间有多少重叠”,并不能解决下面这些更根本的问题:
- 文档有没有明显的段落结构
- 文档有没有标题层级
- 文档是不是多主题切换
- 文档里有没有代码、表格、图片
- 这个场景更需要精准命中,还是更需要保留完整上下文
- 这个系统能不能接受 embedding 带来的额外成本
换句话说,chunk_size 只是参数,不是策略。
如果只有一个按长度切分的方案,那本质上你还是在用同一把尺子处理所有文档。
而真实文档的结构差异,远远比很多人一开始想象得更大。
这也是为什么我在 RAG Pipeline Hub 里没有停留在单一切分方式,而是把分块模块做成了一个“多策略 + 推荐 + 预览 + 历史管理”的完整工作台。
我为什么要把分块模块单独做成一个工作台
我对这个模块的定义不是“把文本切开”,而是:
把标准化文档结果转换成可检索、可引用、可追溯的块级内容。
这里有几个关键词很关键。
1. 可检索
切出来的块不是为了好看,而是为了后续能被向量化、能被检索命中。
2. 可引用
后面的生成模块不仅要回答,还要能给出来源。
这意味着 chunk 不能只是随机切开的文本片段,它得保留足够的上下文边界和结构线索。
3. 可追溯
出了问题之后,你得知道这个 chunk 是从哪段原文来的,用的是什么策略,参数是什么,有没有版本差异。
所以在这个项目里,分块模块除了算法本身,还配了:
- 策略选择
- 参数配置
- 结果预览
- 可视化展示
- 历史记录
- 版本管理
- 智能推荐
因为我越来越确信:
如果没有“观察和比较”的能力,分块策略就很难真正调优。
当前支持的 6 种分块策略
这个项目里目前支持 6 种核心分块策略:
- 按字数分块
- 按段落分块
- 按标题分块
- 按语义分块
- 父子分块
- 混合分块
我没有把它们理解成“功能列表”,而是把它们理解成一组面对不同文档问题的工具箱。
下面逐个说一下。
1. 按字数分块:最简单,但不是万能
按字数分块是最容易理解、也最稳定的方案。
它的优点很明显:
- 快
- 可预测
- 参数简单
- 对纯文本很友好
如果你处理的是:
- 结构不明显的连续文本
- 对块大小有比较严格的控制需求
- 追求处理速度
那按字数分块通常是一个不错的起点。
但它的问题也很明显:
- 可能切断句子
- 可能破坏语义连续性
- 对章节、段落、列表等天然结构不敏感
所以我把它视为“基础兜底策略”,而不是默认最优策略。
2. 按段落分块:很多文档的默认优解
按段落分块在我看来,是一个经常被低估的方案。
因为很多业务文档天然就是按段落组织的。
这时候如果还用固定字数硬切,往往是在主动破坏原始结构。
按段落分块更适合:
- 说明文档
- 技术文章
- 一般性业务文档
- 有清晰段落边界的文本
它的核心优势是:
- 保持段落完整性
- 更符合人类写作结构
- 比纯字数分块更容易解释
当然它也不是没有问题:
- 块大小不一定均匀
- 有些段落可能过长
- 有些短段落可能需要合并
所以在工程实现里,它通常要结合 min_chunk_size 和 max_chunk_size 一起控制,而不是简单按空行直接切。
3. 按标题分块:最适合结构化文档
如果文档有清晰的标题体系,比如:
- Markdown 文档
- 技术设计文档
- 章节化的说明文
- 层级结构明显的手册
那按标题分块往往是最自然的选择。
因为这种策略会尽量保留文档原本的章节边界,让每个 chunk 都带有更明确的语义范围。
它特别适合解决一个问题:
有些内容不是靠相邻句子形成语义,而是靠“属于同一节”形成上下文。
比如某一章专门讲配置、另一章讲限制条件、再后一章讲异常处理。
如果只按字数切,很容易把这些内容搅在一起;但按标题切,块的语义边界会清晰得多。
当然,这种策略依赖文档本身的结构质量。
标题不规范、层级不稳定、原始格式不完整时,效果就会打折。
4. 按语义分块:当你真的在乎“语义连续性”
如果说前面三种策略更偏结构驱动,那按语义分块就是典型的内容驱动。
它更关注的问题是:
- 主题有没有发生变化
- 相邻段落是不是还在讲同一件事
- 某一块内容是否应该一起保留
这个项目里的语义分块支持两种思路:
- 轻量方式:基于 TF-IDF 等相似度做切分
- 增强方式:结合 Embedding 模型做语义相似度判断
它适合的场景通常是:
- 语义要求高
- 主题切换频繁
- 文档结构不一定稳定
- 更关注召回质量而不是极致性能
但它的代价也很明确:
- 处理更慢
- 成本更高
- 更依赖模型能力
- 参数调优更复杂
所以我不会把语义分块当成“默认高级解”,而是把它当成一类高质量但更依赖工程配套的策略。
5. 父子分块:为“命中更准 + 上下文更完整”而设计
父子分块是这个模块里我特别看重的一种策略。
它解决的是一个非常典型的矛盾:
- 如果 chunk 太小,检索命中会更精确,但上下文不够完整
- 如果 chunk 太大,上下文更完整,但召回粒度又会变粗
父子分块的思路就是把这两个目标拆开:
- 父块负责保留更完整的上下文
- 子块负责提高检索精度
这在长文档场景下特别有价值,尤其适合:
- 技术手册
- 长篇制度文档
- 章节较长的知识库内容
- 希望“命中精准,但返回时上下文也完整”的 RAG 场景
它当然也会带来额外成本:
- 数据结构更复杂
- 存储会增加
- 索引组织也要配合
但如果你的目标不是“先跑通”,而是“把效果做稳”,父子分块是非常值得投入的一种设计。
6. 混合分块:因为文档不只有正文
这是我觉得很多 RAG 系统最容易被简化过头的一点。
现实里的文档,经常不只是连续自然语言,还可能包含:
- 代码块
- 表格
- 图片
- 配置片段
- 结构化列表
如果把这些内容全部按同一策略处理,通常效果不会太好。
比如:
- 代码更适合按行或逻辑块处理
- 表格更适合独立保留
- 图片需要配合上下文信息处理
- 正文部分更适合语义或段落切分
所以混合分块的思路是:
针对不同内容类型,用不同策略处理。
这也是为什么我把它视为一种更贴近真实业务文档的策略。
特别是技术文档、教程类内容、报告类内容,多数时候都比纯文本复杂得多。
为什么要做智能推荐,而不是让用户自己选
如果只从工程实现角度看,支持 6 种策略已经不少了。
但如果从用户体验看,问题马上就来了:
用户怎么知道该选哪一种?
这也是我决定加“智能推荐”的原因。
在这个项目里,推荐逻辑主要会结合三类信号:
1. 文档结构信号
- 有没有标题结构
- 段落是否明显
- 是否包含大量代码、表格、图片
2. 内容语义信号
- 主题是否频繁切换
- 是否属于术语密集、学术性或技术性内容
3. 运行约束
- 是否允许调用 embedding 服务
- 是否更关注吞吐和速度
- 是否需要多模态处理
基于这些信号,系统会给出:
- 推荐主策略
- 是否启用 embedding 增强
- 是否启用父子分块
- 是否启用混合分块
- 参数建议,比如
chunk_size、overlap、阈值等
这件事的价值,不只是“更智能”,而是:
把策略选择从拍脑袋,变成有依据的推荐。
参数推荐为什么也很重要
很多时候,即便选对了策略,参数没调对,效果还是会出问题。
比如:
chunk_size太小,召回碎片化chunk_size太大,命中不精准overlap太小,定义和解释容易被切开overlap太大,冗余显著增加
所以推荐系统不仅要给策略,还要给参数建议。
在这个项目里,参数推荐会结合一些经验规则,例如:
- 默认
chunk_size建议落在500-1000 overlap常见建议是chunk_size的10%-20%- 如果经常跨段引用,可以进一步提高 overlap
- 语义分块阈值会根据 TF-IDF 或 embedding 方式采用不同起点
你可以把它理解成一种“工程经验前置”。
它不一定一步到位,但能显著减少用户在初次试用时的试错成本。
为什么分块结果一定要预览、可视化、可比较
我对分块模块的另一个坚持是:
不能只给用户一个“执行成功”,而要让用户看见到底切成了什么。
原因非常简单。
如果没有预览能力,你很难回答下面这些问题:
- 这个策略到底切成了多少块
- 块大小分布是否合理
- 有没有明显切断句子或章节
- 父子分块的层级关系是否正常
- 混合分块有没有把表格和代码处理好
- 换一种策略后,结果到底好在哪
所以这个模块里配了:
- 分块预览
- 结果列表
- 块详情
- 线性可视化
- 树形可视化
- 混合可视化
- 统计图表
尤其是父子分块和混合分块,没有可视化其实很难真正理解效果。
这也是我为什么把“可视化展示”当作分块模块的重要组成部分,而不是一个可有可无的锦上添花。
历史记录和版本管理为什么是工程必需品
如果你认真调过分块,就会很快发现一个现实问题:
分块不是一次性动作,而是一个持续试验和比较的过程。
你会不断去调整:
- 策略
- 参数
- overlap
- 是否启用 embedding
- 是否启用父子结构
- 是否启用混合分块
如果每次调整都没有历史记录、没有版本概念,那你很快就会失去对结果的控制。
所以这个项目在分块模块里专门保留了:
- 历史记录
- 结果管理
- 版本控制
- 多版本并存或智能覆盖
这样做的意义很大:
- 你可以回看某次配置到底生成了什么
- 你可以比较不同策略的实际差异
- 你可以把更好的结果稳定传给后续向量化模块
- 你可以形成真正可复盘的质量优化闭环
这也是我一直强调的一个点:
工程化 RAG 不是把链路打通,而是让每一步都能被比较、被调优、被复用。
分块结果最后如何进入向量化模块
这个问题看起来简单,其实很关键。
文档加载模块解决的是“把原始文档标准化”。
分块模块解决的是“把标准化文档转换成可向量化的块级资产”。
也就是说,分块模块最终输出的不只是文本数组,而是更完整的结构化结果,比如:
- chunk 列表
- 块类型
- 父子关系
- 分块策略信息
- 参数配置
- 统计信息
这些结果会被后续向量化模块直接消费。
所以分块质量不只是影响检索,还影响向量化输入的稳定性和可解释性。
这也是为什么我一直不把它看成“预处理小步骤”,而是把它看成内容处理链路里的关键桥梁。
我对分块模块的一个核心判断
如果只让我总结一句话,我会说:
Chunk 不只是 chunk_size,分块也不是把文本切开,而是在为后续检索和生成设计“知识颗粒度”。
这件事做好了,检索会更准,引用会更稳,回答也更容易建立在正确上下文上。
这件事做不好,后面所有优化都很容易变成低质量基础上的局部补丁。
所以在 RAG Pipeline Hub 里,我才会愿意把分块单独做成一个完整模块,而不是藏在某个工具函数里。
下一篇写什么
分块解决的是“内容应该怎么切”。
接下来的问题就是:
这些块,应该用什么模型、什么方式,变成更适合检索的向量表示?
所以下一篇我会继续写:
《Embedding 不是随便选个模型就行:RAG 向量化模块的工程化设计》
项目地址:
- GitHub:
https://github.com/qingni/rag-pipeline-hub
如果这篇文章对你有帮助,欢迎:
- 点个
star - 提个
issue - 留言说说你最常用的分块策略
如果你也做过 RAG 分块、参数调优、语义切分或者父子分块,欢迎交流你踩过的坑。
我越来越觉得,很多 RAG 项目效果差距的根源,不在模型,而在“知识到底是怎么被切出来的”。