Parent-child-Chunk
1895 字约 6 分钟
domain/aiai/rag
2026-07-24
Parent-child Chunk(父子分块)是 RAG 中缓解"检索精度 vs 上下文完整性"矛盾的经典架构。核心思想是用小 Chunk 检索,返回大 Chunk 上下文——Child Chunk 负责精确的向量匹配,Parent Chunk 负责为 LLM 提供充足的上下文信息。
一句话解释
检索的时候用小块(精确命中),返回的时候用大块(完整上下文)——两全其美。
核心问题
为什么需要 Parent-child 架构?
Chunk 设计面临一个根本矛盾(详见 Chunk设计):
- 小 Chunk(128-256 tokens):向量聚焦、检索精确,但上下文不完整
- 大 Chunk(512-1024 tokens):上下文完整,但语义被"平均化",检索精度下降
Parent-child 架构通过分层解决这个矛盾:检索层用小 Chunk 保证精度,返回层用 Parent Chunk 保证上下文。
完整工作流
文档处理阶段:
原始文档 → 切分为 Parent Chunks(512-1024 tokens)
→ 每个 Parent 进一步切分为 Child Chunks(128-256 tokens)
→ Child Chunks 建立向量索引,记录对应的 Parent ID
→ Parent Chunks 存储在文档库中
检索阶段:
用户 Query → 用 Child 索引做向量检索 → 命中 Top-K Child Chunks
→ 通过 Parent ID 找到对应的 Parent Chunks
→ Parent Chunks 去重(多个 Child 可能属于同一 Parent)
→ 将 Parent Chunks 作为上下文传给 LLM 生成回答层级索引策略
小 Chunk 索引 + 大 Chunk 存储
这是 Parent-child 的核心数据结构设计:
| 层级 | 大小 | 用途 | 存储位置 |
|---|---|---|---|
| Child Chunk | 128-256 tokens | 向量检索匹配 | 向量数据库索引 |
| Parent Chunk | 512-1024 tokens | 提供上下文给 LLM | 文档存储(KV Store / 原始文档) |
关键设计:每个 Child Chunk 的 Metadata 中记录其所属 Parent Chunk 的 ID,形成"子→父"的映射关系。
多层级扩展
Parent-child 可以扩展为多层级结构:
Level 0: 段落(Paragraph) — 50-100 tokens — 最精确检索
Level 1: 小节(Section) — 200-500 tokens — 中间层
Level 2: 章节(Chapter) — 1000-2000 tokens — 完整上下文检索时从 Level 0 开始匹配,命中后返回 Level 2 的完整上下文。层数越多灵活性越高,但管理复杂度也增加。
窗口扩展策略
Parent-child 的核心是"从小找大",但"大"的范围如何确定?有两种经典的窗口扩展策略:
1. Sentence Window(句子窗口)
原理:检索命中的句子(或段落)前后扩展 N 个句子作为上下文。
实现:
# 检索时扩展上下文
def sentence_window_retrieval(query, k=3, window_size=2):
# 1. 用句子级 Chunk 检索
hits = sentence_index.search(query, top_k=k)
# 2. 对每个命中,前后扩展 window_size 个句子
contexts = []
for hit in hits:
start = max(0, hit.sentence_index - window_size)
end = hit.sentence_index + window_size + 1
context = all_sentences[start:end]
contexts.append("".join(context))
return contexts参数选择:
window_size=1-2:紧凑上下文,适合 FAQ、技术文档window_size=3-5:宽泛上下文,适合叙事性文档、法律文本
2. Merge Context(合并上下文)
原理:当多个相邻的 Child Chunk 都被检索命中时,将它们合并为一个更大的上下文块。
实现逻辑:
- 检索 Top-K Child Chunks
- 按文档位置排序
- 相邻的 Child Chunks(属于同一 Parent)合并为一个上下文
- 合并后的上下文不超过 Parent Chunk 的边界
优势:避免了重复返回同一 Parent 的内容,同时保留了多个命中点的信息。
Parent-Child 的召回率-精确率权衡
| 维度 | 纯小 Chunk | 纯大 Chunk | Parent-child |
|---|---|---|---|
| 检索精度(Precision) | 高 | 低 | 高(用小 Chunk 检索) |
| 召回率(Recall) | 低 | 高 | 中-高(取决于 Parent 大小) |
| 上下文完整性 | 低 | 高 | 高(返回 Parent) |
| 索引体积 | 大(块数多) | 小 | 中(Child 索引 + Parent 存储) |
| LLM 生成质量 | 低(上下文不足) | 中(噪声多) | 高(上下文完整且精准) |
核心权衡:Parent 越大,上下文越完整,但引入的噪声也越多。Parent 越小,越接近纯小 Chunk 的效果,失去 Parent-child 的意义。
不同 Parent 窗口大小对生成质量的影响
| Parent 大小 | 适用场景 | 上下文完整性 | 噪声水平 | LLM 生成质量 |
|---|---|---|---|---|
| 256 tokens | 事实性 QA | 基本够用 | 低 | 中 |
| 512 tokens | 通用知识库 | 良好 | 中 | 高(推荐默认值) |
| 1024 tokens | 需要推理的复杂问题 | 完整 | 中-高 | 高(但注意上下文预算) |
| 2048+ tokens | 长文档分析、法律审查 | 非常完整 | 高 | 取决于 Rerank 质量 |
经验法则:Parent 大小 = Child 大小的 2-4 倍。Child 128 tokens → Parent 256-512 tokens。
LangChain ParentDocumentRetriever 实现分析
LangChain 提供了开箱即用的 ParentDocumentRetriever:
from langchain.retrievers import ParentDocumentRetriever
from langchain.storage import InMemoryStore
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
from langchain.text_splitter import RecursiveCharacterTextSplitter
# 定义 Parent 和 Child 的切分器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=1024, chunk_overlap=100)
child_splitter = RecursiveCharacterTextSplitter(chunk_size=256, chunk_overlap=30)
# 存储层:Parent Chunks 存储在 InMemoryStore 中
store = InMemoryStore()
# 向量索引:Child Chunks 存储在向量数据库中
vectorstore = Chroma(embedding_function=OpenAIEmbeddings())
# 创建 ParentDocumentRetriever
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
)
# 添加文档(自动完成 Parent/Child 切分和索引)
retriever.add_documents(documents)
# 检索(用 Child 检索,返回 Parent)
results = retriever.invoke("用户查询")内部流程:
add_documents时先用parent_splitter切分为 Parent,再用child_splitter切分为 Child- Child Chunks 存入 vectorstore,Parent Chunks 存入 docstore(KV 存储)
- 检索时先在 vectorstore 中搜索 Child,再通过 docstore 获取对应的 Parent
与 Chunk设计和文档切分策略的配合
Parent-child Chunk 不是独立存在的,它需要与整个 Chunk 设计体系配合:
| 配合维度 | 具体策略 |
|---|---|
| Chunk设计 | Parent 和 Child 的大小参数需要实验确定 |
| 文档切分策略 | Parent 层可以用语义切分,Child 层用递归字符切分 |
| Metadata设计 | Child 必须携带 Parent ID、文档来源、位置信息 |
| Rerank重排序 | 返回 Parent 后可进一步 Rerank,过滤低质量 Parent |
适用场景与不适用场景
适用:
- 长文档问答(技术文档、法律合同、学术论文)
- 代码库检索(函数级检索,类级返回)
- 需要完整上下文才能理解的领域(医疗、法律)
不适用:
- 短文档(FAQ、微博)——Parent 和 Child 差异不大,分层无意义
- 高度结构化的数据(表格、数据库)——用结构化查询更合适
- 实时性要求极高的场景——双层索引增加了检索延迟
常见误区
- Parent 越大越好:过大的 Parent 引入大量噪声,挤压 LLM 的生成空间
- 忽视去重:多个 Child 命中同一 Parent 时,必须去重,否则浪费上下文窗口
- Child 和 Parent 切分策略不一致:Child 的语义边界应该在 Parent 内部,不能跨越 Parent 边界
- 不调整 Child 大小:Child 大小直接影响检索精度,需要根据场景实验
- 忽视上下文预算:返回多个 Parent 时,总量可能超过 LLM 的上下文窗口
可继续补充的方向
- Parent-child 与 Multi-vector Retriever 的对比
- 动态 Parent 大小(根据检索结果的 Rerank 分数自适应调整)
- Parent-child 在代码检索中的特殊实现
- 与 Hypothetical Document Embedding (HyDE) 的结合
关联笔记
- Chunk设计 — Parent-child 是 Chunk 设计的核心策略之一
- 文档切分策略 — Parent 和 Child 的切分方法选择
- Metadata设计 — Child→Parent 映射关系的 Metadata 设计
- RAG基础 — RAG 基础流程中的检索环节
- Rerank重排序 — Parent 返回后的二次排序
- Multi-query Retrieval — 从查询角度提升召回率,与 Parent-child 互补