4. 文本分割策略:固定长度分割、递归字符分割、语义分割,如何选择chunk_size与chunk_overlap
文本分割,说白了就是决定“一刀切多长”。
很多同学觉得这步简单,随便设个256或512就完事了。我刚开始做RAG时也这么想,结果上线后用户问“张三去年签的合同编号是多少”,系统死活答不上来。后来一查,问题就出在分割上——关键信息被拦腰切断了。
所以今天咱们好好聊聊三种主流分割策略,以及两个最关键的参数:chunk_size和chunk_overlap。
4.1 三种分割策略的核心差异
先看一张对比图,帮你快速建立整体认知。
4.2 固定长度分割
这是最原始的方法。不管内容是什么,数够字符就切一刀。
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separator="\n"
)
chunks = splitter.split_text(text)
优点就一个字:快。处理海量日志、固定格式报表时,我经常用它。
但缺点也很明显——它不懂语义。我在一个项目里用它切法律合同,结果把“甲方应于2024年12月31日前”切成了两段,检索时“2024年12月”和“31日前”分到了不同块,LLM根本理解不了完整日期。
4.3 递归字符分割
这是目前最主流的方案。LangChain、LlamaIndex默认用的就是它。
它的思路是:先按段落切,如果段落太长,再按句子切,句子还长,再按标点切……一层层递归下去。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " ", ""]
)
chunks = splitter.split_text(text)
注意看separators的顺序——从大到小。先尝试用双换行(段落),不行再换单换行,再不行换句号……
我个人习惯把中文标点放在英文前面,因为中文句号比英文句点更能代表语义边界。
4.4 语义分割
这是最“聪明”的方法。它不按字符数切,而是按语义边界切。
具体做法:把文本切成小段,每段算一个embedding向量。然后看相邻两段的相似度,如果相似度突然下降,说明话题变了,就在这切一刀。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
splitter = SemanticChunker(
embeddings=OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=0.3
)
chunks = splitter.split_text(text)
这里有个关键参数breakpoint_threshold_amount,我一般设0.3~0.5。设得太小,切得太碎;设得太大,又切不动。
但代价也很明显——慢。embedding计算一次,相似度计算一次,处理100万字的文档,语义分割比递归分割慢5~10倍。
4.5 如何选择chunk_size与chunk_overlap
这两个参数没有“万能值”,但有几个经验法则。
| 场景 | 推荐chunk_size | 推荐chunk_overlap | 说明 |
|---|---|---|---|
| 短文本(问答、FAQ) | 128~256 | 10~20 | 块小,检索精准 |
| 中等文档(新闻、博客) | 512~768 | 50~100 | 平衡召回与理解 |
| 长文档(论文、报告) | 1024~2048 | 100~200 | 保留上下文连贯性 |
| 代码/结构化文本 | 256~512 | 20~50 | 按函数/类边界切 |
chunk_overlap为什么重要?我举个例子。假设chunk_size=500,chunk_overlap=0,那么第1块是1~500字符,第2块是501~1000字符。如果“张三的合同编号是”刚好在500字符处结束,“A2024-001”在501字符开始,那两块都丢了完整信息。
加了overlap后,第1块末尾会包含第2块开头的一部分内容,保证边界信息至少在一个块里是完整的。
4.6 我的选择建议
如果你刚开始做RAG,我建议这样选:
- 先用递归字符分割,这是最稳妥的起点。
- chunk_size从512开始,跑一遍测试,看检索结果是否完整。
- chunk_overlap设10%,也就是50左右。
- 如果发现关键信息被切碎,增大chunk_size到768或1024。
- 如果发现检索噪声太多,减小chunk_size到256或384。
- 如果文档主题变化明显,换成语义分割。
我曾经在一个金融项目里,用递归分割+chunk_size=512,结果用户问“2023年Q3营收”,系统总是返回Q2和Q4的数据。后来我把chunk_size调到1024,overlap调到100,问题就解决了。因为季度财报的完整描述需要800~900字符,512根本装不下。