Chunking
Chunking是 RAG 的核心技术之一。Chunking的作用是把长文档切成大小合适、语义完整、可以检索、可以组合的小知识单元。
注意:Chunking 的结果会直接影响:
- 检索准确率
- 回答质量
- Token 消耗速度
- Agent 的推理速度
- Memory 的管理能力
1. 为什么 Agent 需要 Chunking?
突破上下文窗口限制 大模型的单词输入长度是有上限的。一个长文档、长对话历史如果不进行分块,就会直接超出窗口上限。
提升检索与推理精度 将信息切割成语义完整的小块,可以让 RAG 更精准定位到关键内容。
控制成本与延迟 分块之后需要把相关的片段送入大模型,可以大幅减少 Token 消耗,降低生成延迟,让 Agent 反应更快。
2. Chunking 的本质是什么?
Chunking 不只是:每 500 个字符切一刀做个分割,而是找到一个信息密度和语义完整性的平衡点。
Chunking 在这个过程中,将长文变成小段,块之间会有重叠的部分用来连接上下文,并且在切分的过程中尽量保证语义的完整。
3. Chunking 的过程
上一节说了 Chunking 追求的是信息密度与语义完整的平衡,下面通过一个具体例子来看这种平衡是如何在切分过程中体现的。
假如我们有以下原文:
Agent 需要分块的原因主要有三点。第一,大模型的上下文窗口有限,长文档直接输入会超出处理上限。第二,分块能让检索更精准,只把相关的片段送给模型。第三,分块可以控制 token 消耗,降低延迟和成本。因此,在 RAG 系统中,chunking 是索引前的关键步骤。
Chunking 之后(假设按固定大小 50 字,重叠 10 字来切)
Chunk 1
Agent 需要分块的原因主要有三点。第一,大模型的上下文窗口有限,长文档直接输入会超出处理上限。第二,分块能让检索更精准
Chunk 2
会超出处理上限。第二,分块能让检索更精准,只把相关的片段送给模型。第三,分块可以控制 token 消耗,降低延迟和成本。因此,在 RAG
Chunk 3
降低延迟和成本。因此,在 RAG 系统中,chunking 是索引前的关键步骤。
上面的内容展示了 Chunking 的实际过程:
- 将长文变成小段,每块都能单独喂给
Embedding模型。 - 块之间存在重叠(也叫 Chunk Overlap),比如:“会超出处理上限。”在 Chunk 1 结尾和 Chunk 2 开头都出现了。这样是防止关键信息卡在被分割点导致语义割裂,保证检索时任何一个 Chunk 都能包含完整的上下文。
- 尽量保证语义完整:切分边界通常选择在句号、换行等自然断点,而不是将句子拦腰截断(示例简单用固定字数演示,实际会用更智能的
RecursiveCharacterTextSplitter)。
当然,固定大小切分只是一种最朴素的策略。不同的参数配置和切分方法会直接决定检索质量的上限。
4. Chunking 的核心参数
4.1 Chunk size
一个 Chunk 多大? 其单位有:
- character
- token
- sentence
- paragraph
例如: Chunk Size = 500 tokens
| 场景 | 推荐大小 |
|---|---|
| 代码文档 | 200~500 tokens |
| 技术文章 | 500~1000 tokens |
| 法律合同 | 1000~2000 tokens |
| 聊天Memory | 50~200 tokens |
| 论文 | 500~1500 tokens |
为什么 Chunk 不是越大越好呢?
因为 Chunk 越大会导致信息更多,噪声更多,以至于 Retrieval 精度下降。
5. 主流 Chunking 方法
5.1 Fixed-Size Chunking(固定大小切片)
最简单的策略:按固定字符数或 token 数来切,每块大小一致。
原文: "Agent 需要分块的原因主要有三点。第一,大模型的上下文窗口有限..."
固定 50 字切分 →
Chunk 1: "Agent 需要分块的原因主要有三点。第一,大模型的上下文窗口有限..."
Chunk 2: "...第二,分块能让检索更精准,只把相关的片段送给模型..."
Chunk 3: "...第三,分块可以控制 token 消耗,降低延迟和成本..."| 维度 | 评价 |
|---|---|
| 实现难度 | 极低,一行代码 |
| 语义完整性 | 差,可能在句子中间截断 |
| 检索精度 | 低,信息碎片化 |
| 适合场景 | 日志分析、快速原型、结构均匀的文本 |
Fixed-Size 通常只作为 baseline。生产环境几乎不会单独使用。
5.2 Recursive Chunking(递归切片)
Fixed-Size 的问题在于"在哪儿切"完全不管语义。Recursive Chunking 的改进思路是:按优先级从高到低尝试不同的分隔符,先找段落边界,找不到再找句子边界,再找不到按空格切,最后才按字符硬切。
分隔符优先级(中文场景):
1. "\n\n" → 段落之间(最优先)
2. "\n" → 换行
3. "。" → 中文句号
4. "!" "?" → 感叹号、问号
5. ";" → 分号
6. "," → 逗号
7. " " → 空格
8. "" → 逐字符(兜底)工作流程:
- 尝试用最高优先级的分隔符切分文本
- 如果某个片段超过 Chunk Size,对该片段递归使用下一级分隔符再切
- 直到所有片段 ≤ Chunk Size,或到达最低优先级
flowchart TD
A[输入文本] --> B{用 \n\n 切分}
B -->|片段 ≤ chunk_size| C[保留为 Chunk]
B -->|片段 > chunk_size| D{用 \n 切分}
D -->|片段 ≤ chunk_size| C
D -->|片段 > chunk_size| E{用 。切分}
E -->|片段 ≤ chunk_size| C
E -->|片段 > chunk_size| F[... 逐级下降 ...]
F --> G[最终按字符数硬切]
G --> C
| 维度 | 评价 |
|---|---|
| 实现难度 | 低,LangChain 等库内置 |
| 语义完整性 | 较好,优先在自然边界切 |
| 检索精度 | 中等偏上 |
| 适合场景 | 最通用的默认选择,文章、文档、代码注释都适用 |
RecursiveCharacterTextSplitter(LangChain)是业界使用最广的实现。
5.3 Semantic Chunking(语义切片)
前两种方法只关心"边界字符",不关心"内容含义"。语义切片的核心思想是:用 Embedding 模型把句子变成向量,通过向量相似度判断哪些句子应该放在一起。
工作流程:
- 将文本拆成句子
- 对每个句子做 Embedding,得到句向量
- 计算相邻句子的余弦相似度
- 在相似度骤降的位置切一刀 —— 那通常就是话题切换点
flowchart LR
S1[句1: Camera2 架构介绍...] --> E1[向量1]
S2[句2: CameraDevice 负责...] --> E2[向量2]
S3[句3: OpenGL ES 是图形库...] --> E3[向量3]
S4[句4: Shader 编程涉及...] --> E4[向量4]
E1 ---|相似度 0.92| E2
E2 ---|相似度 0.35 骤降| E3
E3 ---|相似度 0.88| E4
示例对比:
原文(两个不相关的话题混在一起):
Android Camera2 介绍
Camera2 API 架构...
CameraDevice...
---------
OpenGL ES 介绍
Shader...
Rendering Pipeline...| 方法 | 结果 |
|---|---|
| 固定 500 token | Camera2 + OpenGL 混在同一个 Chunk |
| 语义切片 | Chunk 1 全是 Camera2,Chunk 2 全是 OpenGL |
| 维度 | 评价 |
|---|---|
| 实现难度 | 中,需 Embedding 模型 |
| 语义完整性 | 最优 |
| 检索精度 | 高 |
| 成本 | 较高(每句都要调 Embedding API) |
| 适合场景 | 高质量知识库、多主题混合文档 |
如果文档本身主题单一(比如一篇纯技术文章),语义切片的收益可能不明显,Recursive 就够了。
5.4 Document-Structure Chunking(文档结构切片 / Parent-Child)
利用文档自身的结构信息来切分——Markdown 的标题层级、代码文件的函数/类边界、HTML 的 DOM 树。
典型实现:Parent-Child(父子)模式
Parent Chunk(大块,保留完整上下文):
┌─────────────────────────────────────────┐
│ ## Android 性能优化 │
│ │
│ ### RecyclerView 优化 │
│ RecyclerView 是 Android 中最常用的列表组件... │
│ 优化策略包括:ViewHolder 复用、 │
│ setHasFixedSize、DiffUtil 差异计算... │
│ │
│ ### Bitmap 优化 │
│ 图片加载是内存泄漏的重灾区... │
│ 使用 RGB_565 替代 ARGB_8888 可节省一半内存... │
└─────────────────────────────────────────┘
Child Chunks(小块,用于精确检索):
┌── RecyclerView 优化 ──┐ ┌── Bitmap 优化 ──┐
│ RecyclerView 是... │ │ 图片加载是... │
│ 优化策略包括... │ │ 使用 RGB_565... │
└────────────────────────┘ └─────────────────┘检索逻辑:
- 用户提问 → 检索到最匹配的 Child Chunk
- 返回该 Child 所属的 Parent Chunk(包含更完整的上下文)
- 将 Parent 喂给 LLM 生成回答
这就是 Small-to-Big Retrieval 的核心机制——用小粒度检索保证精度,用大粒度返回保证上下文完整。
| 维度 | 评价 |
|---|---|
| 实现难度 | 中 |
| 语义完整性 | 优秀(利用文档作者的结构意图) |
| 检索精度 | 高(小粒度检索 + 大粒度返回) |
| 适合场景 | Markdown 文档、API 文档、代码库、合同 |
5.5 Agentic Chunking(智能 Agent 切片)
把切分决策交给 LLM 自己——它不是按规则切,而是"读一遍文档,然后决定哪里该切、为什么该切、每个 Chunk 应该包含什么上下文"。
工作流程:
- 将文档分段发给 LLM
- LLM 分析每段的主题、论点和结构
- LLM 输出切分方案:
{ chunk_id: 1, content: "...", topic: "Camera2 架构" } - 可以同时生成每个 Chunk 的摘要、关键词等元数据
与传统方法的本质区别:
| 传统 Chunking | Agentic Chunking | |
|---|---|---|
| 决策者 | 规则/算法 | LLM |
| 理解内容 | 不理解 | 理解语义和论点结构 |
| 切分依据 | 字符数 / 分隔符 / 向量相似度 | 主题、论点、逻辑边界 |
| 产出 | 纯文本片段 | 可附带摘要、关键词、标签 |
| 维度 | 评价 |
|---|---|
| 实现难度 | 高 |
| 语义完整性 | 最优,LLM 真正"理解"内容 |
| 成本 | 高(每篇文档都需要 LLM 调用) |
| 速度 | 慢 |
| 适合场景 | 高价值知识库、企业文档、需要极致检索质量的场景 |
选型建议: 大部分项目从
Recursive Chunking起步就够了。只有当检索质量成为瓶颈时,再考虑升级到Semantic或Agentic。
5.6 方法选型速查
| 方法 | 实现难度 | 语义质量 | 成本 | 适用场景 |
|---|---|---|---|---|
| Fixed-Size | 极低 | 差 | 免费 | Baseline / 日志 |
| Recursive | 低 | 好 | 免费 | 默认首选 |
| Semantic | 中 | 很好 | 中 | 多主题混合文档 |
| Parent-Child | 中 | 很好 | 免费 | 结构化文档 / 代码 |
| Agentic | 高 | 最优 | 高 | 高价值知识库 |
