如何在LangChain框架中结合Parent Document与Self Query检索器
结合Self Query与父文档检索的实现方案
核心思路:用Self Query Retriever精准检索400token小片段(保持小尺寸保证检索准确性),再通过小片段关联的父文档标识,提取对应的2000token大父片段,既保留Self Query的元数据检索能力,又能获取完整上下文。
步骤1:文档预处理——拆分父子片段并添加关联元数据
先将原始文档拆分为2000token的父片段,再把每个父片段拆分为400token的子片段,给每个子片段添加parent_id元数据,关联到所属父片段。
from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain.docstore.document import Document # 父片段拆分器(2000token,可根据需求调整重叠度) parent_splitter = RecursiveCharacterTextSplitter(chunk_size=2000, chunk_overlap=200) # 子片段拆分器(400token) child_splitter = RecursiveCharacterTextSplitter(chunk_size=400, chunk_overlap=40) # 示例原始文档列表 raw_docs = ["你的长文档内容1...", "你的长文档内容2..."] # 拆分得到父片段列表 parent_chunks = parent_splitter.split_documents(raw_docs) # 生成带parent_id的子片段 child_chunks_with_parent = [] for parent_idx, parent_chunk in enumerate(parent_chunks): # 将父片段拆分为子片段 child_texts = child_splitter.split_text(parent_chunk.page_content) # 给每个子片段添加父ID元数据,同时继承父片段的业务元数据 for child_text in child_texts: child_doc = Document( page_content=child_text, metadata={**parent_chunk.metadata, "parent_id": parent_idx} ) child_chunks_with_parent.append(child_doc)
步骤2:构建含parent_id元数据的Self Query Retriever
确保Self Query的元数据字段包含parent_id,让检索结果能带回父片段的关联标识。
from langchain.chains.query_constructor.base import AttributeInfo from langchain.retrievers.self_query.base import SelfQueryRetriever from langchain.vectorstores import Chroma from langchain.embeddings import OpenAIEmbeddings # 定义元数据字段(包含业务元数据+parent_id) metadata_field_info = [ AttributeInfo( name="parent_id", description="父片段的唯一索引ID", type="integer" ), # 添加你的业务元数据,比如: # AttributeInfo(name="category", description="文档所属分类", type="string"), # AttributeInfo(name="publish_date", description="文档发布日期", type="string") ] # 用子片段构建向量索引 vectorstore = Chroma.from_documents( documents=child_chunks_with_parent, embedding=OpenAIEmbeddings() ) # 构建Self Query Retriever self_query_retriever = SelfQueryRetriever.from_llm( llm=your_llm_instance, # 替换为你的LLM实例(如GPT-3.5/4) vectorstore=vectorstore, document_contents="文档的小片段内容", metadata_field_info=metadata_field_info, verbose=True )
步骤3:检索后替换为父片段
用Self Query拿到子片段后,提取唯一的parent_id,再从父片段列表中获取对应的大片段。
def retrieve_parent_docs(query): # 用Self Query检索子片段 retrieved_child_docs = self_query_retriever.get_relevant_documents(query) # 提取不重复的parent_id,避免返回重复父片段 unique_parent_ids = list({doc.metadata["parent_id"] for doc in retrieved_child_docs}) # 根据父ID获取对应的大父片段 retrieved_parent_docs = [parent_chunks[idx] for idx in unique_parent_ids] return retrieved_parent_docs # 使用示例 query = "你的检索问题,比如:'2023年Q3的产品营收数据'" result_parent_docs = retrieve_parent_docs(query) # 输出父片段内容 for doc in result_parent_docs: print(f"父片段内容:\n{doc.page_content}\n---")
生产环境优化建议
- 父片段存储:不要用内存存储父片段,可写入数据库或本地文件系统,用UUID替代索引作为
parent_id,更稳定。 - 元数据扩展:子片段继承父片段的所有业务元数据,确保Self Query可以基于分类、日期等字段精准过滤。
- 去重逻辑:可根据父片段内容哈希去重,避免不同
parent_id对应相同内容的情况。
内容的提问来源于stack exchange,提问作者Andrea Neri
相关产品推荐
相关产品推荐

