02.2 - 文本分块策略
定位: RAG 检索质量的第一个关键控制点——合理的分块策略决定了"能不能检索到正确的信息" 面试高频度: ⭐⭐⭐⭐ 考查方式: 分块参数选择、不同策略的场景适配、分块对检索质量的影响
一、这是什么?为什么需要它?
是什么
文本分块(Chunking)是将加载后的长文档切分为若干较小的文本片段(Chunks),每个 Chunk 将被独立向量化并存储,作为检索的基本单元。
原始文档(3000字):
第1章...第2章...第3章...第4章...
分块后:
Chunk 1: 第1章内容... <- 每个 Chunk 独立向量化
Chunk 2: 第2章内容... <- 检索时按 Chunk 匹配
Chunk 3: 第3章内容...
Chunk 4: 第4章内容...为什么需要分块?
LLM 和 Embedding 模型都有"输入长度限制"。
| 限制 | 含义 | 典型值 |
|---|---|---|
| Embedding 模型最大长度 | 超过则截断 | 512 tokens(如 BGE) |
| LLM 上下文窗口 | 只能处理这么多信息 | 4K-128K tokens |
| 检索精度 | 块太大/太小都影响匹配 | 128-1024 tokens |
不分块的后果:
公司对远程工作的政策是什么?
不分块 -> 整本 500 页员工手册作为一个向量
-> "远程工作"的语义被淹没在整体语义中
-> 检索得分很低
分块后 -> 远程工作政策作为独立 Chunk(约半页)
-> 语义集中 -> 检索得分很高
-> 精准命中!二、原理拆解
2.1 分块的核心权衡
+-- 更精准 --+ +-- 更完整 --+
| |
小块 大块
+------------------+ +------------------+
| 128 tokens | <----> | 1024+ tokens |
+------------------+ +------------------+
| |
+ 语义单一、精确 + 上下文完整、信息丰富
- 缺乏上下文 - 噪声多、关键词易稀释
- 碎片化、需要更多K - 超过模型限制时被截断| 参数 | 小块 (128-256) | 中块 (512) | 大块 (1024-2048) |
|---|---|---|---|
| 语义纯度 | 高 | 中 | 低(可能含多个主题) |
| 上下文完整性 | 低(截断段落) | 中 | 高 |
| Top-K 覆盖率 | 需要更多 K | 适中 | 少量 K 即可 |
| 检索召回(精确查询) | 高 | 中高 | 中低 |
| 检索召回(模糊主题) | 低 | 中 | 高 |
| 存储成本 | 块数多 | 适中 | 块数少 |
2.2 五种主流分块策略
策略 1:固定大小分块
最简单的分块方式,按固定 token 数切分:
[文档开始]
[0-256 tokens] [128-384 tokens] [256-512 tokens] ...
Chunk 1 Chunk 2 Chunk 3
重叠 128 tokens <-- overlap 防止截断语义核心参数:
| 参数 | 作用 | 推荐值 |
|---|---|---|
| chunk_size | 每个块的 token 数 | 256-1024 |
| chunk_overlap | 块之间重叠的 token 数 | 10-20% of chunk_size |
为什么需要重叠?
句子A。句子B。句子C。句子D。
|--- Chunk 1 ---|
|--- Chunk 2 ---|
如果不重叠:"句子B。"可能被 @截断
如果有重叠: 句子B在 Chunk 1 和 2 中都是完整的策略 2:递归字符分块(推荐)
按分隔符优先级递归切分,保留自然文本边界:
python
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
separators=["\n\n", "\n", "。", ".", " ", ""]
# 优先级:段落 > 换行 > 句号 > 空格 > 字符
)分块层级:
文本
|-- 段落 (\n\n) <- 最优分割点
|-- 句子 (。|.!?) <- 次优
|-- 短语 (,|,) <- 再次
|-- 单词 <- 最后手段原始文本:
"第3章 远程工作政策\n\n公司允许员工每周远程工作最多3天。
需提前在系统申请。\n\n第4章 休假制度..."
分块过程:
第1步:按 \n\n 分割 -> ["第3章 远程工作政策\n公司允许...",
"第4章 休假制度..."]
第1块仍 > 500 tokens -> 第2步:按 \n 分割
-> ["第3章 远程工作政策", "公司允许员工每周远程工作最多3天。需提前在系统申请。"]
第1块仍 > 500 tokens -> 第3步:按 。分割
-> 最终 Chunk策略 3:语义分块
基于语义边界(而非字符数)切分,智能识别内容变化点:
原理:
1. 滑动窗口计算相邻句子的 Embedding 相似度
2. 当相似度突变 -> 认为是主题切换点
3. 在切换点切分,保证每个 Chunk 语义完整
效果:
"第3章: 远程工作..." [高相似度:同一主题]
"第3章: 远程工作..."
"公司允许每周3天..." [低相似度:主题切换]
--- 在这里切分 --- [语义边界]
"第4章: 休假制度..."工具:
python
# 使用 LangChain 的语义分块(实验性)
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
semantic_splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile"
# percentile / standard_deviation / interquartile
)
chunks = semantic_splitter.split_documents(documents)| 方法 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 语义分块 | 保留完整语义单元 | 计算成本高 | 写作质量高的文档 |
| 递归分块 | 效率高、效果稳定 | 可能截断句子 | 通用(推荐首选) |
| 固定分块 | 极简单、速度快 | 语义断裂 | 快速原型 |
策略 4:特定文档分块
针对特定文档结构设计的分块策略:
Markdown 分块:
# 标题1 -> Chunk 1 的起点
## 子标题1.1 -> Chunk 2 的起点
正文内容...
## 子标题1.2 -> Chunk 3 的起点
代码分块:
def function1(): -> Chunk 1(按函数)
def function2(): -> Chunk 2
JSON 分块:
{ -> 顶级 key 作为分割点
"employees": [...] -> Chunk 1
"policies": {...} -> Chunk 2
}
HTML 分块(网页):
<article> -> 按语义标签分割
<section>
<p>策略 5:Agentic / LLM 分块
用 LLM 决定如何分块。最智能但最慢、最贵:
python
prompt = """
请将以下文本切分为语义完整的片段,每个片段应该
围绕一个独立的主题。返回 JSON 数组。
文本:{text}
"""
response = llm.invoke(prompt)
chunks = json.loads(response.content)| 策略 | 速度 | 质量 | 成本 | 推荐场景 |
|---|---|---|---|---|
| 固定大小分块 | 最高 | 最低 | 零 | 快速原型 |
| 递归字符分块 | 高 | 中高 | 零 | 通用首选 |
| 语义分块 | 中 | 高 | 中(需Embedding) | 高质量文档 |
| 特定文档分块 | 高 | 高 | 零 | 结构化文档 |
| Agentic分块 | 低 | 最高 | 高 | 最高质量要求 |
三、图解全景
分块策略选择指南
你的文档类型?
|
+-- 长文本/小说/文章?
| -> 递归字符分块(chunk_size=500, overlap=100)
|
+-- Markdown/LaTeX 等结构化文本?
| -> MarkdownHeaderTextSplitter(按标题分块)
|
+-- 代码文件?
| -> 按函数/类分块(RecursiveCharacter with code separators)
|
+-- JSON/HTML/XML?
| -> 特定解析器 + RecursiveCharacter
|
+-- 高价值文档(准确率要求极高)?
| -> 语义分块/Semantic Chunking
|
+-- 不知道 -> 递归字符分块(最通用的默认选择)Chunk 参数速查表
| chunk_size | chunk_overlap | 适合场景 | 说明 |
|---|---|---|---|
| 128 | 16 | 代码、关键词搜索 | 最精确,但上下文少 |
| 256 | 32 | FAQ、短文本 | 精度和上下文的平衡 |
| 512 | 100 | 通用(推荐起点) | 大多数场景的最佳起点 |
| 768 | 150 | 技术文档、论文 | 需要更多上下文时 |
| 1024 | 200 | 长文分析、总结 | 大块,适合主题级检索 |
四、实战验证
4.1 分块策略对比实验
python
# 分块效果对比:同一文档,不同策略
from langchain.text_splitter import (
RecursiveCharacterTextSplitter,
CharacterTextSplitter,
)
from langchain_community.document_loaders import TextLoader
# 加载测试文档
loader = TextLoader("test_doc.txt")
doc = loader.load()[0]
# 策略 1:固定大小分块
fixed_splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
separator=" ",
)
fixed_chunks = fixed_splitter.split_documents([doc])
# 策略 2:递归字符分块(推荐)
recursive_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100,
)
recursive_chunks = recursive_splitter.split_documents([doc])
# 检查分块结果
for i, chunk in enumerate(recursive_chunks[:3]):
print(f"=== Chunk {i+1} ===")
print(f"长度: {len(chunk.page_content)} 字符")
print(f"内容预览: {chunk.page_content[:100]}...")
print()4.2 分块参数的影响实验
bash
# 用 RAGAS 评估不同分块策略对检索质量的影响
# 实验设计:
# 同一知识库,同一测试集,仅改变分块策略
# 观察 Faithfulness 和 Relevancy 分数
# 典型实验结果:
# +----------------+----------+-----------+
# | 分块策略 | Faith. | Relev. |
# +----------------+----------+-----------+
# | 256, overlap=0 | 0.72 | 0.65 | <- 块太小,上下文不足
# | 512, overlap=50 | 0.78 | 0.71 | <- 好一些
# | 512, overlap=128| 0.83 | 0.76 | <- 最佳平衡
# | 1024, overlap=200| 0.81 | 0.73 | <- 块太大,噪声增加
# | 语义分块 | 0.86 | 0.79 | <- 最好但最贵
# +----------------+----------+-----------+五、面试视角
| 问题 | 答案要点 |
|---|---|
| chunk_size 和 chunk_overlap 如何确定? | chunk_size 取决于 Embedding 模型的 max_input (典型 512) 和文档的复杂度。chunk_overlap 取 chunk_size 的 10-20% 防止截断。起点:500/100 |
| 为什么递归分块是推荐的首选策略? | 因为它按自然分割符(段落>句子>短语)递归切分,在保证效率的同时最大程度保留语义完整性,是质量和速度的最佳平衡 |
| 分块大小如何影响检索质量? | 太小:上下文不足、碎片化;太大:噪声多、语义稀释;最优块能让每个块围绕一个主题,检索时击中率高 |
| 代码文件分块有什么特殊考虑? | 代码应按函数/类/模块分割,而不是按行数;注释和文档字符串应与代码一起保留;使用适合代码的分隔符(函数定义、类定义) |
| 分块对 RAG 延迟的影响? | 块数多 -> 存储大 -> 检索慢;块太大 -> 生成时 tokens 多 -> 生成慢。需在检索和生成之间平衡 |
📚 相关链接
- **文档加载与解析** — 分块之前的文档加载
- **Embedding模型与向量化** — 分块之后向量化
- **检索与重排序** — 检索策略与分块的关系
- <- 返回 **检索系统索引**