All-in-RAG文本分块策略全解析:固定切分、递归切分与语义分块,如何选对RAG的Chunking方案

原创2026-09-21 23:35:081,737 阅读
文章标签:示例工程教程人工智能大模型RAG

All-in-RAG文本分块策略全解析:固定切分、递归切分与语义分块,如何选对RAG的Chunking方案

RAG文本分块(Text Chunking)是决定检索增强生成系统效果上限的关键一步。这篇文章基于 Datawhale 开源实战教程 all-in-rag,用最少的心智负担讲透三大主流文本分块策略——固定大小分块、递归字符分块与语义分块,最后附上一份可直接落地的选型对照表,帮你 10 分钟选对 Chunking 方案。

RAG架构演进与文本分块优化示意图

在 RAG 的架构演进图中可以看到,从 Naive RAG 到 Modular RAG,"Chunk Optimization(分块优化)"始终是索引侧最核心的模块之一。分块质量不高,后面做得再花哨的检索策略也会"垃圾进、垃圾出"。

为什么RAG文本分块决定检索好坏?

分块的本质,是把加载后的长文档切成更小的单元——这些"块"是后续向量检索和模型处理的基本单位。它要同时满足两个硬性约束:

约束来源 说明
嵌入模型(Embedding) 有严格的输入长度上限,例如 bge-base-zh-v1.5 的上下文窗口为 512 个 token,超长块会被截断,向量无法完整表达语义
大语言模型(LLM) 检索到的所有块 + 问题 + 提示词必须能塞进上下文窗口,块太大就挤占可用信息量

RAG文本分块送入嵌入模型生成向量的示意图

块并不是越大越好,原因有三:

  1. 嵌入信息稀释:嵌入模型会把整个块池化(pooling)压缩成一个向量,块越长、主题越多,这个向量就越"笼统";
  2. 大海捞针问题:研究表明 LLM 处理长上下文时更容易记住开头和结尾,中间的关键信息会被噪音淹没;
  3. 主题稀释导致检索失败:一个块里混着"技能介绍、推荐出装、背景故事"三种主题时,查询"怎么出装"反而可能检索不到——把主题拆开、各成一块,召回得分会高得多。

💡 想深入理解分块原理,推荐精读教程原文:docs/chapter2/05_text_chunking.md

策略一:固定大小分块,最简单的起步方案

固定大小分块是最直观的切法:按 chunk_size 控制每块大小,用 chunk_overlap 在块间保留重叠,缓解语义割裂。LangChain 中由 CharacterTextSplitter 实现:

text_splitter = CharacterTextSplitter(
    chunk_size=200,    # 每个块的目标大小
    chunk_overlap=10   # 块间重叠,保持上下文连续
)

有两个细节值得了解:

  • 它默认按段落分隔符 \n\n 先切,再"智能合并"直到超过 chunk_size 才成块,所以实际上是段落感知的自适应分块,块大小会随段落边界动态调整;
  • 遇到单个超长段落时,它只会发出警告并保留原样——这是它的主要短板。

✅ 优点:实现简单、速度快、开销小 ❌ 缺点:可能在语义边界处切断文本

完整示例可运行 code/C2/02_character_splitter.py,它加载了教程自带的中文样例 data/C2/txt/蜂医.txt。

策略二:递归字符分块,通用场景的默认选择

递归字符分块(RecursiveCharacterTextSplitter)是绝大多数 RAG 项目的默认方案。它的思路是用一组有优先级的分隔符递归切分:

  1. 先找文本中存在的最高优先级分隔符(如段落 \n\n)来切;
  2. 切完的片段如果还超过 chunk_size,就换下一优先级(如句子 。),递归再切;
  3. 分隔符用尽仍超长的片段,直接保留。
text_splitter = RecursiveCharacterSplitter(
    separators=["\n\n", "\n", "。", ",", " ", ""],  # 分隔符优先级
    chunk_size=200,
    chunk_overlap=10
)

针对中文等无词边界语言,建议像上面示例这样把 。 , 等中文标点加入分隔符列表(完整示例见 code/C2/03_recursive_character_splitter.py);处理代码文档时还可以用 from_language(language=Language.PYTHON) 按函数、类等语法结构切分。

递归字符分块与语义分块的块边界和重叠可视化示意

