跳过正文
  1. 所有文章/

RAG 进阶:文档切片策略深度对比

Aaron
作者
Aaron
I only know that I know nothing.
目录

前言
#

之前写过一篇 RAG 基础文章,把分片、索引、召回、重排、生成的完整链路梳理了一遍。文章里提到分片粒度需要权衡,但当时没有展开。后来在实际项目中,我发现切片策略对最终效果的影响远比想象中大。同样的文档、同样的 Embedding 模型,换一种切法,检索准确率可以差出一倍。这才意识到,RAG 系统回答质量的好坏,在你还没开始检索之前就已经决定了。这篇文章就来把文档切片这个环节彻底说透,对比五种主流策略的优劣和适用场景。

为什么切片策略这么关键
#

RAG 系统的核心是把知识文档变成向量,再跟用户问题做匹配。而决定这个匹配效果的基础,就是你一开始怎么把文档切成块的。

切得不好,一个完整的语义单元会被打散到多个片段里。检索时可能只命中了其中一部分,另一半关键信息丢了。就像切蛋糕,你想吃到完整的草莓,结果草莓被切成碎末分布在五六块蛋糕里,拿到任何一块都尝不到完整的草莓味。

切片的好坏直接决定了 Embedding 的编码效果和检索的准确性。 这是整个 RAG 链路中最容易被忽视,却最值得投入精力的环节。

五种切片策略对比
#

固定大小分块(Fixed-size Chunking)
#

最直觉的方案:按预设的字符数、单词数或 Token 数切分。比如每 500 个 Token 切一块。

# LangChain 中的固定大小切片示例
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=500,
    chunk_overlap=50,  # 相邻块之间保留重叠
    length_function=len,
)
chunks = splitter.split_text(document)

关键技巧是引入滑动窗口:相邻文本块之间保留部分重叠内容(比如重叠 50 个 Token),缓解语义被切断的问题。

优点很明显:实现最简单,文本块大小统一,方便批量化处理。适合作为基线方案或快速验证。

缺点也很明显:会切断句子和段落,导致重要信息分散到不同文本块中。一句话被切成两半,检索时只匹配到一半,上下文丢了。

能跑通,但回答质量不高。适合入门和快速验证,不建议作为最终方案。

语义分块(Semantic Chunking)
#

固定大小分块的问题在于它完全不看内容。语义分块的思路是:按照语义边界来切割。

具体做法分四步:

  1. 先把文档按有意义的单元(句子、段落、主题章节)做预划分
  2. 为每个单元生成 Embedding 向量
  3. 计算相邻单元的余弦相似度
  4. 相似度高的合并,迭代直到相似度显著下降(意味着语义发生了转变)

就像听人讲话,当他从一个话题切换到另一个话题时,你能感觉到「嗯,他在说别的事了」。语义分块就是让机器来做这种判断。

优点是保持了语义的自然连贯性,文本块信息更丰富,检索准确率更高。

缺点是依赖相似性阈值的设定。阈值设高了,块太大;设低了,块太碎。这个阈值很吃经验,不同类型的文档可能需要不同的阈值。

在更多场景中表现相对优异,是性价比很高的选择。

递归分块(Recursive Chunking)
#

这是一种「层层递进」的切法:

  1. 先按段落或章节做预分块(用简单的分隔符就行)
  2. 判断每个文本块是否超过最大尺寸限制
  3. 如果超过了,在段落内进一步切分
  4. 重复迭代,直到每个段落都小于预设阈值
# LangChain 中的递归切片示例
from langchain.text_splitter import RecursiveCharacterTextSplitter

splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=100,
    separators=["\n\n", "\n", "。", "!", "?", ".", " ", ""],
)
chunks = splitter.split_text(document)

separators 参数定义了优先使用的分隔符层级:先尝试按双换行(段落)切,切不动再按单换行(行)切,再切不动按句号切,依此类推。

递归分块既保证了语义的自然流畅度,也确保了语义单元的完整性。是对固定大小分块和语义分块的一种折中优化。缺点是复杂度和计算开销相对较高,需要设计好递归的终止条件和每层的切分策略。

适合结构相对规整但段落长度差异大的文档。

基于文档结构的分块(Structure-based Chunking)
#

充分利用文档现有的结构特征:标题、章节、段落来划分文本块的边界。如果文档有清晰的「第一章」「1.1」「1.1.1」这样的层级结构,就按这个结构来切。

# LangChain 中按 Markdown 标题结构切片
from langchain.text_splitter import MarkdownHeaderTextSplitter

headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
    ("###", "Header 3"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
chunks = splitter.split_text(markdown_document)

语义完整性最好,因为切出来的块天然符合文档的逻辑结构。

但前提是文档必须具备清晰的层次结构。现实是很多文档结构杂糅:标题乱用、层级混乱、格式不统一。而且按结构切出来的文本块长度参差不齐,某个标题下可能就两行字,另一个标题下可能上千字1

文档结构清晰时效果很好,但现实中「结构清晰」这个前提经常不成立。

基于大模型的分块(LLM-based Chunking)
#

通过设计 Prompt 让大语言模型自动生成语义独立、内容完整的文本块。相当于让 AI 来读文档,自己判断该怎么切。

# 概念示例:用 LLM 做语义切分
prompt = """
请将以下文本按照语义主题进行切分。
要求:
1. 每个片段必须是语义完整的主题单元
2. 不要在句子中间切断
3. 返回 JSON 格式:[{"chunk": "...", "topic": "..."}]

文本内容:
{document}
"""

语义准确性最高。大模型真正理解上下文和语义关联性,能做出最接近人类判断的切分。

但计算资源需求也最高。每个文档都要过一遍大模型,成本和耗时都不小2

效果最好但最贵,适合对质量要求极高、文档量可控的场景。

五种策略速查表
#

策略 核心思路 语义完整性 实现复杂度 计算成本 适用场景
固定大小分块 按 Token 数等分 快速验证、基线方案
语义分块 按语义边界切割 通用场景,性价比最优
递归分块 层层递进切分 中高 段落长度差异大的文档
结构化分块 按文档层级切割 结构清晰的文档(如法律条文)
LLM 分块 大模型理解后切割 最高 最高 高质量要求、文档量可控

实战中不是单选题
#

讲了五种策略,到底选哪个?答案是:可以组合使用。

实际落地中,切片策略通常经历这样的演进过程:

第一版: 先用固定大小切片跑通,验证基本流程没问题。这时候回答质量可能不高,但至少系统能运转。

第二版: 针对问题优化。比如加入人工规则,用正则表达式匹配专有名词,确保不被切断。如果文档涉及医学、法律等有大量专业术语的领域,这一步尤其重要。

最终形成三类策略的组合:

策略类型 适用场景 方法
通用切片 通用知识类文档 设定固定 Token 长度直接分割
专业切片 涉及专有术语的学科文档 设定专门策略,确保不切断专有名词
粗精分结合 复杂文档 先粗分(如 5000 Token 一组),再精细化小片段切割

粗精分结合的思路特别值得说一说。先按大段落粗分,保证每个大块在同一个主题范围内。然后在每个大块内部再做精细化的小片段切割,兼顾了主题完整性和检索精度3

Token 大小的权衡
#

不管用哪种策略,都有一个绕不开的参数:每个文本块的 Token 大小。

Token 过大: 语义理解困难,一个段落涵盖的信息太多,无法精准匹配到用户问题的具体关注点。就像问一个人「北京明天天气怎么样」,结果他给你念了一整周的天气预报。

Token 过小: 能精准命中最相关的句子,但切块数量暴增,相似度计算量增大,系统性能下降。而且碎片化太严重,上下文信息丢失。

核心原则是:Token 大小需要在语义完整性和计算性能之间取得平衡。没有一个万能的最优值,需要根据具体的文档类型和使用场景来调4

选择策略时要注意什么
#

每种方案都有独特优势和局限性,不可能用一种通用方案解决所有问题。方案之间也不冲突,可以组合使用。比如递归分块时,对长段落转用固定大小分块或语义分块5

技术选型需要综合考虑内容特性、大模型能力和计算资源等多重因素。最重要的是先跑通再优化,别一上来就追求最完美的方案,先用简单策略把流程跑通,再根据实际问题迭代优化。


  1. 结构化分块在 Markdown、HTML 等格式文档上效果最好,因为这类文档的标题层级是显式标注的。对于扫描件 PDF 或纯文本,需要先做结构化抽取。 ↩︎

  2. LLM 分块的成本可以通过小模型(如 GPT-4o-mini)来降低,但语义理解能力也会相应下降。实际中可以在关键文档上用大模型,普通文档用更轻量的方案。 ↩︎

  3. 粗精分结合的思路在 LlamaIndex 的 HierarchicalNodeParser 中有对应的实现,它将文档构建为树状结构,父节点是粗分块,子节点是细分块。 ↩︎

  4. 一般来说,通用知识问答场景下 256~512 Token 是一个不错的起点。法律、医学等专业领域可能需要更大的块(512~1024 Token)来保持上下文完整。 ↩︎

  5. LangChain 和 LlamaIndex 都提供了多种内置的 Text Splitter,可以按需组合。建议先通读文档了解每种 Splitter 的设计思路,再决定怎么搭配。 ↩︎