摘要: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 年行业最佳实践整理。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