ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Advanced RAG:摘要索引与父子索引优化长文档检索效果

2026/8/13 7:40:20 拓冰建站 浏览量
Advanced RAG:摘要索引与父子索引优化长文档检索效果 1. 项目概述当RAG检索开始“内卷”如果你已经跟着这个系列从零开始搭建过基础的RAG系统可能会发现一个现象当你的知识库文档稍微长一点、复杂一点比如是一份几十页的技术白皮书或一份年度报告直接用向量检索去匹配用户问题效果常常不尽人意。你可能会召回一大堆包含关键词但上下文不完整的片段或者召回了关键段落但LLM因为缺乏全局视野而“断章取义”生成一些看似合理实则偏离原意的答案。这就是基础RAG在应对长文档、复杂文档时的核心痛点检索粒度与生成需求的不匹配。我们通常将文档切成固定大小的块比如512个token但用户的一个问题其答案可能分散在多个块中或者需要从一个很长的块中提炼核心观点。单纯依赖向量相似度就像让一个近视的人只凭触感去拼一幅巨大的拼图效率低下且容易出错。于是RAG的“内卷”开始了。工程师们不再满足于“切块-检索-生成”的三板斧转而研究如何让检索变得更智能、更精准。AdvancedRAG正是这一趋势下的产物它不是某个具体的框架而是一系列用于增强RAG系统效果的高级技术和架构模式的统称。今天我们要深入探讨的摘要索引和父子索引就是AdvancedRAG武器库中两件针对“文档结构”和“信息密度”进行优化的利器。它们通过改变我们组织和管理知识的方式从根本上提升检索质量。简单来说摘要索引不是直接检索原始文档块而是先为每个块或文档生成一个简洁的摘要然后用这个摘要去建立索引和进行检索。它解决的是“信息过载”和“语义模糊”问题让检索目标更清晰。父子索引这是一种层次化的索引结构。将文档切成“父”文档较大的块如整个章节和“子”文档较小的块如段落或句子。检索时可以先定位到相关的“父”文档再在其下的“子”文档中精确定位。它解决的是“上下文缺失”和“粒度控制”问题兼顾了全局和局部信息。在LangChain的生态中这两种高级索引模式通常通过MultiVectorRetriever这个强大的检索器来实现。它允许我们为同一段原始文本存储多种不同的向量表示如摘要、问题、关键词等并在检索时灵活运用。本文将手把手带你理解其原理并用代码实现这两种优化方案让你能直接应用到自己的RAG项目中。2. 摘要索引化繁为简直击核心2.1 为什么需要摘要索引—— 从“全文匹配”到“核心思想匹配”想象一下你是一个研究员你的知识库里有成千上万篇学术论文的片段。当你想查找“对比了Transformer和CNN在图像分类任务上效率的文献”时基础RAG的向量检索可能会给你返回一堆包含“Transformer”、“CNN”、“图像分类”、“效率”这些词的段落。但这些段落可能只是在引言里提到了这些词或者是在讨论无关的细节并没有真正进行对比分析。这就是向量检索的局限性它本质上是词汇和浅层语义的匹配。一个段落即使核心思想完全匹配你的问题但如果表述方式不同、关键词密度不高它的向量相似度得分也可能很低。反之一个罗列了许多相关词汇但主旨无关的段落得分却可能很高。摘要索引的思路很巧妙既然LLM擅长理解和总结那我们何不让LLM先帮我们把文档的核心思想提炼出来我们不再直接为原始文本块创建向量而是为每个文本块生成一个简洁、准确的摘要。为这些摘要创建向量并存入向量数据库。当用户查询时用查询语句的向量去匹配这些摘要的向量。召回最相关的摘要后取出其对应的原始文本块连同原始文本一起交给LLM生成最终答案。这样做的好处是显而易见的降噪与聚焦摘要过滤掉了例子、数据细节、过渡语句等“噪音”只保留核心主张、结论和方法使得向量表示更能体现文本的“主旨”。语义压缩将可能长达数百字的文本压缩成一两句话的摘要这本身就是一次高质量的语义提炼使得检索目标更加清晰和集中。提升相关性查询“对比A和B”与摘要“本文主要对比了A和B在某某指标上的表现”的匹配度显然高于与一段详细描述A技术的原文的匹配度。2.2 基于LangChain的实现详解下面我们以LangChain和Chroma向量数据库为例展示如何实现一个摘要索引检索器。这里的关键是使用MultiVectorRetriever。from langchain.retrievers.multi_vector import MultiVectorRetriever from langchain.storage import InMemoryStore from langchain_chroma import Chroma from langchain_openai import OpenAIEmbeddings, ChatOpenAI from langchain_core.documents import Document from langchain_core.prompts import ChatPromptTemplate from langchain_core.output_parsers import StrOutputParser # 1. 准备原始文档 original_texts [ Transformer模型由Vaswani等人在2017年提出其核心是自注意力机制完全摒弃了RNN和CNN结构。它在机器翻译任务上取得了突破性进展但模型参数量巨大训练和推理成本较高。, 卷积神经网络CNN是计算机视觉领域的基石通过卷积核提取局部特征具有平移不变性。其结构参数效率高但在处理长序列依赖如文本时存在局限性。, 一项2023年的研究在ImageNet数据集上对比了Vision TransformerViT和ResNet。结果表明在大规模数据预训练下ViT能达到甚至超越CNN的精度但在数据量不足时CNN因其归纳偏置更具优势。 ] original_docs [Document(page_contenttext) for text in original_texts] # 2. 创建摘要生成链 llm ChatOpenAI(modelgpt-3.5-turbo, temperature0) prompt ChatPromptTemplate.from_template( “请为以下文本生成一个简洁的摘要突出其核心内容和技术要点\n\n{text}” ) summarize_chain prompt | llm | StrOutputParser() # 3. 为每个原始文档生成摘要 summaries [] for doc in original_docs: summary summarize_chain.invoke({text: doc.page_content}) summaries.append(summary) # 可选将摘要作为元数据存入原始文档后续可能用到 doc.metadata[summary] summary print(生成的摘要) for i, s in enumerate(summaries): print(f文档{i}: {s}) # 假设生成的摘要如下 # 文档0: 介绍了Transformer模型的核心是自注意力机制它取代了RNN/CNN性能强大但计算成本高。 # 文档1: 说明了CNN通过卷积操作提取特征在图像处理上高效但不擅长处理长序列。 # 文档2: 对比了ViT和CNN在图像分类上的表现指出ViT大数据下更优小数据下CNN因归纳偏置而强。 # 4. 创建向量存储用于存摘要和底层存储用于存原始文档 vectorstore Chroma(collection_namesummary_collection, embedding_functionOpenAIEmbeddings()) # InMemoryStore 用于存储id到原始文档的映射 store InMemoryStore() id_key doc_id # 5. 创建MultiVectorRetriever retriever MultiVectorRetriever( vectorstorevectorstore, docstorestore, # 存储原始文档的地方 id_keyid_key, ) # 6. 准备要添加到检索器的文档 # 我们需要创建两组Document对象 # 一组是“摘要文档”用于创建向量索引。 # 另一组是“原始文档”存入docstore并通过id与摘要关联。 from uuid import uuid4 doc_ids [str(uuid4()) for _ in original_docs] # 创建摘要文档其page_content是摘要metadata中包含指向原始文档的id summary_docs [ Document(page_contentsummary, metadata{id_key: doc_ids[i]}) for i, summary in enumerate(summaries) ] # 创建原始文档其metadata中也包含自己的id方便查找 original_docs_with_id [ Document(page_contentdoc.page_content, metadata{id_key: doc_ids[i], **doc.metadata}) for i, doc in enumerate(original_docs) ] # 7. 将文档添加到检索器 # 将摘要文档添加到向量库 retriever.vectorstore.add_documents(summary_docs) # 将原始文档添加到底层存储 retriever.docstore.mset(list(zip(doc_ids, original_docs_with_id))) # 8. 进行检索测试 query “有哪些模型对比了Transformer和CNN的效率” # 检索器会去向量库存的是摘要里找相似的摘要 retrieved_docs retriever.invoke(query) print(f\n查询{query}) print(检索到的原始文档内容) for doc in retrieved_docs: print(f- {doc.page_content[:100]}...) # 打印前100字符关键逻辑解析双存储结构MultiVectorRetriever内部维护了两个存储vectorstore向量数据库这里存摘要的向量和docstore一个键值存储这里存原始文档。ID关联每个“摘要文档”和其对应的“原始文档”共享一个唯一的doc_id。摘要文档的metadata里保存了这个id。检索流程当用户查询时检索器用查询语句的向量在vectorstore中查找最相似的摘要文档- 得到这些摘要文档的doc_id- 用这些doc_id去docstore里取出对应的原始文档- 返回原始文档给后续流程。注意生成摘要本身需要调用LLM这会增加预处理阶段的成本和耗时。因此摘要索引适用于对检索质量要求高、文档相对稳定、且可以接受预处理开销的场景。2.3 实战心得与避坑指南摘要质量是生命线摘要生成提示词Prompt至关重要。一个糟糕的摘要可能比原文更误导。你需要精心设计提示词确保摘要能准确、中立地反映原文核心。可以尝试让LLM以“这是一篇关于...的文章其主要论点是...”的句式来总结。控制摘要长度摘要不宜过短丢失信息也不宜过长失去降噪意义。通常1-3句话为宜。你可以在提示词中明确要求“用一句话总结”或“总结核心要点不超过50字”。成本与缓存为海量文档生成摘要是一笔不小的LLM API开销。务必做好缓存将生成的摘要持久化存储如数据库避免重复生成。对于更新不频繁的知识库这是一个一次性的预处理成本。混合检索的考量有时单纯依赖摘要检索可能会丢失一些细节匹配。一个更健壮的方案是混合检索同时保留原始文本的向量索引和摘要的向量索引在召回时融合两者的结果。MultiVectorRetriever本身支持添加多种向量表示你可以轻松实现这一点。3. 父子索引构建文档的“宏观-微观”地图3.1 为什么需要父子索引—— 解决上下文碎片化问题基础RAG的均匀分块有一个致命缺点它粗暴地割裂了文档固有的逻辑结构。一个完整的论点可能被切到两个块里导致检索到的块缺乏必要的上下文LLM无法理解其完整含义。父子索引引入了层次化思想父文档较大的文本单元如整个章节、一组相关的段落、或一个完整的话题部分。它提供了宏观上下文。子文档较小的文本单元是从父文档中进一步切分出来的如单个段落、关键定义、具体步骤。它提供了微观的、精确的信息点。检索时策略可以很灵活策略A父级优先先检索最相关的父文档然后将整个父文档或其所有子文档作为上下文送给LLM。这保证了答案的上下文完整性适合需要背景知识的问题。策略B子级直接检索直接检索最相关的子文档。这适合答案非常具体、定位明确的问题。策略C混合召回同时检索父文档和子文档然后去重或按分数融合。这是最常用的策略兼顾了精度和广度。父子索引的优势在于保留上下文LLM在生成答案时看到的不是一个孤立的片段而是一个具有完整逻辑的父文档上下文极大减少了“幻觉”的可能。检索粒度可控系统可以根据问题的性质智能地决定是返回父文档还是子文档。例如“解释一下CNN的原理”可能返回父文档“CNN的卷积核大小通常是多少”则直接返回子文档。支持复杂查询对于“请总结文档第三章的主要内容”这类查询直接检索到“第三章”这个父文档比从一堆碎片中拼凑要高效准确得多。3.2 基于LangChain的实现详解实现父子索引的核心在于如何建立父子文档的关联并利用MultiVectorRetriever进行存储和检索。这里我们演示如何手动构建一个简单的父子结构。from langchain.retrievers.multi_vector import MultiVectorRetriever from langchain.storage import InMemoryStore from langchain_chroma import Chroma from langchain_openai import OpenAIEmbeddings from langchain.text_splitter import RecursiveCharacterTextSplitter from langchain_core.documents import Document from uuid import uuid4 # 假设我们有一篇长文档内容关于机器学习 long_text # 第一章监督学习 监督学习是机器学习中最常见的类型。其目标是学习一个从输入到输出的映射函数。 ## 1.1 回归问题 回归问题预测连续值。例如根据房屋面积预测房价。线性回归是经典的回归算法。 线性回归的公式为y wx b其中w是权重b是偏置。 ## 1.2 分类问题 分类问题预测离散类别。例如判断邮件是否为垃圾邮件。逻辑回归和决策树是常用算法。 逻辑回归通过Sigmoid函数将输出映射到(0,1)区间表示概率。 # 第二章无监督学习 无监督学习处理没有标签的数据。其目标是发现数据中的内在结构。 ## 2.1 聚类 聚类将相似的数据点分组。K-Means是最著名的聚类算法。 ## 2.2 降维 降维用于减少数据特征数量同时保留重要信息。PCA主成分分析是常用方法。 # 1. 创建父文档这里我们按“章”作为父文档 # 简单按“# ”分割实际应用中可能需要更复杂的解析如用Markdown标题分割器 parent_sections long_text.split(# )[1:] # 忽略第一个空元素 parent_docs [] for section in parent_sections: # 提取标题和内容 lines section.strip().split(\n, 1) title lines[0] content lines[1] if len(lines) 1 else parent_docs.append(Document(page_contentcontent, metadata{title: title, type: parent})) print(父文档数量:, len(parent_docs)) for i, doc in enumerate(parent_docs): print(f父文档{i} 标题: {doc.metadata[title]}, 内容长度: {len(doc.page_content)}) # 2. 为每个父文档创建子文档这里用递归字符分割器 text_splitter RecursiveCharacterTextSplitter( chunk_size100, # 子文档较小 chunk_overlap20, separators[\n## , \n\n, \n, ] # 按Markdown二级标题、段落等分割 ) child_docs [] parent_to_child_ids {} # 记录父文档到其子文档ID列表的映射 for parent_id, parent_doc in enumerate(parent_docs): # 分割父文档内容得到子文档 children text_splitter.split_text(parent_doc.page_content) child_ids_for_parent [] for child_text in children: if child_text.strip(): # 忽略空文本 child_doc Document( page_contentchild_text, metadata{ parent_id: parent_id, parent_title: parent_doc.metadata[title], type: child } ) child_docs.append(child_doc) child_id str(uuid4()) child_doc.metadata[child_id] child_id # 为子文档也分配一个唯一ID child_ids_for_parent.append(child_id) parent_to_child_ids[parent_id] child_ids_for_parent print(f\n子文档总数: {len(child_docs)}) # 3. 创建MultiVectorRetriever # 这次我们选择将“子文档”存入向量库进行检索。 vectorstore Chroma(collection_nameparent_child_collection, embedding_functionOpenAIEmbeddings()) store InMemoryStore() retriever MultiVectorRetriever( vectorstorevectorstore, docstorestore, id_keydoc_id, # 关键这个id将用于关联 ) # 4. 添加文档到检索器 all_docs_to_store_in_docstore [] all_docs_to_index_in_vectorstore [] # 首先处理父文档和子文档为它们生成关联ID。 # 策略为每个父文档创建一个“代理”文档到向量库这个代理文档的content可以是父文档的标题或摘要其id关联到该父文档的所有子文档。 for parent_id, child_id_list in parent_to_child_ids.items(): if child_id_list: # 创建父文档的“代理”摘要文档用于向量索引 parent_doc parent_docs[parent_id] # 代理文档的内容可以是父文档的标题开头部分用于在向量搜索中代表这个父主题 proxy_content f主题{parent_doc.metadata[title]}。内容概述{parent_doc.page_content[:200]}... proxy_doc_for_vectorstore Document( page_contentproxy_content, metadata{doc_id: fparent_proxy_{parent_id}, type: parent_proxy} ) # 这个代理文档的id我们设定为能映射到所有子文档id的一个逻辑id。 # 但MultiVectorRetriever要求一个id对应docstore里的一个文档。 # 更常见的做法是直接将所有子文档添加到向量库而父文档仅存储在docstore中作为上下文补充。 # 让我们换一种更清晰的策略 # --- 更实用的策略向量库只索引子文档 --- # 我们将所有子文档添加到向量库同时将父文档和子文档都存入docstore。 # 检索时通过子文档找到其父文档ID然后从docstore拉取父文档作为扩展上下文。 # 重新组织数据 store_docs {} # id - Document 映射用于存入docstore vector_docs [] # 用于存入向量库的Document列表 # 存入父文档到docstore for parent_id, parent_doc in enumerate(parent_docs): parent_uuid str(uuid4()) parent_doc.metadata[doc_id] parent_uuid parent_doc.metadata[doc_type] parent store_docs[parent_uuid] parent_doc # 存入子文档到docstore并准备其向量化版本 for child_doc in child_docs: child_uuid str(uuid4()) # 存储用的子文档包含完整的元数据 child_doc_for_store Document( page_contentchild_doc.page_content, metadata{ doc_id: child_uuid, doc_type: child, parent_id: child_doc.metadata[parent_id], parent_title: child_doc.metadata[parent_title] } ) store_docs[child_uuid] child_doc_for_store # 向量化用的子文档page_content相同metadata中只需包含其自身在docstore中的id child_doc_for_vector Document( page_contentchild_doc.page_content, metadata{doc_id: child_uuid} # 这是关键链接 ) vector_docs.append(child_doc_for_vector) # 将所有文档存入docstore retriever.docstore.mset(list(store_docs.items())) # 将子文档的向量化版本存入向量库 retriever.vectorstore.add_documents(vector_docs) print(数据存储完成。) # 5. 实现一个增强检索函数不仅返回子文档也返回其父文档 def retrieve_with_parent_context(query, retriever, k3): 检索并扩展父上下文 # 第一步检索最相关的子文档 child_docs_retrieved retriever.invoke(query) retrieved_results [] seen_parent_ids set() for child_doc in child_docs_retrieved: child_id child_doc.metadata.get(doc_id) # 从docstore中获取完整的子文档信息包含parent_id stored_child_doc retriever.docstore.mget([child_id])[0] if not stored_child_doc: continue parent_id stored_child_doc.metadata.get(parent_id) result_entry { child_content: stored_child_doc.page_content, child_metadata: stored_child_doc.metadata } # 第二步获取父文档内容 if parent_id is not None and parent_id not in seen_parent_ids: # 我们需要找到对应父文档的ID。在之前的存储逻辑中父文档的ID我们并没有直接和parent_id关联。 # 这里暴露了我们设计上的一个缺陷在docstore中需要通过ID查找文档。 # 一个更健壮的设计是在存储时建立parent_id到父文档uuid的映射表。 # 为了演示我们假设可以通过某种方式获取父文档。这里简化处理直接使用父文档列表。 if isinstance(parent_id, int) and parent_id len(parent_docs): parent_doc parent_docs[parent_id] result_entry[parent_content] parent_doc.page_content result_entry[parent_title] parent_doc.metadata[title] seen_parent_ids.add(parent_id) retrieved_results.append(result_entry) return retrieved_results # 6. 测试检索 query “线性回归的公式是什么” results retrieve_with_parent_context(query, retriever, k2) print(f\n查询{query}) for i, res in enumerate(results): print(f\n--- 结果 {i1} ---) print(f子文档内容{res[child_content]}) if parent_title in res: print(f所属父文档标题{res[parent_title]}) print(f父文档内容前200字{res.get(parent_content, )[:200]}...)关键逻辑解析与优化存储设计上述代码展示了一种基本结构但在生产环境中parent_id到父文档存储ID的映射需要更严谨的设计。通常我们会为每个父文档生成一个唯一UUID并将其存入docstore。子文档的元数据中保存其父文档的这个UUID。检索策略我们实现了retrieve_with_parent_context函数。它先召回相关的子文档然后根据子文档的元数据找到其父文档ID进而获取完整的父文档内容。这样在将上下文送给LLM时我们可以选择只发送精准的子文档。发送子文档 其所属的整个父文档内容。发送子文档 父文档的摘要。向量化对象的选择本例中我们选择将子文档进行向量化。这是因为子文档粒度更细更容易与具体问题匹配。父文档则作为“上下文扩展包”使用。你也可以选择为父文档的摘要创建向量索引实现更粗粒度的主题检索。3.3 实战心得与避坑指南分块策略是根基父子索引的效果严重依赖于如何定义“父”和“子”。对于Markdown/HTML文档可以按标题层级H1, H2, H3来划分。对于普通文本可能需要利用语义分割模型或基于规则的段落检测。LangChain的MarkdownHeaderTextSplitter和RecursiveCharacterTextSplitter可以组合使用。元数据至关重要一定要在子文档的元数据中清晰、准确地记录其父文档的标识符如ID、标题、路径。这是后续进行上下文扩展的唯一依据。权衡索引大小与检索速度存储父文档和子文档意味着索引体积会变大。同时检索后需要额外从docstore获取父文档增加了一次IO开销。需要根据业务对延迟和效果的要求进行权衡。动态上下文选择不是所有问题都需要父文档上下文。可以设计一个简单的分类器或基于查询长度的启发式规则判断当前问题是否需要广泛的上下文。例如“总结...”类问题需要父文档“...的定义是什么”类问题可能只需要子文档。4. 进阶融合摘要与父子索引的结合摘要索引和父子索引并非互斥它们可以强强联合构建出更强大的RAG系统。一个典型的融合架构如下文档预处理对原始文档进行父子分块形成父文档和子文档的层次结构。为每个父文档生成一个摘要父摘要。为每个子文档也生成一个摘要子摘要。子摘要可以更精炼。索引构建将子文档的原始文本和子文档的摘要作为两种不同的“视图”都存入MultiVectorRetriever的向量库中使用不同的元数据标识如type: “child_raw”,type: “child_summary”。将父文档的摘要也存入向量库type: “parent_summary”。将所有原始父文档、子文档存入docstore。检索策略混合检索对于用户查询同时从“子原始”、“子摘要”、“父摘要”三个向量索引中进行检索可以设置不同的权重。重排序与融合对召回的所有结果包括不同类型的文档进行分数融合或重排序。上下文组装对于最终选中的子文档不仅将其原始文本加入上下文还将其所属父文档的摘要、甚至父文档的原始文本或关键部分也加入为LLM提供从宏观到微观的完整视角。这种架构既能通过摘要提升检索的语义精度又能通过父子结构保证上下文的完整性和层次性是处理复杂长文档的理想选择。5. 在真实RAG管道中的集成与效果评估将Advanced RAG索引集成到完整管道中你需要考虑以下几个环节管道重构from langchain_core.prompts import ChatPromptTemplate from langchain_openai import ChatOpenAI from langchain_core.runnables import RunnablePassthrough, RunnableLambda from langchain_core.output_parsers import StrOutputParser # 假设我们已经有了一个配置好的 advanced_retriever (融合了摘要和父子索引的MultiVectorRetriever) # 1. 定义检索后处理函数例如扩展父上下文 def expand_with_parent_context(retrieved_docs): expanded_docs [] for doc in retrieved_docs: # 这里应包含从docstore获取父文档并拼接的逻辑 # 简化演示假设doc.metadata中已有parent_content final_content doc.page_content if parent_content in doc.metadata: # 可以选择将父内容放在前面作为背景 final_content f背景信息{doc.metadata[parent_content]}\n\n具体内容{doc.page_content} expanded_docs.append(Document(page_contentfinal_content, metadatadoc.metadata)) return expanded_docs # 2. 构建RAG链 template “”基于以下上下文信息回答用户的问题。如果上下文信息不足以回答问题请直接说“根据提供的信息无法回答该问题”。 上下文 {context} 问题{question} 请给出专业、准确的回答 “” prompt ChatPromptTemplate.from_template(template) llm ChatOpenAI(modelgpt-4, temperature0) # 核心链 rag_chain ( { “context”: advanced_retriever | RunnableLambda(expand_with_parent_context) | (lambda docs: \n\n.join([d.page_content for d in docs])), “question”: RunnablePassthrough() } | prompt | llm | StrOutputParser() ) # 3. 调用 answer rag_chain.invoke(“Transformer和CNN在图像分类上谁更高效为什么”) print(answer)效果评估维度引入高级索引后不能只凭感觉需要系统评估检索精度召回的文档是否真正与问题相关可以使用人工标注或借助LLM如GPT-4对“查询-文档”对进行相关性打分。答案质量最终生成的答案是否准确、完整、无幻觉可以采用忠实度、答案相关性等指标结合人工评估。上下文利用率观察LLM生成的答案是否确实用到了我们提供的父文档上下文。可以通过让LLM在答案中引用来源段落来判断。性能开销预处理时间生成摘要、构建层次、检索延迟、Token消耗因为上下文可能变长是否有显著增加是否在可接受范围内A/B测试建议在真实业务中可以并行运行基础RAG管道和Advanced RAG管道对同一批测试问题进行回答由领域专家或通过自动化指标进行对比用数据来决定是否值得引入这些优化。从基础RAG到Advanced RAG本质上是从“粗糙匹配”走向“智能理解”的过程。摘要索引和父子索引为我们提供了优化检索粒度和上下文质量的强大工具。它们的实现并不复杂核心在于利用MultiVectorRetriever对文档进行多视角的表示和存储。真正的挑战在于如何根据你的文档特性和业务需求设计合适的分块策略、摘要提示词以及检索后的上下文组装逻辑。