文档分块(Chunking)策略有哪些?各有什么优缺点?
难度:基础 分类:RAG
简短回答
主要有三种分块策略:固定大小分块(按字符/token 数切分,简单高效但破坏语义)、递归分块(按段落→句子→词层次切分,是最推荐的通用默认方案,Recall 85-90%)、语义分块(基于 Embedding 相似度在语义断点处切分,Recall 91-92% 但需要额外 Embedding 开销)。核心权衡是:上下文保留 vs 检索精度——块越大保留越多上下文但稀释相关性,块越小匹配越精确但丢失上下文。
详细解析
为什么分块很重要?
分块不是简单的预处理步骤,它是一个影响整个 RAG 系统性能的设计决策。分块策略决定了:
•
Embedding 的语义质量(块是否表达完整的概念)
•
检索的精度和召回率(能否找到正确的信息)
•
生成的上下文充分性(LLM 是否有足够信息回答问题)
•
Token 使用效率和成本
固定大小分块(Fixed-Size Chunking)
原理: 按预定的字符数或 token 数均匀切分文本,通常带有重叠(overlap)。
代码块
Plain Text
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500, # 每块 500 字符
chunk_overlap=50, # 相邻块重叠 50 字符
separator="" # 按字符切
)
chunks = splitter.split_text(document)
#taDMcmGXZmFJ text { white-space: pre; }#taDMcmGXZmFJ .fb0 { stroke: var(--N900-FG, 0.15); }#taDMcmGXZmFJ .fb0 {stroke: rgba(31,35,41, 0.15)}#taDMcmGXZmFJ .fx0 { stroke-miterlimit: 10; }#taDMcmGXZmFJ .fx0 {stroke-miterlimit: 10}#taDMcmGXZmFJ .fa1 { fill: rgb(187, 191, 196); }#taDMcmGXZmFJ .fa1 {fill: rgb(187, 191, 196)}#taDMcmGXZmFJ .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#taDMcmGXZmFJ .fx1 {font-style: normal}#taDMcmGXZmFJ .fx2 { text-anchor: middle; }#taDMcmGXZmFJ .fx2 {text-anchor: middle}#taDMcmGXZmFJ .fx3 { dominant-baseline: central; }#taDMcmGXZmFJ .fx3 {dominant-baseline: central}#taDMcmGXZmFJ .fx4 { stroke: none; }#taDMcmGXZmFJ .fx4 {stroke: none}#taDMcmGXZmFJ .fc0 { fill: var(--text-caption); }#taDMcmGXZmFJ .fc0 {fill: #646a73}#taDMcmGXZmFJ .fa2 { fill: var(--text-link-disabled); }#taDMcmGXZmFJ .fa2 {fill: rgb(187, 191, 196)}#taDMcmGXZmFJ .fx5 { font: 400 16px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#taDMcmGXZmFJ .fx5 {font-style: normal}#taDMcmGXZmFJ .fx6 { text-anchor: start; }#taDMcmGXZmFJ .fx6 {text-anchor: start}#taDMcmGXZmFJ .fx7 { dominant-baseline: alphabetic; }#taDMcmGXZmFJ .fx7 {dominant-baseline: alphabetic}#taDMcmGXZmFJ .fc1 { fill: rgb(31, 35, 40); }#taDMcmGXZmFJ .fc1 {fill: rgb(31, 35, 40)}#taDMcmGXZmFJ .fb1 { fill: var(--N900-FG, 0.15); }#taDMcmGXZmFJ .fb1 {fill: rgba(31,35,41, 0.15)}#taDMcmGXZmFJ .fb2 { fill: rgb(209, 217, 224); }#taDMcmGXZmFJ .fb2 {fill: rgb(209, 217, 224)}#taDMcmGXZmFJ .fa3 { fill: var(--bg-body); }#taDMcmGXZmFJ .fa3 {fill: #fff}AB12345优点实现最简单,无需 NLP 库计算成本最低块大小均匀,便于索引优化适合快速原型缺点完全忽略语义结构可能在句子甚至单词中间切断可能混合不相关的主题即使有 overlap 也不能保证语义完整
最佳场景: 同质化数据集(新闻文章、博客等格式统一的内容)、快速原型验证。
递归分块(Recursive Chunking)
原理: 按层次化分隔符递归切分——先尝试按段落(\n\n),如果段落仍然太大就按句子(\n),再不行按空格,最后按字符。确保尽可能保持段落和句子的完整性。
代码块
Python
#tAsucrHmcHRn text { white-space: pre; }#tAsucrHmcHRn .fb0 { stroke: var(--N900-FG, 0.15); }#tAsucrHmcHRn .fb0 {stroke: rgba(31,35,41, 0.15)}#tAsucrHmcHRn .fx0 { stroke-miterlimit: 10; }#tAsucrHmcHRn .fx0 {stroke-miterlimit: 10}#tAsucrHmcHRn .fa1 { fill: rgb(187, 191, 196); }#tAsucrHmcHRn .fa1 {fill: rgb(187, 191, 196)}#tAsucrHmcHRn .fx1 { font: 400 12px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#tAsucrHmcHRn .fx1 {font-style: normal}#tAsucrHmcHRn .fx2 { text-anchor: middle; }#tAsucrHmcHRn .fx2 {text-anchor: middle}#tAsucrHmcHRn .fx3 { dominant-baseline: central; }#tAsucrHmcHRn .fx3 {dominant-baseline: central}#tAsucrHmcHRn .fx4 { stroke: none; }#tAsucrHmcHRn .fx4 {stroke: none}#tAsucrHmcHRn .fc0 { fill: var(--text-caption); }#tAsucrHmcHRn .fc0 {fill: #646a73}#tAsucrHmcHRn .fa2 { fill: var(--text-link-disabled); }#tAsucrHmcHRn .fa2 {fill: rgb(187, 191, 196)}#tAsucrHmcHRn .fx5 { font: 400 16px MonospacedNumber, LarkHackSafariFont, LarkEmojiFont, LarkChineseQuote, -apple-system, BlinkMacSystemFont, "Helvetica Neue", Tahoma, "PingFang SC", "Microsoft Yahei", Arial, "Hiragino Sans GB", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; }#tAsucrHmcHRn .fx5 {font-style: normal}#tAsucrHmcHRn .fx6 { text-anchor: start; }#tAsucrHmcHRn .fx6 {text-anchor: start}#tAsucrHmcHRn .fx7 { dominant-baseline: alphabetic; }#tAsucrHmcHRn .fx7 {dominant-baseline: alphabetic}#tAsucrHmcHRn .fc1 { fill: rgb(31, 35, 40); }#tAsucrHmcHRn .fc1 {fill: rgb(31, 35, 40)}#tAsucrHmcHRn .fb1 { fill: var(--N900-FG, 0.15); }#tAsucrHmcHRn .fb1 {fill: rgba(31,35,41, 0.15)}#tAsucrHmcHRn .fb2 { fill: rgb(209, 217, 224); }#tAsucrHmcHRn .fb2 {fill: rgb(209, 217, 224)}#tAsucrHmcHRn .fa3 { fill: var(--bg-body); }#tAsucrHmcHRn .fa3 {fill: #fff}AB12345优点实用的默认方案显著减少句子被截断的概率Chroma 测试:400-512 tokens 时 Recall 85-90%计算成本仍然很低缺点仍可能在某些复杂结构处切断不处理表格、列表等特殊元素不同文档格式需要不同的分隔符集不考虑语义相似度
最佳场景: 通用默认方案。结构化文本(技术文档、报告)、代码文件(配合语言特定分隔符)。
推荐参数: 400-512 tokens,10-20% overlap。
语义分块(Semantic Chunking)
原理: 先对每个句子生成 Embedding,计算相邻句子的余弦相似度,在相似度急剧下降的地方切分——即在"主题转换点"分块。
代码块
Plain Text
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
splitter = SemanticChunker(
最佳场景: 准确率优先的场景、叙述性/研究性文档、预算允许额外计算开销时。
其他分块策略
3.1
文档结构分块(Structure-Aware)
利用文档自身的结构(标题、章节、HTML 标签)作为分块边界:
代码块
Python
# Markdown 文档按标题分块
from langchain.text_splitter import MarkdownHeaderTextSplitter
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[
("#", "h1"), ("##", "h2"), ("###", "h3")
]
)
3.2
小块检索 + 大块上下文(Parent-Child / Small-to-Big)
用小块做精确检索,返回时提供包含小块的大块作为上下文:
代码块
Plain Text
# 检索时用小块(200 tokens)匹配精确
核心权衡
经验法则: 400-512 tokens 是大多数场景的最佳起点。
关键问题:"是否需要分块?"
一个常被忽略的问题:当文档本身就很小、聚焦、且直接匹配用户问题时,分块反而可能损害检索精度。评估是否需要分块,而不是默认总要分块。
常见误区 / 面试追问
误区:"语义分块一定最好" — Chroma 的测试数据显示,递归分块(85-90% Recall)和语义分块(91-92% Recall)仅差 2-3%,但语义分块的计算成本显著更高。大多数场景递归分块就够了。
误区:"块越小越好" — 太小的块会丢失上下文,导致 LLM 无法理解信息的含义。需要在精度和上下文之间找平衡。
追问:"overlap 有什么用?设多少合适?" — Overlap 确保分块边界处的信息不会丢失。通常设置为 chunk_size 的 10-20%(如 512 tokens 的块用 50-100 tokens overlap)。
追问:"如何选择最佳的 chunk_size?" — 没有通用最优值。应该在你的数据集上实验——用检索评估指标(Recall@k、MRR)比较不同 chunk_size 的效果。通常从 512 tokens 开始调。