分块的两难困境
RAG 系统里有一个经典矛盾:
- Chunk 太小:向量匹配精准,但返回给 LLM 的内容是片段,缺乏上下文,无法完整回答问题
- Chunk 太大:内容完整,但语义太分散,embedding 质量下降,检索命中率降低
这不是调参能解决的问题,而是 Naive 分块的结构性缺陷。
小块适合检索,大块适合生成——这两个需求本来就是矛盾的,用同一个尺寸的 chunk 同时满足两者,必然顾此失彼。
本篇介绍两种突破这一困境的方案:
- Parent-Child Chunking:用小块做检索,命中后返回对应的大块
- Contextual Retrieval(Anthropic 方案):给每个 Chunk 加上文档上下文描述,让 embedding 更"聪明"
Parent-Child Chunking
核心思路
索引阶段: 父文档(800字)→ 存储在 docstore(InMemoryStore) ↓ 切割 子 Chunk(200字)→ 存入向量库 检索阶段: query → 向量检索匹配子 Chunk(精准) → 找到子 Chunk 对应的父文档 → 返回父文档给 LLM(完整)检索用的是小 chunk,LLM 拿到的是大 chunk。两个需求,各自最优,互不干扰。
代码实现
LangChain 的ParentDocumentRetriever封装了这个逻辑:
fromlangchain_classic.retrieversimportParentDocumentRetrieverfromlangchain_classic.storageimportInMemoryStorefromlangchain_text_splittersimportRecursiveCharacterTextSplitter child_splitter=RecursiveCharacterTextSplitter(chunk_size=200,# 小块:用于向量检索chunk_overlap=20,)parent_splitter=RecursiveCharacterTextSplitter(chunk_size=800,# 大块:命中后返回给 LLMchunk_overlap=50,)vectorstore=Chroma(collection_name="parent_child",embedding_function=embeddings)store=InMemoryStore()# 存储父文档的 docstoreretriever