Chunk 不只是 chunk_size:我在 RAG 里做了 6 种分块策略和智能推荐

0 阅读14分钟

按字数、按段落、按标题、按语义、父子分块、混合分块,再加上参数推荐、结果预览、版本管理。我想解决的不是“把文档切开”,而是“怎么把内容切成更适合检索和生成的知识块”。

上一篇我写了文档加载模块,核心观点是:RAG 的第一步不是 Embedding,而是高质量文档解析。

这一篇继续往下走,聊第二个特别容易被低估、但实际影响非常大的环节:文档分块

很多 RAG 项目在分块这一步都很直接,甚至可以说有点粗暴:

  • 设一个 chunk_size
  • 再加一个 overlap
  • 然后开始切

这样当然能跑,但问题也非常明显:能跑不等于切得对。

因为分块这件事,表面看是在“切文本”,本质上做的是一件更关键的事:

决定什么内容会一起进入检索,什么上下文会一起进入生成。

如果这一层切得不好,后面再好的 Embedding、再复杂的 Hybrid Retrieval、再强的 LLM,也很难把结果救回来。

项目地址:

  • GitHub: https://github.com/qingni/rag-pipeline-hub

为什么说分块质量会直接决定检索质量

RAG 里很多问题,最后看起来像“检索不准”,但往前追一层,根因其实常常出在分块。

比如下面这些情况,你应该都见过:

  • 一个定义在上半段,解释在下半段,结果被切开了
  • 一个章节很完整,但被机械地切成很多碎片
  • 一个段落很短,本来应该和上下文一起看,却被独立当成一个 chunk
  • 一个长章节直接整个进库,结果召回虽然命中了,但上下文太大太散
  • 文档里有表格、代码、图片说明,但被当作普通文本粗暴拼接

这些问题最终会直接影响下游几个核心指标:

  • 召回命中率
  • 召回覆盖率
  • 上下文完整性
  • 冗余率
  • 最终回答稳定性

所以我越来越觉得,分块不是“预处理步骤”,而是检索质量治理的第一现场

为什么 chunk_size 不够

很多教程讲分块,最后都会落到两个参数:

  • chunk_size
  • overlap

这两个参数当然重要,但它们解决的是“块有多大”和“块之间有多少重叠”,并不能解决下面这些更根本的问题:

  • 文档有没有明显的段落结构
  • 文档有没有标题层级
  • 文档是不是多主题切换
  • 文档里有没有代码、表格、图片
  • 这个场景更需要精准命中,还是更需要保留完整上下文
  • 这个系统能不能接受 embedding 带来的额外成本

换句话说,chunk_size 只是参数,不是策略。

如果只有一个按长度切分的方案,那本质上你还是在用同一把尺子处理所有文档。
而真实文档的结构差异,远远比很多人一开始想象得更大。

这也是为什么我在 RAG Pipeline Hub 里没有停留在单一切分方式,而是把分块模块做成了一个“多策略 + 推荐 + 预览 + 历史管理”的完整工作台。

我为什么要把分块模块单独做成一个工作台

我对这个模块的定义不是“把文本切开”,而是:

把标准化文档结果转换成可检索、可引用、可追溯的块级内容。

这里有几个关键词很关键。

1. 可检索

切出来的块不是为了好看,而是为了后续能被向量化、能被检索命中。

2. 可引用

后面的生成模块不仅要回答,还要能给出来源。
这意味着 chunk 不能只是随机切开的文本片段,它得保留足够的上下文边界和结构线索。

3. 可追溯

出了问题之后,你得知道这个 chunk 是从哪段原文来的,用的是什么策略,参数是什么,有没有版本差异。

所以在这个项目里,分块模块除了算法本身,还配了:

  • 策略选择
  • 参数配置
  • 结果预览
  • 可视化展示
  • 历史记录
  • 版本管理
  • 智能推荐

因为我越来越确信:
如果没有“观察和比较”的能力,分块策略就很难真正调优。

当前支持的 6 种分块策略

这个项目里目前支持 6 种核心分块策略:

  1. 按字数分块
  2. 按段落分块
  3. 按标题分块
  4. 按语义分块
  5. 父子分块
  6. 混合分块

我没有把它们理解成“功能列表”,而是把它们理解成一组面对不同文档问题的工具箱。

下面逐个说一下。

1. 按字数分块:最简单,但不是万能

按字数分块是最容易理解、也最稳定的方案。

它的优点很明显:

  • 可预测
  • 参数简单
  • 对纯文本很友好

如果你处理的是:

  • 结构不明显的连续文本
  • 对块大小有比较严格的控制需求
  • 追求处理速度

那按字数分块通常是一个不错的起点。

但它的问题也很明显:

  • 可能切断句子
  • 可能破坏语义连续性
  • 对章节、段落、列表等天然结构不敏感

所以我把它视为“基础兜底策略”,而不是默认最优策略。

