RAG文档切割(Chunking)策略详解:从固定切分到语义切分

目录


一、为什么Chunking是RAG效果的“第一公里”

很多人做RAG时,第一反应是换大模型、调Prompt、上Rerank,但真正决定系统下限的往往是 Chunking。

因为 RAG 的基础链路是:

1
文档 -> 切分Chunk -> 向量化 -> 检索 -> 生成

如果切分阶段已经把语义切碎或混入太多噪声,后面所有优化都只能“补救”,很难“逆天改命”。

典型现象:

  • ❌ 检索召回看起来有内容,但回答仍然答非所问
  • ❌ 命中了关键词,却缺少上下文导致模型误解
  • ❌ 重复召回大量相似片段,白白占用上下文窗口

小结:Chunking不是预处理细节,而是RAG效果的地基。


二、Chunking到底在优化什么

Chunking 的目标可以总结为一句话:

在“语义完整性”和“检索粒度”之间找到最优平衡。

2.1 两个核心矛盾

  1. 切得太大:
  • 语义完整,但噪声也多
  • 检索不够精细
  • 占用大量token
  1. 切得太小:
  • 检索粒度细,但上下文断裂
  • 模型看到的信息不完整
  • 容易丢失关键约束与前提

2.2 Chunking真正影响的三个指标

  • Recall@K:能否召回正确证据
  • Precision-like信号:召回内容是否足够相关
  • Answer Faithfulness:答案是否真正基于证据生成

2.3 一个工程化判断标准

当你调整切分策略时,不要只看“回答是否更像人话”,而应同时看:

  • 召回文档相关性是否提升
  • 最终答案引用一致性是否提升
  • 平均上下文token是否下降或可控

小结:Chunking本质是“为检索服务”,不是“把文档切开”这么简单。


三、四类主流切割策略对比

3.1 固定长度切分(Fixed-size Chunking)

按固定字符数或token数切分,例如每块 500 tokens,overlap 15%。

优点:

  • ✅ 简单稳定,易实现
  • ✅ 适合作为默认基线

缺点:

  • ❌ 容易切断语义边界
  • ❌ 对结构化文档不友好

3.2 递归切分(Recursive Chunking)

按分隔符优先级递归切分(如段落、句子、空格),尽量保持语义边界。

优点:

  • ✅ 比固定切分更“懂结构”
  • ✅ 通用性较强

缺点:

  • ❌ 参数多,调优成本高
  • ❌ 对复杂文档(表格、代码块)仍有挑战

3.3 结构化切分(Markdown/HTML-aware)

利用标题层级、列表、表格、代码块等结构进行切分。

优点:

  • ✅ 语义完整性高
  • ✅ 对技术文档、知识库文章效果好

缺点:

  • ❌ 依赖文档质量
  • ❌ 跨格式统一策略较难

3.4 语义切分(Semantic Chunking)

基于 embedding 相似度、主题变化点进行切分,不拘泥于长度。

优点:

  • ✅ 语义表达最自然
  • ✅ 对跨段落主题跳转更友好

缺点:

  • ❌ 计算成本高
  • ❌ 实现复杂度高

3.5 策略对比表(面试高频)

策略 实现复杂度 语义完整性 检索精度潜力 成本 推荐场景
固定长度 中低 快速上线、基线实验
递归切分 中高 中高 通用知识库
结构化切分 中高 Markdown/HTML文档
语义切分 很高 很高 高价值问答、复杂语义文档

小结:先用固定或递归打底,再按文档形态升级到结构化/语义切分,是更稳的路线。


四、参数设计:size、overlap、tokenizer、metadata

4.1 Chunk Size

常见经验区间(中文技术文档):

  • 300~700 tokens:通用推荐区间
  • 200~350 tokens:检索精度优先
  • 700~1000 tokens:上下文完整性优先

4.2 Overlap

推荐起点:10%~20%。

  • 太低:上下文衔接差
  • 太高:重复召回严重,浪费token

4.3 Tokenizer一致性

切分统计和模型实际计费如果不是同一tokenizer,参数会“看起来对、跑起来偏”。

实践建议:

  • 离线切分与在线推理尽量对齐同一token估算方式
  • 中文场景优先按 token 而非字符数切分

4.4 Metadata注入

建议为每个Chunk附加结构信息:

  • 文档ID、章节标题、时间戳、权限标签
  • 上级标题路径(如:第3章/3.2节)

这样做的意义:

  • 检索时可做过滤(时间、权限)
  • 生成时可返回引用来源
  • 减少“语义孤岛”问题

4.5 参数调优顺序(实战推荐)

1
先定chunk size -> 再调overlap -> 再做metadata增强 -> 最后评估是否需要语义切分

小结:参数调优要有顺序,别同时改四五个开关,否则根本不知道是谁在起作用。


五、Spring AI落地:最小切分与入库流程

5.1 流程概览

1
DocumentReader -> TextSplitter -> EmbeddingModel -> VectorStore

5.2 关键代码(切分 + 入库)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
@Service
public class RagIngestionService {

private final EmbeddingModel embeddingModel;
private final VectorStore vectorStore;

public RagIngestionService(EmbeddingModel embeddingModel, VectorStore vectorStore) {
this.embeddingModel = embeddingModel;
this.vectorStore = vectorStore;
}

public void ingest(List<Document> rawDocs) {
TokenTextSplitter splitter = TokenTextSplitter.builder()
.chunkSize(500)
.minChunkSizeChars(200)
.minChunkLengthToEmbed(80)
.maxNumChunks(10000)
.keepSeparator(true)
.build();

List<Document> chunks = splitter.apply(rawDocs);

// 元数据增强,避免后续检索失去文档上下文
for (Document chunk : chunks) {
chunk.getMetadata().putIfAbsent("source", "internal-kb");
chunk.getMetadata().putIfAbsent("ingestTime", String.valueOf(System.currentTimeMillis()));
}

vectorStore.add(chunks);
}
}

