一、概述
本文将围绕前述内容中提及的父子索引(Parent-Document Retrieval / Parent-Child Index)展开,对其核心原理与实现方法进行深入剖析与详细解读
二、父子索引
- 1 父子索引简介
父子索引 (Parent-Document Retrieval / Parent-Child Index) 也称父文档检索或父子层级索引,是RAG应用领域中一种经典且高效的优化手段。它采用“切分粒度分离”的切分策略,巧妙地化解了检索精度与生成质量之间的固有矛盾。
在传统RAG在实践中常常面临一个“两难困境”:
- 若切片(Chunk)太小:虽然检索准确性较高,但上下文信息容易丢失,导致大模型在生成回答时出现逻辑断层。
- 若切片设置太大:文本块中夹杂的无关信息增多,向量检索的匹配精准度便会随之下降。
父子索引的核心思路在于将检索与生成两个环节解耦处理:即利用小切片(子块)执行精确检索,再依托大切片(父块)提供完整上下文用于生成回答。

- 2 父子索引核心思想
其核心思想是 “分而搜之,合而答之”:
- 检索环节:采用较小的文本单元,以提升匹配的精准度和敏感度。
- 生成环节: 调用较大的文本单元,以确保上下文信息的完整性和连贯性。
该机制通过在数据库中预先构建 “父子层级关联”,使得系统一旦命中相关性最高的“子块”,便能自动溯源并提取其所归属的“父块”,最终将完整的父块内容提供给模型用于答案生成。
- 3 父子索引实现原理
父子索引的实现流程通常可划分为以下四个关键步骤:
A. 分层切分 (Recursive Splitting)
- 设定父块: 将文档按语义完整性或段落划分为较大的单元(例如1000至2000词元),确保段落信息相对完整。
- 生成子块:在每个父块内部,进一步细分为更小的片段(例如100至200词元)。
- 建立关联:为每个子块赋予一个父块标识parent_id,用以记录其归属于哪个父块。
B. 索引构建 (Indexing)
- 仅向量化子块: 系统仅对“子块”进行向量化处理,并将其存入向量数据库用于检索。
- 存储父块: 父块的原始文本则存放于内存或简单的文档数据库(Docstore)中,无需参与向量计算,以降低资源消耗。
C. 检索与回溯 (Retrieval & Expansion)
- 子块检索查询: 用户发起查询时,系统首先在向量库中检索出最相关的若干子块。
- 父块回溯: 依据命中的子块所携带的父块的标识符
parent_id,从文档库中调取对应的完整父块内容。 - 结果合并: 若多个子块同属一个父块,则自动去重,仅保留一份父块文本。
D. 答案生成 (Generation)
大语言模型结合上述筛选出的、上下文完整的高相关度文本片段,最终生成准确的回答。
- 4 父子索引优势
父子索引是当前工业级应用中最成熟的 RAG 增强手段之一。它不依赖复杂的模型推理机制,仅凭数据结构的巧妙设计,便能显著提升系统的回答质量与专业度,同时在很大程度上规避了上下文断裂的风险,并有效优化了信息的纯度与可读性。
三、父子索引实现方式
==============
- 1 数据分割及索引构建
本章节沿用上一篇《RAG工程化实践方法论 - 摘要索引》中所使用的新闻数据。
加载数据和数据索引代码如下:
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_chroma import Chroma
#================================数据加载================================
# 初始化文档加载器
path = "news.txt"
loader = TextLoader(path,encoding='utf-8')
# 加载文档
docs = loader.load()
print("================================分割器准备================================")
#创建主文档分割器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=600)
#创建子文档分割器
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
print("文档分割器创建成功")
print("================================存储准备================================")
from langchain_core.stores import InMemoryStore
# 存储小块
vectorstore = Chroma(
collection_name="split_parents", embedding_function = embeddings_model
)
# 创建内存存储对象,存储大块
store = InMemoryStore()
print("存储器创建成功")
print("================================创建检索器================================")
from langchain.retrievers import ParentDocumentRetriever
# 创建父文档检索器
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
search_kwargs={"k": 1}
)
print("检索器创建成功")
print("================================文档添加至存储库================================")
retriever.add_documents(docs)
print("文档添加至存储库成功")
运行结果如下:

- 2 检索测试
对子块的数据检索代码如下:
print("------------搜索子块------------------------")
sub_docs = vectorstore.similarity_search("OpenClawd是什么")
print(sub_docs[0].page_content)
运行结果如下:

对父块的数据检索代码如下:
print("------------搜索子块,返回关联大块------------------------")
retrieved_docs = retriever.get_relevant_documents("OpenClawd是什么")
print(retrieved_docs[0].page_content)
检索结果如下:

