摘要:在构建企业级 RAG(检索增强生成)应用时,很多开发者将精力集中在 Embedding 模型的选择和 Prompt Engineering 上,却往往忽视了最基础也是最关键的一步——文档分块(Chunking)。分块策略直接决定了检索的颗粒度和 LLM 获取上下文的准确性。本文将深入剖析 5 种主流的文档切分策略,分析其优劣势,并给出实际场景下的选型建议。

关键词:RAG, LLM, 文档分块, Chunking, LangChain, 向量数据库

为什么要重视文档分块?

在 RAG 流程中,我们无法将整本书或几百页的 PDF 直接丢给大模型(受限于 Context Window 和 成本)。我们需要将长文本切分成一个个小的片段(Chunks),将其向量化后存入数据库。

  • 切分过大:包含过多无关噪音,检索匹配度低,且容易超出 LLM 窗口限制。

  • 切分过小:丢失上下文(Context),例如代词丢失(“它”指代什么?),导致模型产生幻觉。

因此,找到“语义完整性”与“检索精准度”的平衡点,是分块的核心目标。

策略一:固定大小分块 (Fixed-size Chunking)

这是最基础、最直接的方法。我们设定一个固定的字符数(或 Token 数)作为块的大小,并设定一定的重叠(Overlap)来保持上下文的连贯性。

工作原理

设定块大小为 X,重叠大小为 Y。文本像滑动窗口一样被截断。

代码示例 (LangChain)

from langchain.text_splitter import CharacterTextSplitter

text_splitter = CharacterTextSplitter(
    separator="\n",
    chunk_size=1000,
    chunk_overlap=200,
    length_function=len
)

优劣势分析

  • 优势

    • 简单粗暴:实现成本极低,计算开销小。

    • 可预测:每个块的大小基本一致。

  • 缺陷

    • 切断语义:它不理解文本结构,很可能在句子的中间、列表的一半或者代码块的内部进行强行截断。

    • 上下文丢失:即使有重叠,也难以保证一个逻辑段落的完整性。

策略二:递归字符分块 (Recursive Character Chunking)

这是目前 RAG 系统中默认且最常用的策略(LangChain 的默认推荐)。

工作原理

它并非一次性切分,而是尝试使用一列表的分隔符(Separators),按顺序从大到小进行尝试,直到切分后的块满足大小要求。 默认分隔符顺序通常是:["\n\n", "\n", " ", ""]

  1. 先尝试按双换行符(段落)切分。

  2. 如果段落太大,再按单换行符(句子)切分。

  3. 如果还大,再按空格切分。

代码示例

from langchain.text_splitter import RecursiveCharacterTextSplitter

text_splitter = RecursiveCharacterTextSplitter(
    chunk_size=1000,
    chunk_overlap=200,
    separators=["\n\n", "\n", " ", ""]
)

优劣势分析

  • 优势

    • 保留语义结构:优先保持段落和句子的完整性,比固定大小切分更智能。

    • 灵活性:可以通过调整分隔符参数适应不同文本。

  • 缺陷

    • 对结构混乱的文本无效:如果源文档(如 PDF 解析后的文本)丢失了换行符,退化为字符切分。

策略三:基于文档结构的分块 (Document Based Chunking)

针对 Markdown、代码(Python/Java/JS)或 HTML 等具有强结构的文档,利用其自身的语法结构进行切分是最佳选择。

工作原理

  • Markdown:按 Header(# ## ###)切分,将标题作为元数据(Metadata)带入块中。

  • Code:按类(Class)、函数(Function)定义切分。

代码示例 (Markdown)

from langchain.text_splitter import MarkdownHeaderTextSplitter

markdown_document = "# Title\n\n## Section 1\n\nContent..."
headers_to_split_on = [
    ("#", "Header 1"),
    ("##", "Header 2"),
]

markdown_splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
md_header_splits = markdown_splitter.split_text(markdown_document)

优劣势分析

  • 优势

    • 极高的逻辑完整性:确保每个块都是一个完整的知识点(如一个完整的函数或一个小节)。

    • 元数据增强:可以将章节标题作为上下文保留,检索效果极佳。

  • 缺陷

    • 通用性差:仅适用于格式规范的文档。如果是扫描版 PDF 转出的纯文本,此法失效。

策略四:语义分块 (Semantic Chunking)

这是一种进阶策略,不再基于符号,而是基于“含义”的变化来切分。

工作原理

  1. 将文档按句子分割。

  2. 计算相邻句子之间的 Embedding 相似度。

  3. 当相邻句子的相似度低于某个阈值(Threshold)时,认为话题发生了转换(Breakpoint),在此处进行切分。

优劣势分析

  • 优势

    • 真正的语义连贯:块的边界是话题的边界,而非机械的字符数。

    • 减少噪音:一个块内通常只讨论一个主题。

  • 缺陷

    • 计算成本高:需要对整个文档进行大量的 Embedding 计算(尽管可以使用轻量级模型)。

    • 阈值难调:不同的文档类型需要不同的相似度阈值。

策略五:父子索引 / 小到大策略 (Parent-Child / Small-to-Big)

这是目前提升 RAG 效果的大杀器

工作原理

这种策略将“用于检索的内容”和“传给 LLM 的内容”解耦。

  1. Parent Chunk(大块):比如 2000 token,包含了完整的上下文信息,但不做向量化(或仅存 ID)。

  2. Child Chunk(小块):将 Parent 切分成 200-400 token 的小块,进行向量化索引。

  3. 检索时:匹配 Child Chunk。

  4. 生成时:通过 Child Chunk 的 ID 找到对应的 Parent Chunk,将 Parent 喂给 LLM。

优劣势分析

  • 优势

    • 检索精准:小块更容易匹配具体细节。

    • 生成准确:大块提供了完整的上下文,解决了断章取义的问题。

  • 缺陷

    • 存储冗余:需要同时存储父块和子块(或建立复杂的映射关系)。

    • 实现复杂:需要数据库支持或更复杂的逻辑代码。

总结:如何选择分块策略?

没有一把锤子能钉所有的钉子。在实际工程中,建议参考以下决策流程:

场景/文档类型

推荐策略

理由

通用文本、MVP 快速验证

递归字符分块

平衡了效果与开发速度,容错率高。

技术文档、API 手册、代码

结构化分块 (Markdown/Code)

必须保持函数或章节的完整性,否则代码无法理解。

问答系统、FAQ 库

不做分块 (按条目)

FAQ 本身就是原子化的,直接按 QA 对存储即可。

长篇论文、复杂的法律合同

父子索引 (Parent-Child)

需要极高的细节检索能力,同时也需要看到条款的上下文。

话题跳跃性大的对话记录

语义分块

对话内容通常没有明显的段落标识,靠语义判断话题切换更准。

避坑小贴士

  1. Overlap 很重要:无论选什么策略(除了父子索引),一定要保留 10%-20% 的重叠(Overlap),防止关键词被切断。

  2. 数据清洗先行:垃圾进,垃圾出。如果 PDF 解析出来全是乱码或页眉页脚,再好的分块策略也没用。先做 Cleaning。

  3. Embedding 模型适配:有些 Embedding 模型(如 BERT 类)最大只支持 512 token,切分过大直接报错或被截断,务必确认模型的 Max Sequence Length。

Logo

中国智能体开发者社区,聚焦智能体与大模型开发,提供前沿资讯、实用工具链、开源项目及行业案例。通过技术沙龙、开发者大赛等活动,促进经验交流与协作,助力开发者快速构建创新智能应用。

更多推荐