为什么切分至关重要?
在RAG系统中,文档切分(Chunking)是被严重低估的环节。切分的好坏直接决定检索质量——切得太小,单个chunk语义不完整;切得太大,引入大量噪音,消耗宝贵的上下文窗口。没有一刀切的最优解,需要根据文档类型和应用场景选择策略。
chunk大小如何选择?
- 太小(<100 tokens):语义片段不完整,丢失上下文,检索精度低,召回片段难以理解。
- 太大(>1000 tokens):一个chunk包含多个主题,相关性被稀释,噪音增多,占用更多上下文窗口。
- 推荐区间(200-500 tokens):对大多数通用场景是一个合理的起点,之后根据评估指标调整。
固定大小切分 vs 语义切分
固定大小切分(Character / Token Split)
按固定字符数或token数切分,实现最简单,但会在句子中间截断。
from langchain.text_splitter import CharacterTextSplitter
splitter = CharacterTextSplitter(
chunk_size=500, # 每个chunk最多500字符
chunk_overlap=50, # 相邻chunk重叠50字符
separator="\n" # 优先在换行处切分
)
chunks = splitter.split_text(long_text)
递归字符切分(推荐)
LangChain的 RecursiveCharacterTextSplitter 按优先级尝试多种分隔符(段落→换行→句号→空格),更自然。
from langchain.text_splitter import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=50,
separators=["\n\n", "\n", "。", "!", "?", " ", ""]
)
chunks = splitter.split_text(text)
语义切分
用Embedding模型计算相邻句子的语义相似度,在"语义断层"处切分,保证每个chunk内容连贯。代价是需要调用Embedding模型,速度较慢。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai import OpenAIEmbeddings
splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile",
breakpoint_threshold_amount=90 # 相似度低于90百分位时切分
)
chunks = splitter.split_text(text)
Overlap(重叠)的作用
相邻chunk之间保留一定重叠,是为了避免关键信息被切割在两个chunk的边界处。例如chunk_overlap=50意味着每个chunk的结尾50个字符会出现在下一个chunk的开头。10-15%的overlap是常见选择,过大的overlap会增加存储量并引入重复。
不同文档类型的切分策略
| 文档类型 | 推荐策略 | 关键注意点 |
|---|---|---|
| 普通文本/文章 | 递归字符切分,按段落优先 | 保留段落完整性 |
| Markdown文档 | 按标题层级切分(MarkdownHeaderSplitter) | 保留每个section的语义完整性 |
| 代码文件 | 按函数/类切分,保留缩进结构 | 不要在函数中间截断 |
| PDF文档 | 先提取文本,再按段落切分;注意表格和图片 | PDF布局可能打乱文字顺序 |
| 表格/结构化数据 | 保持行的完整性,或将表格序列化为文本描述 | 尽量不跨行切分 |
Markdown按标题切分示例
from langchain.text_splitter import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "H1"),
("##", "H2"),
("###", "H3"),
]
splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=headers_to_split_on
)
chunks = splitter.split_text(markdown_text)
# 每个chunk会附带标题元数据,方便定位来源
实用建议
- 切分后必须进行人工抽查,看几十个chunk是否语义完整。
- chunk大小要配合Embedding模型的最大输入长度(如BGE最大512 tokens)。
- 在chunk中存储元数据(来源文件、页码、标题),方便引用溯源。
- 同一系统中不同文档类型可以使用不同的切分策略,分别入库。