RAG 文档切割策略:从入门到精通的 8 种实战方案
本文系统梳理了RAG系统中8种主流文档切割策略,从基础固定长度切割到前沿语义切割和Agent智能切割,并比较了各策略的优劣势及适用场景。文章指出文档切割质量直接影响80%的检索效果,关键要平衡信息碎片化与检索精度。推荐默认采用递归切割+重叠策略,结构文档用结构感知切割,高精度场景选用语义切割,高价值语料可考虑Agent智能切割。每种策略均提供代码示例和参数调优建议,帮助开发者根据项目需求选择最佳方
摘要:RAG 系统的检索质量,80% 取决于文档怎么切。本文系统梳理 8 种主流切割策略,从最简单的固定长度到最前沿的 Agent 智能切割,每种策略都给出代码示例、适用场景和避坑指南,帮你快速选出适合自己项目的方案。
一、为什么文档切割是 RAG 的生死线?
先问一个灵魂问题:为什么不把整篇文档直接塞进向量库?
答案很简单:大模型上下文窗口再大(GPT-4 Turbo 128K、Claude 3 200K),也不可能一次性塞下整个知识库。检索阶段必须精准定位到用户问题相关的片段,再把片段喂给 LLM 生成答案。
但切割这件事有个天然矛盾:
- 切太小:信息碎片化,丢失上下文。比如把「公司全职员工入职满一年后享有 10 天带薪年假」切成两半,前半句根本不知道在说什么。
- 切太大:检索精度下降,浪费上下文窗口。用户问「年假几天」,结果检索出整本 100 页的员工手册,LLM 根本看不过来。
所以,怎么切、切多大、在哪切,直接决定了 RAG 系统的天花板。
二、8 种切割策略速查表
| 策略 | 核心思路 | 典型实现 | 优势 | 劣势 | 适用场景 |
|---|---|---|---|---|---|
| 固定长度 | 按字符/token 数硬切 | FixedSizeChunker |
最简单、可预测 | 打断语义 | 日志/代码 |
| 递归切割 | 优先段落→句子→词 | RecursiveCharacterTextSplitter |
保结构、通用首选 | 略慢 | 通用首选 |
| 句子切割 | 按句号、问号等 | SentenceTokenTextSplitter |
语义完整 | 长度悬殊 | 故事/文章 |
| 段落切割 | 按换行符 \\n\\n |
自定义 splitter | 保主题 | 可能太大 | 文档章节 |
| 语义切割 | 用模型找主题边界 | SemanticChunker |
质量最高 | 成本高 | 高精度场景 |
| 重叠切割 | 相邻块重叠 10-20% | chunk_overlap=100 |
防边界丢失 | 冗余 | 几乎所有场景叠加 |
| 结构感知 | 保留 Markdown/HTML 层级 | MarkdownHeaderTextSplitter |
结构完整 | 格式依赖 | MD/HTML |
| Agent 智能切割 | 用 LLM 判断切割点 | 自定义 Prompt | 理解最深 | 成本最高 | 高价值语料 |
一句话总结:默认用递归切割 + 重叠,结构文档用结构感知,精度要求高上语义切割,不差钱/高价值场景上 Agent 智能切割。
三、策略详解与代码实战
1. 固定长度切割:最简单粗暴的 MVP 方案
核心思路:不管内容是什么,按固定字符数或 token 数一刀切。
from langchain.text_splitter import CharacterTextSplitter
# 每 500 字符切一块,相邻块重叠 50 字符
text_splitter = CharacterTextSplitter(
chunk_size=500, # 每个块 500 字符
chunk_overlap=50, # 重叠 50 字符,防止边界信息丢失
separator="\n" # 优先在换行处切
)
chunks = text_splitter.split_text(long_document)
print(f"共切出 {len(chunks)} 个块")
优点:实现简单、速度快、块大小可预测,适合快速验证 MVP。
缺点:完全无视语义,可能把一句话从中间切开。
原文:「公司全职员工入职满一年后享有 10 天带薪年假。」
切后:
块1:「公司全职员工入职满一年后享有 10」 ← 什么鬼?
块2:「天带薪年假。」 ← 前面是啥?
适用场景:日志文件、代码文件、高度结构化的表格数据。
避坑指南:务必加 chunk_overlap(建议 10-20%),否则边界信息 100% 丢失。
2. 递归字符切割:LangChain 默认的通用首选
核心思路:按文本的自然边界分层切割,优先保段落,其次保句子,最后保词。
维护一个分隔符优先级列表:["\n\n", "\n", "。", "!", "?", ";", ",", " "]
先尝试用双换行符(段落)切,如果块太大就用单换行符,再大就用句号……层层降级,直到块大小符合要求。
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 生产环境推荐参数
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=400, # 每个块约 400 字符(或 tokens)
chunk_overlap=50, # 重叠 50,防边界丢失
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " "],
length_function=len # 按字符数计算长度
)
chunks = text_splitter.split_text(long_document)
优点:尽量保持段落、句子的完整性,不会把一句话拦腰斩断。LangChain 默认推荐,社区验证最充分。
缺点:比固定长度稍慢,但对现代硬件来说完全可以忽略。
适用场景:通用场景首选,尤其是长文章、技术文档、产品手册。
避坑指南:
chunk_size建议 300-800 字符(或 100-300 tokens),太小信息碎片化,太大检索精度下降。- 中文内容建议把
。放在\n前面,因为中文段落内很少用换行。
3. 句子切割:语义完整性的极致追求
核心思路:严格按句子边界切分,以句号、问号、感叹号等作为唯一分隔符。
from langchain.text_splitter import SentenceTransformersTokenTextSplitter
# 按句子切,每个句子作为一个块
text_splitter = SentenceTransformersTokenTextSplitter(
chunk_overlap=0, # 句子边界清晰,不需要重叠
tokens_per_chunk=128 # 每个块最多 128 tokens
)
chunks = text_splitter.split_text(long_document)
优点:每个块都是完整的句子,语义零损失,适合对语义完整性要求极高的场景。
缺点:句子长度差异巨大。短的可能只有 5 个 token,长的可能 200+,导致向量检索时块大小不均衡,影响相似度计算。
适用场景:故事类文本、法律文书、小说、需要严格保句的场景。
避坑指南:如果句子过长(超过 chunk_size),需要回退到递归切割或手动截断,否则单个块会超出 embedding 模型限制。
4. 段落切割:按主题的自然边界
核心思路:以段落(双换行符 \n\n)为最小单元,一个段落就是一个块。
from langchain.text_splitter import CharacterTextSplitter
# 自定义分隔符,严格按段落切
text_splitter = CharacterTextSplitter(
separator="\n\n", # 只认段落分隔符
chunk_size=10000, # 设很大,让段落自然决定大小
chunk_overlap=0
)
chunks = text_splitter.split_text(long_document)
优点:段落天然是一个主题单元,切出来的块主题集中、内聚性强。
缺点:段落长度不可控。有的段落只有一句话,有的可能上千字,导致块大小悬殊。
适用场景:格式规范的报告、论文、新闻文章,段落长度相对均匀的文档。
避坑指南:如果段落差异太大,建议先用段落切,再对超长段落做二次递归切割。
5. 语义切割:用 Embedding 找主题边界
核心思路:不是按规则切,而是按语义切。计算相邻句子的向量相似度,相似度骤降的地方就是话题转折点,在这里下刀。
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
# 使用 OpenAI 的 embedding 模型判断语义边界
text_splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile", # 按百分位数判断阈值
breakpoint_threshold_amount=95 # 相似度低于前 95% 就切
)
chunks = text_splitter.split_text(long_document)
原理:先把文档按句子切分,然后逐句计算 embedding 的余弦相似度。如果句子 A 和句子 B 的相似度很高,说明是同一个话题,合并;如果相似度突然跳水,说明话题变了,在这里切开。
优点:切割质量最高,能识别「前半段讲 TCP,后半段讲 UDP」这种话题转折。
缺点:
- 成本高:需要对每个句子做 embedding 计算,索引阶段成本 5-10 倍。
- 阈值难调:breakpoint_threshold 设太低切太碎,设太高切太大,需要反复调参。
适用场景:医疗、法律、金融等对检索精度要求极高的场景。
避坑指南:
- 先用小样本测试阈值,不要直接全量跑。
- 可以先用递归切割做基线,再用语义切割做对比,看提升是否值得成本。
6. 重叠切割:几乎所有场景的必选项
核心思路:不是独立的切割策略,而是叠加在其他策略上的保险层。相邻两个块之间保留 10-20% 的重复内容。
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 核心参数:chunk_overlap
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=500,
chunk_overlap=100, # 重叠 20%,防边界信息丢失
separators=["\n\n", "\n", "。", "!", "?", ";", ",", " "]
)
chunks = text_splitter.split_text(long_document)
为什么必须加重叠?
假设一个关键信息刚好落在切割边界:
原文:「年假可以拆分使用,每次不少于半天。未休年假可累计至次年 3 月。」
无重叠切割:
块1:「年假可以拆分使用,每次不少于半天。」
块2:「未休年假可累计至次年 3 月。」
→ 用户问「年假怎么累计」,块1 没提到,块2 没上下文,两边都检索不到。
有重叠切割(overlap=50%):
块1:「年假可以拆分使用,每次不少于半天。未休年假可累计至」
块2:「每次不少于半天。未休年假可累计至次年 3 月。」
→ 块1 和块2 都包含「累计」关键词,检索命中率大幅提升。
建议重叠比例:
- 固定长度/递归切割:10-20%(如 chunk_size=500,overlap=50-100)
- 语义切割:通常不需要,因为边界本身就是语义转折点
避坑指南:重叠不是越多越好。超过 30% 会导致大量冗余,增加向量库体积和检索噪音。
7. 文档结构感知:让格式成为你的盟友
核心思路:利用文档本身的格式结构(Markdown 标题、HTML 标签、代码函数)来切割,而不是无视结构硬切。
Markdown 文档
from langchain.text_splitter import MarkdownHeaderTextSplitter
# 按 Markdown 标题层级切分
text_splitter = MarkdownHeaderTextSplitter(
headers_to_split_on=[
("#", "Header 1"), # 一级标题
("##", "Header 2"), # 二级标题
("###", "Header 3") # 三级标题
]
)
chunks = text_splitter.split_text(markdown_document)
# 每个 chunk 的 metadata 会自动带上标题信息
for chunk in chunks:
print(chunk.metadata) # {"Header 1": "概述", "Header 2": "安装指南"}
HTML 文档
from langchain.text_splitter import HTMLHeaderTextSplitter
# 按 HTML 标签层级切分
text_splitter = HTMLHeaderTextSplitter(
headers_to_split_on=[
("h1", "Header 1"),
("h2", "Header 2"),
("h3", "Header 3")
]
)
chunks = text_splitter.split_text(html_document)
优点:
- 切出来的块天然带有层级 metadata,检索时可以按标题过滤。
- 语义完整性最好,因为标题本身就是主题边界。
缺点:
- 强依赖文档格式规范。如果文档没有标题、格式混乱,就失效了。
- 需要预处理清洗 HTML/Markdown,去除样式标签等噪音。
适用场景:技术文档(README、API 文档)、产品手册、维基百科类内容。
避坑指南:
- 切割后务必检查 metadata 是否完整,有时候标题嵌套太深会导致信息丢失。
- 对于没有标题的长段落,建议回退到递归切割做兜底。
8. Agent 智能切割:用 LLM 当编辑
核心思路:让 LLM 本身来判断哪里该切、哪里该合并。不是按规则,而是按理解。
from langchain import OpenAI, PromptTemplate
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 第一步:先用递归切割生成初始块
base_splitter = RecursiveCharacterTextSplitter(chunk_size=800, chunk_overlap=100)
initial_chunks = base_splitter.split_text(long_document)
# 第二步:用 LLM 审视每个块,判断是否需要调整
llm = OpenAI(temperature=0)
refine_prompt = PromptTemplate(
input_variables=["chunk", "prev_chunk", "next_chunk"],
template="""
你是一个文档编辑专家。请审视以下文本块,判断:
1. 这个块的内容是否完整、自包含?
2. 是否需要与相邻块合并或拆分?
3. 如果拆分,建议在哪里下刀?
当前块:{chunk}
前一块:{prev_chunk}
后一块:{next_chunk}
请输出调整建议,格式:KEEP / MERGE_PREV / MERGE_NEXT / SPLIT_AT:[位置]
"""
)
# 逐个块调用 LLM 判断(实际生产建议批量处理或异步)
refined_chunks = []
for i, chunk in enumerate(initial_chunks):
prev_chunk = initial_chunks[i-1] if i > 0 else ""
next_chunk = initial_chunks[i+1] if i < len(initial_chunks)-1 else ""
decision = llm.predict(refine_prompt.format(
chunk=chunk, prev_chunk=prev_chunk, next_chunk=next_chunk
))
# 根据决策调整块边界
# ... 实际实现需要解析决策并重组块
refined_chunks.append(chunk) # 简化示例
优点:
- 理论上质量最高,因为 LLM 能理解深层语义和指代关系。
- 可以处理复杂的逻辑结构,比如跨段落的长论证链条。
缺点:
- 成本极高:索引阶段需要调用大量 LLM,成本可能是其他策略的 10-50 倍。
- 速度慢:不适合实时索引,只适合离线预处理高价值语料。
- 一致性差:LLM 的输出有随机性,同样的文档两次切割结果可能不同。
适用场景:
- 法律合同、医学文献、金融研报等高价值、高精度要求的语料。
- 语料量不大(<1000 篇),但每篇都很重要的场景。
避坑指南:
- 不要直接全量跑,先用 10-20 篇测试,对比召回率提升是否值得成本。
- 建议结合 Proposition-Based Chunking(原子事实切割),让 LLM 把文档拆成自包含的命题,而不是整块调整。
四、生产环境决策树
面对一个具体项目,怎么选?按这个流程走:
文档有清晰的 Markdown/HTML 结构?
→ 是 → 用【结构感知切割】+ 重叠 10%
→ 否 → 继续
对检索精度要求极高(医疗/法律/金融)?
→ 是 → 语料量 < 1000 篇?
→ 是 → 用【Agent 智能切割】或【语义切割】
→ 否 → 用【语义切割】+ 异步批处理
→ 否 → 继续
通用场景(技术文档、产品手册、客服知识库)?
→ 是 → 用【递归切割】+ 重叠 10-20% + 元数据
→ 否 → 日志/代码/结构化数据?
→ 是 → 用【固定长度】+ 重叠
→ 否 → 故事/小说/长文章?
→ 是 → 用【句子切割】或【段落切割】
五、2024-2026 年的新进展(进阶必看)
如果你已经掌握了上面的 8 种策略,以下三个新方向值得关注:
1. Parent-Child(父子)切割
小 chunk 做检索(保证精度),大 chunk(父级)做生成(保证上下文)。比如子 chunk 200 tokens 用于向量检索,父 chunk 1500 tokens 返回给 LLM。
LangChain 的 ParentDocumentRetriever 和 LlamaIndex 的 SentenceWindowNodeParser 已实现此模式。在策略密集或手册密集的语料中,检索速度可提升 5-10 倍,token 使用量减少 85-90%。
2. Late Chunking(延迟切割)
Jina AI 2024 年提出。传统做法是先切割再嵌入,Late Chunking 是先嵌入整个文档(利用长上下文嵌入模型),然后在 token 级别应用切割边界,再对每段做 mean pooling。这样每个 chunk 的 embedding 天然携带全文上下文,解决了跨 chunk 的指代消解问题。
3. Contextual Retrieval(上下文检索)
Anthropic 2024 年发布。不是切割策略,而是切割后的增强层:给每个 chunk 前面 prepend 一段 LLM 生成的上下文摘要(如「This chunk is from the Risk Factors section of Q3 2024 earnings…」)。这能让 embedding 携带文档级位置信息,减少检索失败率最高达 67%。
六、总结与最佳实践
| 层级 | 策略 | 备注 |
|---|---|---|
| 基础层 | 递归切割 + 重叠 10-20% | 90% 场景够用了 |
| 结构层 | Markdown/HTML 结构感知 | 有格式的文档优先 |
| 精度层 | 语义切割 / Agent 智能切割 | 高精度场景,注意成本 |
| 增强层 | Parent-Child + Contextual Retrieval | 2024 年后生产标配 |
| 元数据 | 来源、章节、页码、标题 | 所有场景都应该加 |
最后一句忠告:
不要过度优化切割策略。先用递归切割 + 重叠跑通整个 RAG 流程,拿到 baseline 数据,再针对性升级。很多团队花 80% 时间调切割参数,最后发现瓶颈其实在 embedding 模型或 reranker 上。
切割策略只是 RAG 拼图的一块,但这一块选对了,后面的路会好走很多。
本文策略分类参考 LangChain、LlamaIndex、Chroma 官方文档及 2024-2025 年行业最佳实践整理。
更多推荐

所有评论(0)