上图就是典型的分块可视化效果:不同颜色代表不同 chunk,重叠部分清晰可见。用工具把分块结果可视化,是快速验证切分是否合理的好习惯。

✅ 优点:在语义完整性和块大小之间平衡最好,支持多语言和代码 ❌ 缺点:不理解"语义",只理解"格式"

策略三:语义分块,按语义断点智能切割

语义分块(SemanticChunker)不依赖字符数,而是在语义主题发生显著变化的地方下刀,让每个块内部高度连贯。其工作流程分五步:

  1. 句子分割:按句号、问号等规则拆成句子列表;
  2. 上下文感知嵌入:每个句子连同前后相邻句子一起送入嵌入模型,让向量融入上下文语义;
  3. 计算语义距离:求相邻句子嵌入向量的余弦距离;
  4. 识别断点:用统计方法找出"显著的语义跳跃"——支持 percentile(百分位,默认95)、standard_deviation(3倍标准差)、interquartile(1.5倍IQR)、gradient(梯度法)四种阈值策略;
  5. 合并成块:在断点处切开,句序列合成最终文本块。
text_splitter = SemanticChunker(
    embeddings,
    breakpoint_threshold_type="percentile"  # 断点识别方法
)

✅ 优点:切分点贴合语义,块内一致性最高 ❌ 缺点:需要嵌入模型参与,速度明显慢于前两种,语料量大时要权衡成本

完整示例见 code/C2/04_semantic_chunker.py,教程中使用的中文嵌入模型为 BAAI/bge-small-zh-v1.5。

结构感知分块:处理Markdown文档的隐藏大招

如果你的语料是结构清晰的 Markdown / HTML / LaTeX(比如本文所在的 all-in-rag 文档站本身),强烈建议利用文档结构分块:

  • 按标题分块 + 元数据注入:MarkdownHeaderTextSplitter 按 #、##、### 层级切分,并给每个块附加完整标题路径元数据,等于给每块内容配了一个精确"地址",检索和生成时上下文都更准;
  • 两阶段组合拳:先用标题切出"大逻辑块",再用递归字符分块把过长的章节继续切小,最终小块会继承标题元数据——教程第八章的菜谱 RAG 项目就是这个思路,实现了"小块检索、大块生成"的父子文本块架构,相关文档见 docs/chapter8/02_data_preparation.md;
  • 跨框架方案:Unstructured 通过"先分区(识别标题、段落、列表等元素)、后分块"来保留文档语义结构;LlamaIndex 则提供 SemanticSplitterNodeParser、SentenceWindowNodeParser 等面向节点的解析器。

RAG文档处理工具Unstructured的结构化分块官网界面

分块策略选型对照表:一眼选对方案

策略 核心机制 适用场景 速度 上手难度
固定大小分块 按大小切 + 段落合并 日志、扁平文本、快速验证 ⚡最快 ⭐
递归字符分块 分隔符优先级递归切 通用首选,中文/代码均可 ⚡快 ⭐
语义分块 嵌入距离找断点 主题跳跃多、精度要求高 🐢慢 ⭐⭐
结构感知分块 标题/层级切分 + 元数据 Markdown、报告等结构化文档 ⚡快 ⭐⭐

参数调优三条经验:

  1. chunk_size 从 200~500 个字符起步,确保不超过嵌入模型窗口;
  2. chunk_overlap 取块大小的 5%~15%,太少会割裂语义,太多浪费存储;
  3. 先可视化、后调参——看切出来的块是否"一块一主题",比盯着参数猜要高效得多。

动手实践:三步开始RAG文本分块

  1. 克隆仓库获取全部代码与样例数据:git clone https://gitcode.com/datawhalechina/all-in-rag
  2. 阅读教程第二章的 docs/chapter2/05_text_chunking.md,对照上文三种策略理解原理;
  3. 依次运行 code/C2/02_character_splitter.py、code/C2/03_recursive_character_splitter.py、code/C2/04_semantic_chunker.py,用同一份 蜂医.txt 对比三种策略的切分结果差异。

一句话总结:先固定、再递归、要精度上语义、有结构用结构分块。分块没有银弹,但有"对的数据结构"——先想清楚你的语料长什么样,再从上表中锁定方案,RAG 的效果就赢在起跑线上了。🚀

登录后查看全文
all-in-rag