RAG 分块策略


文档摘要

RAG 分块策略 本节摘要:分块配置对检索质量的影响,堪比嵌入模型的选择(Vectara,NAACL 2025)。分块搞错了,再多重排也救不回。你把一份 50 页合同塞进 RAG,用户问「解约条款是什么?」,检索器却返回封面页——为什么?因为模型在 512 token 的块上训过,解约条款埋在第 20 页、跨页断裂、局部无关键词与查询挂钩。修法不是「买个更好的嵌入模型」,而是分块:多大?重叠?在哪切?要不要带上下文?2026 年 2 月的基准给出反直觉结果——Vectara 研究里递归式 512 token 分块以 69% 对 54% 击败语义分块;SPLADE + Mistral-8B 上重叠带来零可测收益;2,500 token 上下文处存在「上下文悬崖」,响应质量骤降。

RAG 分块策略

本节摘要:分块配置对检索质量的影响,堪比嵌入模型的选择(Vectara,NAACL 2025)。分块搞错了,再多重排也救不回。你把一份 50 页合同塞进 RAG,用户问「解约条款是什么?」,检索器却返回封面页——为什么?因为模型在 512 token 的块上训过,解约条款埋在第 20 页、跨页断裂、局部无关键词与查询挂钩。修法不是「买个更好的嵌入模型」,而是分块:多大?重叠?在哪切?要不要带上下文?2026 年 2 月的基准给出反直觉结果——Vectara 研究里递归式 512 token 分块以 69% 对 54% 击败语义分块;SPLADE + Mistral-8B 上重叠带来零可测收益;2,500 token 上下文处存在「上下文悬崖」,响应质量骤降。「显而易见」的答案(语义分块、20% 重叠、1000 token)常常是错的。本节为六种策略(定长、递归、语义、句子、父文档、晚期分块)外加上下文检索建立直觉,告诉你何时取用何者——并把一条压过所有默认的规则交给你:把块大小匹配到查询类型

对应原课程:Phase 5 · Lesson 23 · chunking-strategies-rag(原英文 phases/05-nlp-foundations-to-advanced/23-chunking-strategies-rag/docs/en.md)。前置依赖:第 14 节(信息检索)、第 22 节(嵌入模型)。

学习目标

阅读完本节,你应当能够:

  1. 说清六种分块策略(定长、递归、语义、句子、父文档、晚期分块)与上下文检索各自解决什么。
  2. 块大小匹配到查询类型(事实型 256512、分析/多跳 5121024、整节理解 1024~2048)。
  3. 从零实现递归、语义、父子文档与上下文检索分块器,并用 recall@k 评估。
  4. 识别 2026 基准带来的反直觉教训:重叠常常零收益、上下文悬崖、语义分块需最小 token 地板

一、问题与直觉

你把一份 50 页合同塞进 RAG 系统。用户问:「解约条款是什么?」检索器返回封面页。为什么?因为模型在 512 token 的块上训过,解约条款埋在第 20 页、跨页断裂、局部无关键词与查询挂钩。

修法不是「买个更好的嵌入模型」,而是分块。多大?重叠?在哪切?带不带周围上下文?

2026 年 2 月的基准给出反直觉结果:

  • Vectara 2026 研究:递归式 512 token 分块以 69% 对 54% 击败语义分块。
  • SPLADE + Mistral-8B 在 Natural Questions 上:重叠带来零可测收益。
  • 上下文悬崖:响应质量在约 2,500 token 上下文处骤降。

「显而易见」的答案(语义分块、20% 重叠、1000 token)常常是错的。

  • 定长分块:每 N 字符或 token 切一刀。最简基线,会断在句中,压缩好、连贯差。
  • 递归分块:LangChain 的 RecursiveCharacterTextSplitter。先试 \n\n,再 \n,再 .,再空格。优雅回退。2026 默认。
  • 语义分块:嵌入每句,算相邻句余弦相似度,在相似度跌破阈值处切。保留话题连贯;慢,有时产出 40 token 的碎片伤检索。
  • 句子分块:在句界切,一块一句或 N 句窗口。5k token 内逼近语义分块,成本却低得多。
  • 父文档:存小的子块用于检索,大的父块用于上下文。按子检索、返回父。优雅退化:差的子块仍能返回合理的父。
  • 晚期分块(2024):先把整篇文档在 token 级嵌入,再把 token 嵌入池化成块嵌入。保留跨块上下文,需长上下文嵌入器(BGE-M3、Jina v3)。算力更高。
  • 上下文检索(Anthropic, 2024):给每块前缀一个 LLM 生成的、它在文档中位置的摘要(「本块是解约条款的第 3.2 节……」)。Anthropic 自家基准检索提升 35~50%。索引贵。