2. 按段落分块:很多文档的默认优解

按段落分块在我看来,是一个经常被低估的方案。

因为很多业务文档天然就是按段落组织的。
这时候如果还用固定字数硬切,往往是在主动破坏原始结构。

按段落分块更适合:

  • 说明文档
  • 技术文章
  • 一般性业务文档
  • 有清晰段落边界的文本

它的核心优势是:

  • 保持段落完整性
  • 更符合人类写作结构
  • 比纯字数分块更容易解释

当然它也不是没有问题:

  • 块大小不一定均匀
  • 有些段落可能过长
  • 有些短段落可能需要合并

所以在工程实现里,它通常要结合 min_chunk_sizemax_chunk_size 一起控制,而不是简单按空行直接切。

3. 按标题分块:最适合结构化文档

如果文档有清晰的标题体系,比如:

  • Markdown 文档
  • 技术设计文档
  • 章节化的说明文
  • 层级结构明显的手册

那按标题分块往往是最自然的选择。

因为这种策略会尽量保留文档原本的章节边界,让每个 chunk 都带有更明确的语义范围。

它特别适合解决一个问题:

有些内容不是靠相邻句子形成语义,而是靠“属于同一节”形成上下文。

比如某一章专门讲配置、另一章讲限制条件、再后一章讲异常处理。
如果只按字数切,很容易把这些内容搅在一起;但按标题切,块的语义边界会清晰得多。

当然,这种策略依赖文档本身的结构质量。
标题不规范、层级不稳定、原始格式不完整时,效果就会打折。

4. 按语义分块:当你真的在乎“语义连续性”

如果说前面三种策略更偏结构驱动,那按语义分块就是典型的内容驱动。

它更关注的问题是:

  • 主题有没有发生变化
  • 相邻段落是不是还在讲同一件事
  • 某一块内容是否应该一起保留

这个项目里的语义分块支持两种思路:

  • 轻量方式:基于 TF-IDF 等相似度做切分
  • 增强方式:结合 Embedding 模型做语义相似度判断

它适合的场景通常是:

  • 语义要求高
  • 主题切换频繁
  • 文档结构不一定稳定
  • 更关注召回质量而不是极致性能

但它的代价也很明确:

  • 处理更慢
  • 成本更高
  • 更依赖模型能力
  • 参数调优更复杂

所以我不会把语义分块当成“默认高级解”,而是把它当成一类高质量但更依赖工程配套的策略

5. 父子分块:为“命中更准 + 上下文更完整”而设计

父子分块是这个模块里我特别看重的一种策略。

它解决的是一个非常典型的矛盾:

  • 如果 chunk 太小,检索命中会更精确,但上下文不够完整
  • 如果 chunk 太大,上下文更完整,但召回粒度又会变粗

父子分块的思路就是把这两个目标拆开:

  • 父块负责保留更完整的上下文
  • 子块负责提高检索精度

这在长文档场景下特别有价值,尤其适合:

  • 技术手册
  • 长篇制度文档
  • 章节较长的知识库内容
  • 希望“命中精准,但返回时上下文也完整”的 RAG 场景

它当然也会带来额外成本:

  • 数据结构更复杂
  • 存储会增加
  • 索引组织也要配合

但如果你的目标不是“先跑通”,而是“把效果做稳”,父子分块是非常值得投入的一种设计。

6. 混合分块:因为文档不只有正文

这是我觉得很多 RAG 系统最容易被简化过头的一点。

现实里的文档,经常不只是连续自然语言,还可能包含:

  • 代码块
  • 表格
  • 图片
  • 配置片段
  • 结构化列表

如果把这些内容全部按同一策略处理,通常效果不会太好。

比如:

  • 代码更适合按行或逻辑块处理
  • 表格更适合独立保留
  • 图片需要配合上下文信息处理
  • 正文部分更适合语义或段落切分

所以混合分块的思路是:

针对不同内容类型,用不同策略处理。

这也是为什么我把它视为一种更贴近真实业务文档的策略。
特别是技术文档、教程类内容、报告类内容,多数时候都比纯文本复杂得多。

为什么要做智能推荐,而不是让用户自己选

如果只从工程实现角度看,支持 6 种策略已经不少了。
但如果从用户体验看,问题马上就来了:

用户怎么知道该选哪一种?

这也是我决定加“智能推荐”的原因。

在这个项目里,推荐逻辑主要会结合三类信号:

1. 文档结构信号

  • 有没有标题结构
  • 段落是否明显
  • 是否包含大量代码、表格、图片

2. 内容语义信号

  • 主题是否频繁切换
  • 是否属于术语密集、学术性或技术性内容

3. 运行约束

  • 是否允许调用 embedding 服务
  • 是否更关注吞吐和速度
  • 是否需要多模态处理