5.3 落地注意点

  • 切分参数建议配置化,不要硬编码
  • 入库前做去重,避免重复chunk污染召回
  • 保留原文索引,便于回溯与重建

小结:Spring AI能很快跑通链路,但真正的效果差异来自切分参数和元数据策略。


六、如何评估切割质量:不是凭感觉调参数

6.1 评估目标

我们要回答两个问题:

  1. 目标证据能否被召回(检索层)
  2. 召回证据是否真正支持答案(生成层)

6.2 推荐评估集设计

构建一个小型黄金集(50~200条即可起步):

  • query:用户问题
  • gold_doc_id:标准证据文档ID
  • expected_fact:关键事实点

6.3 关键代码(离线评估样例)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class ChunkingEvaluator {

private final Retriever retriever;

public ChunkingEvaluator(Retriever retriever) {
this.retriever = retriever;
}

public double recallAtK(List<EvalCase> cases, int k) {
int hit = 0;
for (EvalCase c : cases) {
List<RetrievedDoc> docs = retriever.search(c.getQuery(), k);
boolean matched = docs.stream()
.anyMatch(d -> d.getDocId().equals(c.getGoldDocId()));
if (matched) {
hit++;
}
}
return cases.isEmpty() ? 0.0 : (double) hit / cases.size();
}

public void compareStrategies(List<EvalCase> cases) {
double recallAt5 = recallAtK(cases, 5);
double recallAt10 = recallAtK(cases, 10);
System.out.println("Recall@5=" + recallAt5);
System.out.println("Recall@10=" + recallAt10);
}
}

6.4 一个实用评估流程

1
固定模型和Prompt -> 仅更改Chunking策略 -> 对比Recall@K与答案一致性 -> 记录token成本变化

小结:如果没有评估集,所谓“优化”大概率只是主观感觉。


七、常见坑位与排障路径(高频)

7.1 症状:回答总是差一点

原因:chunk过小,关键信息分散在多个片段。

修复:

  • 增大 chunk size
  • 适当提高 overlap
  • 检查是否误切标题与正文

7.2 症状:召回很多但答案很空

原因:召回重复chunk过多,信息密度低。

修复:

  • 降低 overlap
  • 加入去重与Rerank
  • 控制TopK并提升候选质量

7.3 症状:技术文档问答经常误解

原因:代码块、表格、列表被粗暴切断。

修复:

  • 使用结构化切分(按Markdown节点)
  • 对代码块和表格设置独立切分策略
  • 给chunk补充章节路径metadata

7.4 症状:中文语料效果不稳定

原因:按字符切分导致语义边界错位。

修复:

  • 优先 token 级切分
  • 评估时单独统计中文query集合
  • 对专业术语做词典增强或query重写

小结:多数线上问题都能回溯到“切分粒度、结构边界、元数据”这三个点。


八、进阶策略:Parent-Child与动态拼块

8.1 Parent-Child Chunk

思路:

  • 子块(small chunk)用于精准召回
  • 父块(large chunk)用于生成时补全上下文

收益:

  • ✅ 兼顾检索精度与语义完整性
  • ✅ 尤其适合长文档与规范类文档

8.2 查询时动态拼块

不是固定把TopK拼进Prompt,而是按问题类型做动态组装:

  • 定义类问题:偏向概念块
  • 流程类问题:偏向连续步骤块
  • 对比类问题:拼接多来源块并标注来源

8.3 何时升级到进阶策略

当你出现以下信号时可考虑升级:

  • 基础Chunking已调过仍有明显上限
  • 长文档问答经常“信息不够完整”
  • 检索精度和生成完整性无法同时满足

小结:进阶策略不是一开始就上,而是在基础策略跑稳后用于突破上限。


九、面试速答版(60秒+扩展版)

9.1 60秒版(可直接背)

RAG里的Chunking本质是把文档切成既可检索又保留语义的信息单元。切太大会噪声高、切太小会语义断裂,所以要在语义完整性和检索粒度之间做平衡。工程上常见策略有固定切分、递归切分、结构化切分和语义切分。调优通常先定chunk size,再调overlap,再加metadata,最后通过Recall@K和答案一致性做评估。以Spring AI为例,可以通过TextSplitter把文档切块后入VectorStore,并结合评估集持续回归,这样才能稳定提升RAG效果。

9.2 扩展版(面试加分)

我一般把Chunking看成RAG的第一公里,因为它直接决定召回质量。

在策略选择上,固定切分适合快速上线,递归切分适合通用场景,结构化切分适合技术文档,语义切分适合高价值复杂问答。

在参数设计上,我会优先关注四个变量:chunk size、overlap、tokenizer一致性、metadata注入。中文场景通常建议从300到700 tokens起步,overlap从10%到20%试探。

在评估上,我会构建小型黄金集,固定模型与Prompt,仅替换切分策略,比较Recall@K、答案可追溯性和token成本,避免凭感觉优化。

如果基础策略遇到瓶颈,再升级到Parent-Child或动态拼块,这样可以在精度、完整性和成本之间取得更好的平衡。


总结

1
2
3
4
5
Chunking做得好,RAG才有上限;
Chunking做不好,后面的优化都在救火。

调优顺序建议:
策略选型 -> 参数调优 -> 评估回归 -> 再做进阶。