通过与原文档、子块和父块三者的对比验证,子块完全包含在父块中,且父块和原文档内容保持一致,这表明父子索引具备从原文中准确检索内容的能力。

- 3 完整代码
完整代码如下:
from langchain_community.embeddings.dashscope import DashScopeEmbeddings
from langchain_community.chat_models.tongyi import ChatTongyi
from langchain.storage import InMemoryByteStore
from langchain_chroma import Chroma
from langchain_community.document_loaders import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from dotenv import load_dotenv
from langchain.retrievers import ParentDocumentRetriever
_ = load_dotenv()
# 初始化llm(通义千问)
llm = ChatTongyi(model="qwen-max")
embeddings_model = DashScopeEmbeddings(model="text-embedding-v1")
#================================数据加载================================
# 初始化文档加载器
path = "news.txt"
loader = TextLoader(path,encoding='utf-8')
# 加载文档
docs = loader.load()
#================================分割器准备================================
#创建主文档分割器
parent_splitter = RecursiveCharacterTextSplitter(chunk_size=600)
#创建子文档分割器
child_splitter = RecursiveCharacterTextSplitter(chunk_size=200)
#================================存储准备================================
# 存储小块
vectorstore = Chroma(
collection_name="split_parents", embedding_function = embeddings_model
)
# 创建内存存储对象,存储大块
store = InMemoryStore()
#================================创建检索器================================
# 创建父文档检索器
retriever = ParentDocumentRetriever(
vectorstore=vectorstore,
docstore=store,
child_splitter=child_splitter,
parent_splitter=parent_splitter,
search_kwargs={"k": 1}
)
#================================文档添加至存储库================================
retriever.add_documents(docs)
print("------------搜索子块------------------------")
sub_docs = vectorstore.similarity_search("OpenClawd是什么")
print(sub_docs[0].page_content)
print("------------搜索子块,返回关联大块------------------------")
retrieved_docs = retriever.get_relevant_documents("OpenClawd是什么")
print(retrieved_docs[0].page_content)
四、总结
========
父子索引通过“检索用小块、生成用大块”的分层设计,巧妙化解了RAG系统中检索精度与上下文完整性之间的矛盾。其核心流程为:将文档切分为语义完整的大块(父块),再细分出小块(子块)进行向量检索;命中子块后,可溯源至对应父块,将其作为最终生成的上下文。该方法不依赖复杂模型,仅凭数据结构设计即可提升检索准确率和生成质量,同时优化资源消耗,是工业级RAG应用中成熟且高效的优化手段。
这里给大家精心整理了一份全面的AI大模型学习资源,包括:AI大模型全套学习路线图(从入门到实战)、精品AI大模型学习书籍手册、视频教程、实战学习、面试题等,资料免费分享!
👇👇扫码免费领取全部内容👇👇

1. 成长路线图&学习规划
要学习一门新的技术,作为新手一定要先学习成长路线图,方向不对,努力白费。
这里,我们为新手和想要进一步提升的专业人士准备了一份详细的学习成长路线图和规划。可以说是最科学最系统的学习成长路线。

2. 大模型经典PDF书籍
书籍和学习文档资料是学习大模型过程中必不可少的,我们精选了一系列深入探讨大模型技术的书籍和学习文档,它们由领域内的顶尖专家撰写,内容全面、深入、详尽,为你学习大模型提供坚实的理论基础。(书籍含电子版PDF)

3. 大模型视频教程
对于很多自学或者没有基础的同学来说,书籍这些纯文字类的学习教材会觉得比较晦涩难以理解,因此,我们提供了丰富的大模型视频教程,以动态、形象的方式展示技术概念,帮助你更快、更轻松地掌握核心知识。

4. 2026行业报告
行业分析主要包括对不同行业的现状、趋势、问题、机会等进行系统地调研和评估,以了解哪些行业更适合引入大模型的技术和应用,以及在哪些方面可以发挥大模型的优势。

5. 大模型项目实战
学以致用 ,当你的理论知识积累到一定程度,就需要通过项目实战,在实际操作中检验和巩固你所学到的知识,同时为你找工作和职业发展打下坚实的基础。

6. 大模型面试题
面试不仅是技术的较量,更需要充分的准备。
在你已经掌握了大模型技术之后,就需要开始准备面试,我们将提供精心整理的大模型面试题库,涵盖当前面试中可能遇到的各种技术问题,让你在面试中游刃有余。

7. 资料领取:全套内容免费抱走,学 AI 不用再找第二份
不管你是 0 基础想入门 AI 大模型,还是有基础想冲刺大厂、了解行业趋势,这份资料都能满足你!
现在只需按照提示操作,就能免费领取:
👇👇扫码免费领取全部内容👇👇

849

被折叠的 条评论
为什么被折叠?



