为什么切分至关重要?

在RAG系统中,文档切分(Chunking)是被严重低估的环节。切分的好坏直接决定检索质量——切得太小,单个chunk语义不完整;切得太大,引入大量噪音,消耗宝贵的上下文窗口。没有一刀切的最优解,需要根据文档类型和应用场景选择策略。

chunk大小如何选择?

固定大小切分 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会附带标题元数据,方便定位来源

实用建议