压过所有默认的规则

把块大小匹配到查询类型:

查询类型 块大小
事实型(「CEO 叫什么?」) 256~512 token
分析型 / 多跳 512~1024 token
整节理解 1024~2048 token

💡 NVIDIA 2026 基准:块应大到装得下答案加局部上下文,小到检索器的 top-K 聚焦在答案而非上下文噪声上。

二、从零实现

第 1 步:定长与递归分块

def chunk_fixed(text, size=512, overlap=0): step = size - overlap return [text[i:i + size] for i in range(0, len(text), step)] def chunk_recursive(text, size=512, seps=("\n\n", "\n", ". ", " ")): if len(text) <= size: return [text] for sep in seps: if sep not in text: continue parts = text.split(sep) chunks = [] buf = "" for p in parts: if len(p) > size: if buf: chunks.append(buf) buf = "" chunks.extend(chunk_recursive(p, size=size, seps=seps[1:] or (" ",))) continue candidate = buf + sep + p if buf else p if len(candidate) <= size: buf = candidate else: if buf: chunks.append(buf) buf = p if buf: chunks.append(buf) return [c for c in chunks if c.strip()] return chunk_fixed(text, size)

第 2 步:语义分块

def chunk_semantic(text, encoder, threshold=0.6, min_chars=200, max_chars=2048): sentences = split_sentences(text) if not sentences: return [] embs = encoder.encode(sentences, normalize_embeddings=True) chunks = [[sentences[0]]] for i in range(1, len(sentences)): sim = float(embs[i] @ embs[i - 1]) current_len = sum(len(s) for s in chunks[-1]) if sim < threshold and current_len >= min_chars: chunks.append([sentences[i]]) else: chunks[-1].append(sentences[i]) result = [] for group in chunks: text_group = " ".join(group) if len(text_group) > max_chars: result.extend(chunk_recursive(text_group, size=max_chars)) else: result.append(text_group) return result

threshold 在你领域上调。过高→碎片;过低→一个巨型块。

第 3 步:父文档

def chunk_parent_child(text, parent_size=2048, child_size=256): parents = chunk_recursive(text, size=parent_size) mapping = [] for p_idx, parent in enumerate(parents): children = chunk_recursive(parent, size=child_size) for child in children: mapping.append({"child": child, "parent_idx": p_idx, "parent": parent}) return mapping def retrieve_parent(child_query, mapping, encoder, top_k=3): child_embs = encoder.encode([m["child"] for m in mapping], normalize_embeddings=True) q_emb = encoder.encode([child_query], normalize_embeddings=True)[0] scores = child_embs @ q_emb top = np.argsort(-scores)[:top_k] seen, parents = set(), [] for i in top: if mapping[i]["parent_idx"] not in seen: parents.append(mapping[i]["parent"]) seen.add(mapping[i]["parent_idx"]) return parents

关键洞见:父块去重。多个子块可能映射到同一父块,全返回会浪费上下文。

第 4 步:上下文检索(Anthropic 模式)

def contextualize_chunks(document, chunks, llm): context_prompts = [ f"""<document>{document}</document> Here is the chunk to situate: <chunk>{c}</chunk> Write 50-100 words placing this chunk in the document's context.""" for c in chunks ] contexts = llm.batch(context_prompts) return [f"{ctx}\n\n{c}" for ctx, c in zip(contexts, chunks)]

索引带上下文化的块。查询时,检索从额外的周围信号里受益。

第 5 步:评估

