RAG 分块策略选型:5 种 Chunking 实测对比,Document-aware 召回率高 15 个点
我见过太多 RAG 项目的分块配置:
RecursiveCharacterTextSplitter(chunk_size=512, chunk_overlap=50),跑完一看召回率 70% 出头,然后开始疯狂调 Embedding 模型、换向量库、加 Reranker。方向错了。问题不在检索,在切分–你第一刀就切歪了,后面怎么调都补不回来。
这篇把 5 种主流分块策略逐层拆开,每种有 benchmark 数据,最后给场景决策树。你的文档该用哪种切法,一眼看清。
分块不是预处理步骤,是检索质量的第一道天花板。某企业文档 benchmark 的实测数据:Document-aware 分块 Recall@5 达 86.7%,而最常用的 Fixed-size 仅 71.3%–差了 15.4 个百分点,相对提升 21.6%【S3】。更反直觉的是,这两种策略的代码量差不多,区别只在于你切的时候有没有看文档结构。
一、固定切分为什么注定有天花板
先说清楚问题根源。固定 512 切分有两个结构性缺陷,不是调参能填平的。
缺陷 1:语义割裂。一个完整的论证被从中间截断–比如某段先说"退款政策分为三种情况",紧接着列条件 A、B、C。固定切分可能把"三种情况"这句话和条件 A 放在一个 chunk,条件 B、C 被切到下一个 chunk。用户问"条件 B 是什么",向量检索命中的 chunk 里根本没有 B 的上下文,召回了一个"半句话"。
缺陷 2:上下文丢失。这是更隐蔽的问题。一篇关于柏林的维基百科文章,第一句是"Berlin is the capital and largest city of Germany"。第二句"Its more than 3.85 million inhabitants make it…"–这个"Its"指代的是 Berlin。传统先切分再嵌入的做法,第二句被切成独立 chunk 后做 embedding,"Its"失去了指代对象,向量质量直接下降。Jina AI 的实验显示,这句话与"Berlin"的查询相似度从 0.849(第一句,含 Berlin)降到 0.708(第二句,Its 指代丢失)【S1】。
| 问题类型 | 固定切分表现 | 后果 |
|---|---|---|
| 完整论证被截断 | 半句话进索引 | 召回的 chunk 缺关键上下文 |
| 代词/指代丢失 | “Its”"该产品"无指代对象 | embedding 方向偏移,相似度下降 |
| 表格/列表被打散 | 行被切到不同 chunk | 结构信息全丢 |
| 标题与正文分离 | 标题一个 chunk,正文一个 chunk | 检索命中正文但不知属于哪节 |
核心矛盾:chunk 切得小,向量语义聚焦,检索精准;但 LLM 拿到一个 150-token 的小 chunk,往往缺少足够上下文,答案质量下降。chunk 切得大,语义稀释,召回率反而变差。这个矛盾不是调 chunk_size 能解决的,得换策略。
这就是分块策略的起点:让切法适配文档结构,而不是让文档硬塞进固定大小。
二、五种分块策略逐层拆解
先上一张总表,把五种策略的 benchmark 数据摆出来,再说每种适合什么场景。
某企业文档 benchmark 的实验设置:4 类企业文档(合同/技术手册/财务报告/工单)各 50 篇共 200 篇,每类 100 组 ground-truth QA;Embedding 模型 OpenAI text-embedding-3-large(3072 维);向量库 Qdrant(HNSW);目标 chunk 512 tokens;Overlap 20%【S3】。
| 策略 | Recall@5 | 查询延迟 | Token 效率 | 索引大小 |
|---|---|---|---|---|
| Document-aware | 86.7% | 16ms | 0.88x(最优) | 0.91x |
| Semantic | 83.2% | 38ms(最高) | 0.92x | 0.95x |
| Recursive | 78.6% | 14ms | 1.05x | 1.02x |
| Sliding window | 76.8% | 13ms | 1.82x(最低效) | 1.45x |
| Fixed-size | 71.3%(最低) | 12ms(最快) | 1.0x(基线) | 1.0x |
数据来源标注:S3 为某技术博客的企业文档 benchmark,单一信源,核心结论已由 JavaGuide 的独立测试(逻辑边界 87% vs 固定大小 50%,p=0.001)交叉印证【S4】。以下展开说每种策略。
策略 1:Fixed-size(固定大小)–最常用,但最差
就是按固定 token 数硬切,可选 overlap。绝大多数教程和 LangChain 默认模板用的就是这种。
from langchain.text_splitter import CharacterTextSplittersplitter = CharacterTextSplitter( chunk_size=512, chunk_overlap=50, separator="\n\n")chunks = splitter.split_text(document)为什么最常用:实现最简单,零依赖,所有框架开箱即用。
为什么最差:它完全无视文档结构–标题、段落、表格、列表全被一刀切断。一篇合同里的"第一条 退款条件"可能被从中间劈开,"第二条"的开头粘在"第一条"的尾巴上。
我的判断:Fixed-size 适合做原型快速验证、跑通 pipeline 流程,但不该进生产。如果你的知识库只有几十篇文档且都是短文本,固定切分够用;一旦上了几百篇结构化文档,它的召回率天花板就卡死在 70% 出头。
策略 2:Recursive character(递归字符)–通用首选
按分隔符优先级递归切分:先按段落切,段落太大就按句子切,句子还太大就按词切。尽量在自然断点处分割。
from langchain.text_splitter import RecursiveCharacterTextSplittersplitter = RecursiveCharacterTextSplitter( chunk_size=512, chunk_overlap=50, separators=["\n\n\n", "\n\n", "\n", "。", "!", "?", ",", " ", ""], # 优先级:三空行 > 双空行 > 单换行 > 句号 > 感叹号 > 问号 > 逗号 > 空格 > 字符)chunks = splitter.split_text(document)与 Fixed-size 的区别:同样是目标 512 tokens,但 Recursive 会先尝试在段落边界切,切不下去才降级到句子、再到词。结果是大块更可能对齐自然语义边界。
benchmark 数据:Recall@5 从 71.3% 提到 78.6%,提升 7.3 个百分点,代码改动量几乎为零–只换了 splitter 类名和分隔符列表【S3】。
适用场景:通用知识库、混合文档类型、不知道该选什么时的默认方案。如果你的文档类型杂(既有 Markdown 又有纯文本又有 HTML),Recursive 是最稳的保底策略。
策略 3:Semantic(语义分块)–非结构化最强,但最贵
用 Embedding 模型检测相邻句子的语义边界。相邻句子 cosine 相似度低于阈值时切一刀,把语义相近的句子聚成一组。
from semantic_chunker import SemanticChunker# 基于句子嵌入相似度检测主题边界chunker = SemanticChunker( embedder=embeddings, # 传入 embedding 模型 breakpoint_threshold=0.75, # 相似度低于此值时切分 min_chunk_size=200 # ⚠️ 这个参数很关键,见下方说明)chunks = chunker.split_text(document)反直觉的坑:语义切分看起来很高级,但实测平均块大小仅43 Token–远低于预期的 512【S4】。原因是默认参数太激进,几乎每个句子边界都会触发切分。你必须设min_chunk_size(建议 200~400 Token),否则效果比 Fixed-size 还差。
benchmark 数据:调好参数后 Recall@5 达 83.2%,在工单等非结构化文档上更是达到87.0%,超过 Document-aware 的 78.0%【S3】。
代价:索引时间是其他策略的4~5 倍–200 篇文档要 22 分钟(Fixed-size 仅 4 分钟),10,000 篇要 18.4 小时(Fixed 仅 3.2 小时)。因为每个句子都要算 embedding 来检测主题边界【S3】。
适用场景:非结构化文本(工单、邮件、聊天记录、客服对话)。这类文档没有标题/章节可利用,Document-aware 无从下手,Semantic 是唯一能检测语义边界的方案。
策略 4:Document-aware(文档感知)–结构化文档王者
利用文档自身的结构信息定义分块边界:按标题、章节、表格行、列表项切分。
from langchain.text_splitter import MarkdownHeaderTextSplitter# 按 Markdown 标题层级切分headers_to_split = [ ("#", "Header 1"), ("##", "Header 2"), ("###", "Header 3"),]splitter = MarkdownHeaderTextSplitter(headers_to_split_on=headers_to_split)chunks = splitter.split_text(markdown_doc)# 每个 chunk 自动携带 metadata:# {'Header 1': '退款政策', 'Header 2': '条件 B', 'Header 3': '时间限制'}核心优势:切出来的每个 chunk 天然对齐语义单元–一个章节、一个条款、一个表格行。而且 metadata 自动生成(标题路径),检索时可以按章节过滤,也可以在答案中标注"来自《退款政策》条件 B"。
benchmark 数据:Recall@5 达86.7%,比 Fixed-size 高 15.4 个百分点。在合同(89.0%)、技术手册(91.0%)、财务报告(88.0%)上全面领先【S3】。
另一个加成:给 chunk 附加 metadata(文档标题、章节标题、页码)可将检索准确率再提升8~12%(某 benchmark 测试)【S3】。Document-aware 天然带 metadata,这个加成几乎白捡。
适用场景:有明确结构的文档–Markdown、HTML、PDF(带书签)、Word(带样式)、合同/手册/报告。只要文档有标题层级或章节划分,Document-aware 就应该首选。
策略 5:Late Chunking(延迟分块)–上下文派折中方案
前面四种都是"先切再嵌",Late Chunking 反过来:先对整篇文档做 embedding 生成 token 级向量,再按 chunk 边界做 mean pooling。每个 chunk 的嵌入自动"条件化"于前文上下文。
# Late Chunking 核心思路(需长上下文 embedding 模型)# 1. 整篇文档送入模型,获取 token 级向量model = AutoModel.from_pretrained("jinaai/jina-embeddings-v3", trust_remote_code=True)# jina-embeddings-v3 支持 8192 tokens 长上下文inputs = tokenizer(document, return_tensors="pt", max_length=8192)with torch.no_grad(): outputs = model(**inputs)token_embeddings = outputs.last_hidden_state # [seq_len, dim]# 2. 按 chunk 边界做 mean pooling(而非对单个 chunk 做 pooling)# 假设 chunk_boundaries = [(0, 120), (120, 280), (280, 450), ...]chunk_embeddings = []for start, end in chunk_boundaries: chunk_emb = token_embeddings[start:end].mean(dim=0) chunk_embeddings.append(chunk_emb)为什么有效:回到柏林文章的例子。第二句"Its more than 3.85 million inhabitants…“在传统切分后独立 embedding,与"Berlin"查询的相似度只有 0.708。用 Late Chunking 后,模型处理整篇文档时"Its"已经关联到前文的"Berlin”,pooling 后的 chunk 嵌入保留了这个关联–相似度提升到0.825【S1】。
BeIR 数据集实测:
| 数据集 | 平均文档长度(字符) | 传统切分 | Late Chunking | 提升 |
|---|---|---|---|---|
| SciFact | 1,498 | 64.20% | 66.10% | +1.90pp |
| NFCorpus | 1,590 | 23.46% | 29.98% | +6.52pp |
| FiQA2018 | 767 | 33.25% | 33.84% | +0.59pp |
| TRECCOVID | 1,117 | 63.36% | 64.70% | +1.34pp |
数据来源:Jina AI 官方实验,jina-embeddings-v2-small-en,chunk 约 256 tokens【S1】。
关键发现:改善幅度与文档平均长度正相关–文档越长,上下文丢失问题越严重,Late Chunking 优势越大。NFCorpus(1,590 字符)提升 6.52pp,而 Quora(62 字符,极短)提升为 0pp。
限制条件:
- • 依赖长上下文嵌入模型(需支持 8192 tokens,如 jina-embeddings-v2/v3)
- • 超过 8192 tokens(约 10 页文本)的文档仍受限,上下文覆盖不到
- • 仍需边界线索(用 Recursive 或 Document-aware 的边界),只是延后了 pooling 时机
三、进阶:Contextual Retrieval–给每个 chunk 注入文档上下文
如果说 Late Chunking 是"先嵌再切"的折中,Anthropic 的 Contextual Retrieval 走了另一条路:切分之后,给每个 chunk 补一段文档级上下文,再一起 embed。
做法很直白:用 Claude 为每个 chunk 生成 50~100 token 的上下文摘要,prepend 到原始 chunk 前。比如原始 chunk 是"The company’s revenue grew by 3% over the previous quarter",加上下文后变成"This chunk is from an SEC filing on ACME corp’s performance in Q2 2023; the previous quarter’s revenue was $314 million. The company’s revenue grew by 3% over the previous quarter."【S2】
效果数据(Anthropic 官方实验,评估指标 = 检索失败率 = 1 - Recall@20):
| 技术组合 | 失败率 | 相对降幅 |
|---|---|---|
| 基线(传统方法) | 5.7% | - |
| 仅 Contextual Embeddings | 3.7% | ↓ 35% |
| Contextual Embeddings + BM25 | 2.9% | ↓ 49% |
| 上述 + Reranking | 1.9% | ↓ 67% |
实验设置:数据集 = 代码库/小说/ArXiv 论文/科学论文;Chunk = 800 tokens;文档 = 8k tokens;Reranker = Cohere(top 150 -> top 20)【S2】。
成本:利用 Claude prompt caching,文档加载一次缓存、各 chunk 共享。800-token chunk / 8k 文档 / 100-token 上下文,一次性成本约$1.02 / 百万文档 tokens【S2】。这是一次性索引成本,不是每次查询的运行时成本。
与 Late Chunking 的区别:
| 维度 | Late Chunking | Contextual Retrieval |
|---|---|---|
| 思路 | 先嵌再切,pooling 时保留上下文 | 切后补上下文,再 embed |
| 依赖 | 长上下文 embedding 模型(8192) | 任意 embedding 模型 + LLM |
| 额外成本 | 无额外 LLM 调用 | 每 chunk 1 次 LLM 调用(可缓存摊薄) |
| 上下文来源 | 模型隐式学习 | LLM 显式生成摘要 |
| 限制 | 超 8192 tokens 仍受限 | 无长度限制,但长文档摘要质量可能下降 |
我的判断:如果你的 embedding 模型支持长上下文(jina-embeddings-v3 / Voyage),Late Chunking 更省成本。如果你用的是 OpenAI text-embedding-3 或其他短上下文模型,Contextual Retrieval 是更现实的方案–代价是多一次 LLM 调用,但 prompt caching 把成本压到了 $1.02/M。
四、完整 Pipeline 与场景决策树
生产级分块链路
原始文档 │ ▼ 【解析层】 PDF/Word/HTML -> 结构化文本 + metadata(标题路径、页码) │ ▼ 【分块策略选择】 ├─── 有标题/章节结构? ──> Document-aware(首选) ├─── 纯文本无结构? ──> Recursive(保底)或 Semantic(非结构化) ├─── 长文档+代词密集? ──> Late Chunking(需长上下文模型) └─── 高精度要求? ──> Document-aware + Contextual Retrieval │ ▼ 【chunk 大小校验】 目标 256~768 tokens;<200 补上下文,>1000 拆分 │ ▼ 【metadata 附加】 文档标题 + 章节路径 + 页码 + 来源(+8~12% 准确率) │ ▼ 【embedding + 索引】 支持 Late Chunking 的模型 -> 先嵌再切 普通模型 -> 切后 embed(可加 Contextual Retrieval)场景选型决策树
| 你的场景 | 推荐策略 | 理由 |
|---|---|---|
| 合同/法律/政策文档(有条款编号) | Document-aware | 合同 89% Recall,结构切分天然对齐条款 |
| 技术手册/API 文档(有标题层级) | Document-aware | 手册 91% Recall,标题路径可做 metadata 过滤 |
| 财务报告(表格+叙述混合) | Document-aware+ 保留表格完整性 | 报告 88% Recall;注意 FinanceBench 上 1024-token 反超页面级【S4】 |
| 工单/邮件/聊天记录(无结构) | Semantic(调好 min_chunk_size) | 工单 87% Recall,Document-aware 无结构可利用 |
| 长文档(>5 页,代词/指代密集) | Late Chunking | 保留跨 chunk 上下文依赖,NFCorpus +6.52pp |
| 混合文档类型(什么都有) | Recursive(保底)+ 按类型路由 | 78.6% Recall,不偏科,实现简单 |
| 召回质量要求极高(>85%) | Document-aware +Contextual Retrieval | 上下文注入 + Reranking 失败率降 67%【S2】 |
| 快速原型验证 | Fixed-size | 71.3% 够用,零配置,先跑通再说 |
💡 这张表建议收藏,下次搭 RAG 分块层时对照选型。
五、三个反直觉发现
发现 1:不切分有时比分块更好
Jina AI 的 BeIR 实验里有个反直觉数据:TRECCOVID 和 NFCorpus 两个数据集上,"不切分"整文档编码的 nDCG@10 反而最高(65.18% 和 30.40%),比分块更好【S1】。
原因不难理解:不切分就没有上下文丢失问题,整篇文档的 embedding 是完整的。但现实是,大多数 RAG 场景不可能不切分–文档太长塞不进 embedding 模型,或者 LLM 上下文窗口放不下检索结果。
这就是 Late Chunking 的价值:在"必须切分"的约束下,尽量逼近"不切分"的效果。
发现 2:语义切分的参数陷阱
语义切分实测平均块大小仅43 Token,远低于预期【S4】。默认参数太激进,几乎每个句子边界都触发切分,结果是 chunk 太碎、上下文太短、召回率反而不如 Fixed-size。
解法很简单但很多人不知道:设min_chunk_size(建议 200~400 Token),强制最小块大小。调好之后 Semantic 才能在非结构化文档上发挥 87% 的实力。
发现 3:页面级切分不是万能解
NVIDIA 在金融报告和法律文档上的测试发现:Page-Level Chunking(按页切)平均准确率 0.648,方差最低,看似稳妥。但在 FinanceBench 上,1024-token 切分反而优于页面级(0.579 vs 0.566)【S4】。
原因是金融文档的表格常跨页–按页切会把一个完整表格劈成两半。1024-token 的 token 级切分虽然不那么"干净",但在数值密集场景反而保留了更完整的上下文。
教训:没有一种切法适用所有场景。Page-Level 看起来安全,在表格密集场景照样翻车。
总结
核心判断(每条均有来源支撑):
1. Fixed-size 是最常用的策略,但 Recall@5 最低(71.3%),比 Document-aware(86.7%)差 15.4 个百分点。【S3】 如果你现在用的是chunk_size=512的固定切分,光换策略就能涨 15 个点,不需要换 Embedding 模型。
2. 最好的分块不是一刀切,是看文档类型选策略。结构化文档用 Document-aware,非结构化用 Semantic,长文档用 Late Chunking。混合方案可达 89~92% Recall@5(原作者结论,未独立复现)【S3】。
3. Late Chunking 在所有 BeIR 数据集上均优于传统切分,且文档越长优势越大。【S1】 代价是依赖长上下文 embedding 模型(8192 tokens)。
4. Contextual Retrieval 给每个 chunk 注入文档上下文,检索失败率直降 67%(+Reranking)。【S2】 一次性索引成本仅 $1.02/M 文档 tokens,是精度提升性价比最高的方案。
5. Chunk 大小最佳区间是 256~768 tokens。【S3】 低于 200 丢失上下文,高于 1000 稀释相关性。元数据附加(标题、章节、页码)可再提升 8~12%。
对你的实际建议:
- •正在搭第一版 RAG 的开发者:先用 Recursive 替掉 Fixed-size,代码改一行,Recall@5 从 71% 涨到 79%。这是 ROI 最高的一步。
- •知识库是结构化文档(合同/手册/报告)的团队:直接上 Document-aware,按标题/章节切。配合 metadata 附加,轻松到 86%+。
- •文档是非结构化文本(工单/聊天/邮件)的团队:Semantic 调好
min_chunk_size(200~400),否则默认参数会切出 43-token 的碎片。 - •长文档+高精度要求的场景:Document-aware 切边界 + Contextual Retrieval 注入上下文 + Reranker 精排,三件套把失败率压到 1.9%【S2】。
- •关心成本的团队:检索阶段占 RAG 总 token 消耗的 40~60%(行业工程经验)【S5】。Chunk 从 3000 缩到 1500 Token 可省约 50% 输入成本,但别低于 200–否则答案质量会断崖。
一句话结语:RAG 的召回瓶颈不一定是 Embedding 模型不够好,很可能第一刀就切歪了。换切法比换模型便宜得多。
学AI大模型的正确顺序,千万不要搞错了
🤔2026年AI风口已来!各行各业的AI渗透肉眼可见,超多公司要么转型做AI相关产品,要么高薪挖AI技术人才,机遇直接摆在眼前!
有往AI方向发展,或者本身有后端编程基础的朋友,直接冲AI大模型应用开发转岗超合适!
就算暂时不打算转岗,了解大模型、RAG、Prompt、Agent这些热门概念,能上手做简单项目,也绝对是求职加分王🔋
📝给大家整理了超全最新的AI大模型应用开发学习清单和资料,手把手帮你快速入门!👇👇
学习路线:
✅大模型基础认知—大模型核心原理、发展历程、主流模型(GPT、文心一言等)特点解析
✅核心技术模块—RAG检索增强生成、Prompt工程实战、Agent智能体开发逻辑
✅开发基础能力—Python进阶、API接口调用、大模型开发框架(LangChain等)实操
✅应用场景开发—智能问答系统、企业知识库、AIGC内容生成工具、行业定制化大模型应用
✅项目落地流程—需求拆解、技术选型、模型调优、测试上线、运维迭代
✅面试求职冲刺—岗位JD解析、简历AI项目包装、高频面试题汇总、模拟面经
以上6大模块,看似清晰好上手,实则每个部分都有扎实的核心内容需要吃透!
我把大模型的学习全流程已经整理📚好了!抓住AI时代风口,轻松解锁职业新可能,希望大家都能把握机遇,实现薪资/职业跃迁~