基于这些信号,系统会给出:

  • 推荐主策略
  • 是否启用 embedding 增强
  • 是否启用父子分块
  • 是否启用混合分块
  • 参数建议,比如 chunk_sizeoverlap、阈值等

这件事的价值,不只是“更智能”,而是:

把策略选择从拍脑袋,变成有依据的推荐。

参数推荐为什么也很重要

很多时候,即便选对了策略,参数没调对,效果还是会出问题。

比如:

  • chunk_size 太小,召回碎片化
  • chunk_size 太大,命中不精准
  • overlap 太小,定义和解释容易被切开
  • overlap 太大,冗余显著增加

所以推荐系统不仅要给策略,还要给参数建议。

在这个项目里,参数推荐会结合一些经验规则,例如:

  • 默认 chunk_size 建议落在 500-1000
  • overlap 常见建议是 chunk_size10%-20%
  • 如果经常跨段引用,可以进一步提高 overlap
  • 语义分块阈值会根据 TF-IDF 或 embedding 方式采用不同起点

你可以把它理解成一种“工程经验前置”。

它不一定一步到位,但能显著减少用户在初次试用时的试错成本。

为什么分块结果一定要预览、可视化、可比较

我对分块模块的另一个坚持是:
不能只给用户一个“执行成功”,而要让用户看见到底切成了什么。

原因非常简单。

如果没有预览能力,你很难回答下面这些问题:

  • 这个策略到底切成了多少块
  • 块大小分布是否合理
  • 有没有明显切断句子或章节
  • 父子分块的层级关系是否正常
  • 混合分块有没有把表格和代码处理好
  • 换一种策略后,结果到底好在哪

所以这个模块里配了:

  • 分块预览
  • 结果列表
  • 块详情
  • 线性可视化
  • 树形可视化
  • 混合可视化
  • 统计图表

尤其是父子分块和混合分块,没有可视化其实很难真正理解效果。

这也是我为什么把“可视化展示”当作分块模块的重要组成部分,而不是一个可有可无的锦上添花。

历史记录和版本管理为什么是工程必需品

如果你认真调过分块,就会很快发现一个现实问题:

分块不是一次性动作,而是一个持续试验和比较的过程。

你会不断去调整:

  • 策略
  • 参数
  • overlap
  • 是否启用 embedding
  • 是否启用父子结构
  • 是否启用混合分块

如果每次调整都没有历史记录、没有版本概念,那你很快就会失去对结果的控制。

所以这个项目在分块模块里专门保留了:

  • 历史记录
  • 结果管理
  • 版本控制
  • 多版本并存或智能覆盖

这样做的意义很大:

  1. 你可以回看某次配置到底生成了什么
  2. 你可以比较不同策略的实际差异
  3. 你可以把更好的结果稳定传给后续向量化模块
  4. 你可以形成真正可复盘的质量优化闭环

这也是我一直强调的一个点:

工程化 RAG 不是把链路打通,而是让每一步都能被比较、被调优、被复用。

分块结果最后如何进入向量化模块

这个问题看起来简单,其实很关键。

文档加载模块解决的是“把原始文档标准化”。
分块模块解决的是“把标准化文档转换成可向量化的块级资产”。

也就是说,分块模块最终输出的不只是文本数组,而是更完整的结构化结果,比如:

  • chunk 列表
  • 块类型
  • 父子关系
  • 分块策略信息
  • 参数配置
  • 统计信息

这些结果会被后续向量化模块直接消费。
所以分块质量不只是影响检索,还影响向量化输入的稳定性和可解释性。

这也是为什么我一直不把它看成“预处理小步骤”,而是把它看成内容处理链路里的关键桥梁。

我对分块模块的一个核心判断

如果只让我总结一句话,我会说:

Chunk 不只是 chunk_size,分块也不是把文本切开,而是在为后续检索和生成设计“知识颗粒度”。

这件事做好了,检索会更准,引用会更稳,回答也更容易建立在正确上下文上。
这件事做不好,后面所有优化都很容易变成低质量基础上的局部补丁。

所以在 RAG Pipeline Hub 里,我才会愿意把分块单独做成一个完整模块,而不是藏在某个工具函数里。

下一篇写什么

分块解决的是“内容应该怎么切”。
接下来的问题就是:

这些块,应该用什么模型、什么方式,变成更适合检索的向量表示?

所以下一篇我会继续写:

《Embedding 不是随便选个模型就行:RAG 向量化模块的工程化设计》

项目地址:

  • GitHub: https://github.com/qingni/rag-pipeline-hub

如果这篇文章对你有帮助,欢迎:

  • 点个 star
  • 提个 issue
  • 留言说说你最常用的分块策略

如果你也做过 RAG 分块、参数调优、语义切分或者父子分块,欢迎交流你踩过的坑。
我越来越觉得,很多 RAG 项目效果差距的根源,不在模型,而在“知识到底是怎么被切出来的”。