def recall_at_k(queries, corpus_chunks, encoder, k=5): chunk_embs = encoder.encode(corpus_chunks, normalize_embeddings=True) hits = 0 for q_text, gold_idxs in queries: q_emb = encoder.encode([q_text], normalize_embeddings=True)[0] top = np.argsort(-(chunk_embs @ q_emb))[:k] if any(i in gold_idxs for i in top): hits += 1 return hits / len(queries)

总要基准。「最佳」策略未必匹配任何博客帖。

三、框架对比

陷阱

  • 只在事实型查询上评估分块:多跳查询会揭示截然不同的赢家。用按查询类型分层的评估集。
  • 语义分块无最小尺寸:产出 40 token 碎片伤检索。总强制 min_tokens
  • 重叠当货物崇拜:2026 研究发现重叠常常零收益,却让索引成本翻倍。要测,不要假设。
  • 无 min/max 强制:5 token 与 5000 token 的块都会坏检索。要钳制。
  • 跨文档分块:永远别让一块跨两篇文档。总按文档分块,再合并。

2026 的栈:

情形 策略
首次构建,语料未知 递归,512 token,无重叠
事实型 QA 递归,256~512 token
分析型 / 多跳 递归,512~1024 token + 父文档
交叉引用密集(合同、论文) 晚期分块或上下文检索
对话型 / 对话语料 轮次级块 + 说话人元数据
短话术(推文、评论) 一文档 = 一块

💡 2026 模式:从递归 512 起步,在 50 查询评估集上测 recall@5,再据此调。

四、可复用产物

保存为 outputs/skill-chunker.md:

--- name: chunker description: Pick a chunking strategy, size, and overlap for a given corpus and query distribution. version: 1.0.0 phase: 5 lesson: 23 tags: [nlp, rag, chunking] --- Given a corpus (document types, avg length, domain) and query distribution (factoid / analytical / multi-hop), output: 1. Strategy. Recursive / sentence / semantic / parent-document / late / contextual. Reason. 2. Chunk size. Token count. Reason tied to query type. 3. Overlap. Default 0; justify if >0. 4. Min/max enforcement. `min_tokens`, `max_tokens` guards. 5. Evaluation plan. Recall@5 on 50-query stratified eval set (factoid, analytical, multi-hop). Refuse any chunking strategy without min/max chunk size enforcement. Refuse overlap above 20% without an ablation showing it helps. Flag semantic chunking recommendations without a min-token floor.

五、练习

  1. 基础:把一份 20 页文档分别用 fixed(512,0)、recursive(512,0)、recursive(512,100) 分块,对比块数与边界质量。
  2. 进阶:在 5 篇文档上构一个 30 查询评估集,测递归、语义、父文档三者的 recall@5。哪个赢?和博客帖一致吗?
  3. 挑战:实现上下文检索,测相对递归基线的 MRR 提升,报告索引成本(LLM 调用)对准确率增益。

本节要点回顾

  1. 分块影响堪比嵌入选择:搞错再多重排也救不回,Vectara NAACL 2025 已量化。
  2. 六种策略:定长、递归(2026 默认)、语义、句子、父文档、晚期分块,外加上下文检索。
  3. 递归分块优雅回退:先 \n\n\n. 再空格,LangChain RecursiveCharacterTextSplitter
  4. 语义分块按相邻句余弦跌切:保话题连贯,但易生 40 token 碎片,需 min_tokens
  5. 父文档两段式:小子块检索、大父块回填,父块要去重以免浪费上下文。
  6. 晚期分块先 token 级嵌再池化:保留跨块上下文,需长上下文嵌入器。
  7. 上下文检索前缀 LLM 位置摘要:Anthropic 基准提升 35~50%,索引贵。
  8. 块大小匹配查询类型:事实 256512、分析/多跳 5121024、整节 1024~2048。
  9. 重叠常常零收益:2026 研究发现常零收益却翻倍索引成本,要测不要假设。
  10. 上下文悬崖约 2500 token:超过响应质量骤降;无 min/max 强制、跨文档分块都会坏检索。

下一节,我们钻进一个经典难题——进入「共指消解」,看如何把「他」「她」「它」这些代词指回正确的实体,以及为何这是机器阅读理解与信息抽取的隐性地基。


发布者: 作者: Rohit Gupta 转发
评论区 (0)
U