RAG 的 Chunking 有什么好方案?从原理到实战选型
Reddit 上有一个观点说得很直接:
"Chunking 优化的是 embedding 的便利性,不是文档被使用的方式。"
这句话点出了 RAG 工程中最被低估的问题。大多数开发者花了大量时间调 prompt、换模型、优化 rerank,但 chunking 策略却是拍脑袋定的------固定 512 token,加 50 token overlap,完事。
这是 RAG 质量差的根本原因之一。
一、为什么 Chunking 这么重要?
RAG 的工作流是:
文档 → Chunk → Embedding → 向量索引 → 检索 → 填入 LLM
Chunking 是第二步,但它的影响贯穿整条链路:
原因 1:Embedding 模型的压缩是有损的
Embedding 模型把一段文本压缩成一个固定维度的向量(通常 768 维)。一个 chunk 里混杂了多个主题,向量就会"稀释",检索时语义匹配精度下降。
原因 2:结构信息在 Chunk 时被抹掉
合同里的日期、条款编号、当事方、状态字段,往往被"切"到不同 chunk 里。检索时只能靠相似度找到"主题接近"的 chunk,但真正回答问题所需的结构信息已经丢失了。
原因 3:Chunk 质量决定了后续所有优化的上限
Rerank 再好,也只是在候选集里重新排序。候选集本身的质量上限取决于 chunking。
二、七种主流 Chunking 策略对比
Vecta 团队做了一次严谨的基准测试:50 篇学术论文,905,746 tokens,7 种策略,公平对比(控制总上下文量而非 top-k 数量)。
| 策略 | 准确率 | 接地性 | 适用场景 |
|---|---|---|---|
| Recursive 512 | 69% | 81% | 通用首选 |
| Fixed 512 | 67% | 85% | 要求低幻觉 |
| Fixed 1024 | 61% | 86% | 长文档覆盖 |
| Doc-Structure | 52% | 84% | 结构化文档 |
| Semantic | 54% | 81% | 实验性 |
| Proposition | 51% | 87% | 精细引用场景 |
结论:Recursive 512 赢了,"无聊"的策略反而最稳。
但这不是故事的全部。
三、每种策略详解
策略 1:Recursive Character Splitting(推荐默认)
原理 :按分隔符层级递归切分------先试双换行 \n\n,再试单换行 \n,再试句号,最后才按字符数强切。尽可能在自然边界处切割。
python
from langchain_text_splitters import RecursiveCharacterTextSplitter
splitter = RecursiveCharacterTextSplitter(
chunk_size=512,
chunk_overlap=50,
separators=["\n\n", "\n", ". ", " ", ""]
)
chunks = splitter.split_text(text)
适合:博客文章、技术文档、研究报告------80% 的 RAG 场景。
局限:不理解语义,可能把相关内容切开;不能处理表格、代码块等特殊结构。
策略 2:Markdown Header-Based Chunking
原理 :按文档的标题层级切分。# 一级标题 → ## 二级标题 → ### 三级标题,保留结构关系。
python
from langchain_text_splitters import MarkdownHeaderTextSplitter
headers_to_split_on = [
("#", "h1"),
("##", "h2"),
("###", "h3"),
]
splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split_on)
chunks = splitter.split_text(markdown_text)
# 每个 chunk 自动携带 metadata: {"h1": "...", "h2": "..."}
关键优势:每个 chunk 自动携带标题路径作为 metadata,检索时既可以用语义匹配,也可以用结构过滤。
适合:技术文档、API 文档、Wiki、FAQ------结构清晰的 Markdown 文档。
策略 3:Parent-Child Chunking(生产环境推荐)
原理:解决"小块精确 vs 大块完整"的矛盾。
父块(512-1024 tokens)← 存入索引,提供完整上下文
├── 子块 1(128 tokens)← 用于精确向量检索
├── 子块 2(128 tokens)
└── 子块 3(128 tokens)
检索时用子块,填充 LLM 上下文用父块。
python
from llama_index.core.node_parser import HierarchicalNodeParser
parser = HierarchicalNodeParser.from_defaults(
chunk_sizes=[1024, 512, 128]
)
nodes = parser.get_nodes_from_documents(documents)
效果:在长文档问答场景,答案完整性显著提升,同时保持检索精度。
适合:长文档、合同、报告------需要精确定位又需要完整上下文的场景。
策略 4:Semantic Chunking
原理:不按字符数切,而是按语义相似度切。计算相邻句子的 embedding 相似度,当相似度骤降时,判断为语义边界,在此处切割。
python
from langchain_experimental.text_splitter import SemanticChunker
from langchain_openai.embeddings import OpenAIEmbeddings
splitter = SemanticChunker(
OpenAIEmbeddings(),
breakpoint_threshold_type="percentile" # 或 "standard_deviation"
)
chunks = splitter.create_documents([text])
理论上很美,实际有坑:
- 每次切分都要调 embedding API,成本高、速度慢
- 基准测试里准确率反而低于 Recursive(54% vs 69%)
- chunk 大小高度不稳定(最小 43 tokens,最大几千 tokens)
结论:适合实验,不建议直接上生产。
策略 5:Proposition Chunking(原子级)
原理:用 LLM 把文档分解成最小的自包含命题单元------每个 chunk 是一个独立的事实陈述。
示例:
yaml
原文:"GPT-4 于 2023 年 3 月发布,支持 128K 上下文窗口,定价为每百万 input token $30。"
→ Proposition 1: GPT-4 于 2023 年 3 月发布。
→ Proposition 2: GPT-4 支持 128K 上下文窗口。
→ Proposition 3: GPT-4 定价为每百万 input token $30。
优势:精确引用(page F1 达 0.97),接地性最高(87%)。
代价:每个文档都要跑一遍 LLM,成本极高,索引体积膨胀(平均 17 tokens/chunk,需要 top-115 才能覆盖足够上下文)。
适合:需要精确引用的高价值文档(法律、医疗、合规),而非海量文档的通用场景。
策略 6:Document-Structure-First(结构化文档专用)
核心思想:不要"切文档",而是"理解文档"。
流程:
- 保留完整文档,不破坏结构
- 提取真实段落/章节(而不是按 token 数量切)
- 给每个章节附上结构化 metadata
- 用语义 + 结构双通道检索
- 返回答案时带上引用/证据
- 让模型显式声明"检索不完整"(很多人忽视这一步)
python
# 概念实现
def index_contract(doc_path):
doc = parse_document(doc_path)
for section in doc.sections:
chunk = {
"content": section.text,
"metadata": {
"section_type": section.type, # "obligation" / "definition" / "payment_term"
"parties": section.parties,
"dates": section.dates,
"clause_id": section.id,
"dependencies": section.references # 引用了哪些其他条款
}
}
index(chunk)
适合:合同、法规、标准文档------结构信息本身就是答案的一部分。
四、选型决策树
sql
你的文档是什么类型?
│
├── Markdown / 有清晰标题层级
│ └── → Markdown Header Chunking + 标题 metadata
│
├── 长文档,需要精确定位 + 完整上下文
│ └── → Parent-Child Chunking(128/512/1024)
│
├── 合同 / 法律 / 结构化业务文档
│ └── → Document-Structure-First + 结构化 metadata
│
├── 需要精确引用 + 预算充足
│ └── → Proposition Chunking(LLM 辅助)
│
└── 其他(博客/文档/报告/通用场景)
└── → Recursive Character Splitting,chunk_size=400~512,overlap=10%
五、几个常被忽视的工程细节
关于 overlap :2026 年 1 月的一项研究发现,在 SPLADE 检索 + Mistral-8B 的组合下,overlap 对准确率没有可测量的提升。Overlap 不是万金油,要根据你的检索方式测试。
关于 chunk size:越小越精准,但 top-k 需要越大,上下文拼接成本越高。越大覆盖越完整,但语义越稀释。400-512 tokens 是目前经验最优的平衡点。
关于 PDF 处理 :结构化 chunking 的前提是文档解析质量。推荐先用 marker-pdf 或 MarkItDown 转成干净的 Markdown,再做 chunking,效果远好于直接在原始 PDF 文本上操作。
关于评估:换了 chunking 策略之后,一定要跑一遍评估(用 RAGAS 或自建测试集),不要靠感觉判断好坏。
六、总结
| 场景 | 推荐策略 |
|---|---|
| 快速上线,通用文档 | Recursive 512 |
| 技术文档 / Markdown | Markdown Header |
| 长文档,精确 + 完整两手抓 | Parent-Child |
| 合同 / 法律 / 结构化 | Document-Structure-First |
| 需要精确引用,预算充足 | Proposition Chunking |
Chunk 不是越"聪明"越好。实测数据显示,朴素的 Recursive 512 在大多数场景下依然最稳。
但对于结构化文档,标准的 token 切分从设计上就是错的------你切掉的不只是文字,而是文档的语义骨架。
"Agents 需要的不是附近 chunk 的模糊感觉,而是可靠的状态。"
选对 chunking 策略,是让 RAG 从"能用"到"好用"最关键的一步。