【RAG实战】文档分块(Chunking)的5种核心策略及选型指南
没有一把锤子能钉所有的钉子。在实际工程中,建议参考以下决策流程:场景/文档类型推荐策略理由通用文本、MVP 快速验证递归字符分块平衡了效果与开发速度,容错率高。技术文档、API 手册、代码结构化分块 (Markdown/Code)必须保持函数或章节的完整性,否则代码无法理解。问答系统、FAQ 库不做分块 (按条目)FAQ 本身就是原子化的,直接按 QA 对存储即可。长篇论文、复杂的法律合同父子索引
摘要:在构建企业级 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", " ", ""]。
-
先尝试按双换行符(段落)切分。
-
如果段落太大,再按单换行符(句子)切分。
-
如果还大,再按空格切分。
代码示例
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)
这是一种进阶策略,不再基于符号,而是基于“含义”的变化来切分。
工作原理
-
将文档按句子分割。
-
计算相邻句子之间的 Embedding 相似度。
-
当相邻句子的相似度低于某个阈值(Threshold)时,认为话题发生了转换(Breakpoint),在此处进行切分。
优劣势分析
-
优势:
-
真正的语义连贯:块的边界是话题的边界,而非机械的字符数。
-
减少噪音:一个块内通常只讨论一个主题。
-
-
缺陷:
-
计算成本高:需要对整个文档进行大量的 Embedding 计算(尽管可以使用轻量级模型)。
-
阈值难调:不同的文档类型需要不同的相似度阈值。
-
策略五:父子索引 / 小到大策略 (Parent-Child / Small-to-Big)
这是目前提升 RAG 效果的大杀器。
工作原理
这种策略将“用于检索的内容”和“传给 LLM 的内容”解耦。
-
Parent Chunk(大块):比如 2000 token,包含了完整的上下文信息,但不做向量化(或仅存 ID)。
-
Child Chunk(小块):将 Parent 切分成 200-400 token 的小块,进行向量化索引。
-
检索时:匹配 Child Chunk。
-
生成时:通过 Child Chunk 的 ID 找到对应的 Parent Chunk,将 Parent 喂给 LLM。
优劣势分析
-
优势:
-
检索精准:小块更容易匹配具体细节。
-
生成准确:大块提供了完整的上下文,解决了断章取义的问题。
-
-
缺陷:
-
存储冗余:需要同时存储父块和子块(或建立复杂的映射关系)。
-
实现复杂:需要数据库支持或更复杂的逻辑代码。
-
总结:如何选择分块策略?
没有一把锤子能钉所有的钉子。在实际工程中,建议参考以下决策流程:
|
场景/文档类型 |
推荐策略 |
理由 |
|---|---|---|
|
通用文本、MVP 快速验证 |
递归字符分块 |
平衡了效果与开发速度,容错率高。 |
|
技术文档、API 手册、代码 |
结构化分块 (Markdown/Code) |
必须保持函数或章节的完整性,否则代码无法理解。 |
|
问答系统、FAQ 库 |
不做分块 (按条目) |
FAQ 本身就是原子化的,直接按 QA 对存储即可。 |
|
长篇论文、复杂的法律合同 |
父子索引 (Parent-Child) |
需要极高的细节检索能力,同时也需要看到条款的上下文。 |
|
话题跳跃性大的对话记录 |
语义分块 |
对话内容通常没有明显的段落标识,靠语义判断话题切换更准。 |
避坑小贴士
-
Overlap 很重要:无论选什么策略(除了父子索引),一定要保留 10%-20% 的重叠(Overlap),防止关键词被切断。
-
数据清洗先行:垃圾进,垃圾出。如果 PDF 解析出来全是乱码或页眉页脚,再好的分块策略也没用。先做 Cleaning。
-
Embedding 模型适配:有些 Embedding 模型(如 BERT 类)最大只支持 512 token,切分过大直接报错或被截断,务必确认模型的 Max Sequence Length。
更多推荐
所有评论(0)