资讯动态

LlamaIndex TreeIndex 四大检索器深度解析:TreeAllLeaf、TreeSelectLeaf、TreeRoot 与 Embedding 变体的源码级实现

发布时间:2026/9/10 10:27:51 来源:尧图企业网站定制
LlamaIndex TreeIndex 四大检索器深度解析TreeAllLeaf、TreeSelectLeaf、TreeRoot 与 Embedding 变体的源码级实现【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index本文围绕 LlamaIndex 中树索引TreeIndex配套的四个检索器展开TreeAllLeafRetriever、TreeSelectLeafRetriever、TreeRootRetriever与TreeSelectLeafEmbeddingRetriever。这四个类是官方 API 参考retrievers API 文档 中列出的llama_index.core.retrievers成员在树结构索引上的具体实现。读完本文你将理解每种检索模式的适用场景、child_branch_factor等关键参数的行为、as_retriever工厂方法的分发逻辑以及“LLM 选路”与“向量相似度选路”两条核心调用链的源码细节能够在 RAG 系统中正确地为树索引选择并配置检索器。树索引TreeIndex与四种检索模式的关系在讨论检索器之前需要先明确它们依附的索引结构。TreeIndex定义在 tree 索引基类 中其设计目标是树索引是一个树状索引每个节点是其子节点的摘要。索引构建时自底向上bottoms-up地构建树直到得到一组根节点root_nodes。查询时的主要选项是从根节点向下遍历树traverse down the tree from the root nodes次要选项是直接从根节点合成答案directly synthesize the answer from the root nodes。TreeIndex的关键构造参数包括参数默认值说明num_children10每个节点应有的子节点数量决定树的宽度与层级深度build_treeTrue构建索引时是否真正构建树生成摘要节点。设为False时只保留叶子节点summary_templateDEFAULT_SUMMARY_PROMPT节点摘要所用的提示词insert_promptDEFAULT_INSERT_PROMPT增量插入时决定新文本挂在哪一层的提示词use_asyncFalse是否使用异步构建树的结构体是IndexGraphTreeIndex.index_struct_cls检索器通过index_struct.root_nodes拿到根节点、通过index_struct.get_children(node)拿到某节点的子节点、通过docstore.get_node(node_id)取出节点内容。下面四种检索器正是围绕这三个访问点实现了不同的查询策略。四种模式由枚举TreeRetrieverModebase.py 第 25-29 行定义class TreeRetrieverMode(str, Enum): SELECT_LEAF select_leaf SELECT_LEAF_EMBEDDING select_leaf_embedding ALL_LEAF all_leaf ROOT root其中SELECT_LEAF、SELECT_LEAF_EMBEDDING、ROOT三种模式要求索引在构建时生成了树build_treeTrueALL_LEAF则不要求。这一约束由REQUIRE_TREE_MODES集合base.py 第 32-36 行和_validate_build_tree_required校验方法共同保证若对未建树的索引使用需要树结构的模式会抛出ValueErrorbase.py 第 130-136 行。工厂方法as_retriever 如何分发到四种检索器树索引并没有把“检索策略”写死在索引里而是通过as_retriever工厂方法按需创建具体检索器实例。完整实现位于 base.py 第 94-128 行def as_retriever( self, retriever_mode: Union[str, TreeRetrieverMode] TreeRetrieverMode.SELECT_LEAF, embed_model: Optional[BaseEmbedding] None, **kwargs: Any, ) - BaseRetriever: self._validate_build_tree_required(TreeRetrieverMode(retriever_mode)) if retriever_mode TreeRetrieverMode.SELECT_LEAF: return TreeSelectLeafRetriever(self, object_mapself._object_map, **kwargs) elif retriever_mode TreeRetrieverMode.SELECT_LEAF_EMBEDDING: embed_model embed_model or Settings.embed_model return TreeSelectLeafEmbeddingRetriever( self, embed_modelembed_model, object_mapself._object_map, **kwargs ) elif retriever_mode TreeRetrieverMode.ROOT: return TreeRootRetriever(self, object_mapself._object_map, **kwargs) elif retriever_mode TreeRetrieverMode.ALL_LEAF: return TreeAllLeafRetriever(self, object_mapself._object_map, **kwargs) else: raise ValueError(fUnknown retriever mode: {retriever_mode})从源码结构看有几点值得注意默认模式是select_leaf即 LLM 逐层选路这是树索引的“正统”查询方式。select_leaf_embedding模式需要embed_model未显式传入时会回落到全局Settings.embed_model这与TreeSelectLeafEmbeddingRetriever构造器中的回退逻辑一致见下文。kwargs会被透传给具体检索器例如child_branch_factor、verbose、query_template等因此可以通过工厂方法自定义提示词与分支因子。四个检索器都同时从llama_index.core.indices.tree包和llama_index.core.retrievers包导出见 retrievers 包初始化文件 与 indices.tree 包初始化文件CLI 的组件映射mappings.json也确认这四个类的“官方地址”是llama_index.core.retrievers。典型用法是配合RetrieverQueryEnginefrom llama_index.core import TreeIndex, Settings from llama_index.core.indices.query.engine import RetrieverQueryEngine tree_index TreeIndex.from_documents(documents, num_children10) # 默认LLM 逐层选路到叶子 retriever tree_index.as_retriever() # 或使用向量相似度选路 # retriever tree_index.as_retriever( # retriever_modeselect_leaf_embedding, child_branch_factor2 # ) query_engine RetrieverQueryEngine.from_args(retriever) response query_engine.query(这篇文档的核心结论是什么)TreeAllLeafRetriever把整篇文档的叶子节点全部作为检索结果TreeAllLeafRetriever实现于 all_leaf_retriever.py是四种模式中逻辑最简单的一种。类 docstring 明确说明了它的定位这个类从叶子节点出发构建一个查询相关的树来返回回答。使用该查询模式意味着树索引在初始化时不需要构建因为每次查询都会重建树。核心检索逻辑只有三行all_leaf_retriever.py 第 47-56 行def _retrieve(self, query_bundle: QueryBundle) - List[NodeWithScore]: Get nodes for response. logger.info(f Starting query: {query_bundle.query_str}) index_struct cast(IndexGraph, self._index_struct) all_nodes self._docstore.get_node_dict(index_struct.all_nodes) sorted_node_list get_sorted_node_list(all_nodes) return [NodeWithScore(nodenode) for node in sorted_node_list]可以归纳出它的特点不依赖树结构取的是index_struct.all_nodes索引中所有叶子节点而非root_nodes或get_children。这也正是为什么ALL_LEAF不在REQUIRE_TREE_MODES中——即使构建索引时build_treeFalse该模式仍然可用。返回全部叶子没有打分、没有筛选所有节点以scoreNone包装成NodeWithScore返回节点顺序由get_sorted_node_listindices/utils.py 第 16 行按节点 id 排序保证确定性。适用场景它本质上是“把树索引当作文档容器用”——把全部源文档内容交给 LLM/响应合成器来回答等价于忽略层级结构的全量上下文检索。文件顶部还残留着DEFAULT_NUM_CHILDREN 10常量与TreeIndex的默认子节点数保持一致。注意其构造参数只接受index、callback_manager、object_map、verbose没有 LLM 相关参数因为检索过程本身不调用模型问答发生在下游的响应合成阶段。TreeRootRetriever直接从根节点摘要中取答案TreeRootRetriever实现于 tree_root_retriever.pydocstring 对其适用前提有非常关键的说明这个类直接从根节点检索答案。与 GPTTreeIndexLeafQuery 不同它假设图中已经存储了答案因为它是带 query_str 构建的因此不会试图沿着图往下解析信息来合成答案。检索实现tree_root_retriever.py 第 42-50 行def _retrieve(self, query_bundle: QueryBundle) - List[NodeWithScore]: Get nodes for response. logger.info(f Starting query: {query_bundle.query_str}) root_nodes self._docstore.get_node_dict(self._index_struct.root_nodes) sorted_nodes get_sorted_node_list(root_nodes) return [NodeWithScore(nodenode) for node in sorted_nodes]从源码可以读出它的设计边界只取index_struct.root_nodes这些根节点是整棵树的最高层摘要。对于“文档级/章节级宏观问题”如“这份报告的整体结论”根节点摘要往往已经足够无需下钻。前提条件苛刻注释里说的“带 query_str 构建”指的是旧版GPTTreeIndex的查询式建图方式——建图时就围绕某个问题逐层摘要根节点直接就是答案。而当前TreeIndex是问题无关的通用摘要树因此用TreeRootRetriever时根节点返回的是文档摘要最终回答仍由下游响应合成器基于这些摘要节点生成。该模式要求build_treeTrueROOT属于REQUIRE_TREE_MODES未建树的索引调用as_retriever(retriever_moderoot)会被_validate_build_tree_required拒绝。TreeSelectLeafRetrieverLLM 驱动的逐层“迷宫式”检索这是树索引的默认、也是最能体现树结构价值的检索器实现于 select_leaf_retriever.py。类 docstring 概述为这个类遍历索引图搜索最能回答查询的叶子节点。child_branch_factorint每一层考虑的子节点数量。若为 1则任何父节点只会选择 1 个子节点继续遍历若为 2则选择 2 个。构造参数一览参数默认值说明index必填TreeIndex实例query_templateDEFAULT_QUERY_PROMPT单分支child_branch_factor1时的选路提示词query_template_multipleDEFAULT_QUERY_PROMPT_MULTIPLE多分支child_branch_factor1时的选路提示词text_qa_templateDEFAULT_TEXT_QA_PROMPT叶子节点问答提示词响应合成器使用refine_templateDEFAULT_REFINE_PROMPT_SEL多节点结果精化提示词child_branch_factor1每层保留的子节点数verboseFalse打印每层选择过程选路提示词定义在 default_prompts.py 第 46-78 行。单分支模板要求模型在编号列表中“只返回最相关的一个编号ANSWER: number”多分支模板则要求返回“至多{branching_factor}个按相关度排序的编号”。两个模板都强调“只依据上述选项、不使用先验知识”以减少模型幻觉干扰选路。两条工作路径_query 与 _retrieve该类的实现同时保留了“查询即回答”和“纯检索”两条路径这也是理解它的关键路径一_query问答路径select_leaf_retriever.py 第 282-299 行def _query(self, query_bundle: QueryBundle) - Response: source_nodes: List[BaseNode] [] response_str self._query_level( self._index_struct.root_nodes, query_bundle, level0, source_nodessource_nodes, ).strip() return Response( response_str, source_nodes[NodeWithScore(nodenode) for node in source_nodes], )它直接覆写了基类的_query方法注释明确写了this overrides the _query method in the base class因此以查询引擎方式直接query时走的是完整的“逐层选路 → 叶子问答 → refine 聚合”流程_query_level从根节点集合开始把每个节点的摘要文本编号后拼进选路提示词用PromptHelper.get_text_splitter_given_prompt按模型上下文窗口自适应截断再调用self._llm.predict获得选择结果select_leaf_retriever.py 第 165-224 行。用extract_numbers_given_response(response, nself.child_branch_factor)从 LLM 回复中解析编号indices/utils.py 第 22 行。解析失败回复中没有数字或编号越界时源码采取降级策略直接把该层的 LLM 原始回复当作最终答案返回而不是崩溃select_leaf_retriever.py 第 231-249 行。对每个选中的编号1-indexed代码中number - 1转 0-indexed递归进入_query_with_selected_node若该节点是叶子用get_response_synthesizer基于text_qa_template从节点文本生成回答否则继续对其子节点调用_query_levelselect_leaf_retriever.py 第 110-163 行。当一次查询命中多个叶子child_branch_factor 1时后面的叶子会携带前一个答案prev_response通过refine_template对既有答案进行精化——即“逐叶累进 refine”的响应聚合方式。沿途访问过的叶子节点被收集进source_nodes作为最终Response.source_nodes返回便于溯源。路径二_retrieve纯检索路径select_leaf_retriever.py 第 398-441 行def _retrieve_level(self, cur_node_ids, query_bundle, level0) - List[BaseNode]: cur_nodes {index: self._docstore.get_node(node_id) for index, node_id in cur_node_ids.items()} cur_node_list get_sorted_node_list(cur_nodes) if len(cur_node_list) self.child_branch_factor: selected_nodes self._select_nodes(cur_node_list, query_bundle, levellevel) else: selected_nodes cur_node_list children_nodes {} for node in selected_nodes: node_dict self._index_struct.get_children(node) children_nodes.update(node_dict) if len(children_nodes) 0: # NOTE: leaf level return selected_nodes else: return self._retrieve_level(children_nodes, query_bundle, level 1) def _retrieve(self, query_bundle: QueryBundle) - List[NodeWithScore]: nodes self._retrieve_level(self._index_struct.root_nodes, query_bundle, level0) return [NodeWithScore(nodenode) for node in nodes]这条路径与路径一的区别在于它只负责“选路到叶子”不在叶子处调用 LLM 生成答案而是把最终选中的叶子节点作为NodeWithScore列表返回。这样当把TreeSelectLeafRetriever装进RetrieverQueryEngine时选路过程照常发生而真正的问答由外部注入的response_synthesizer对返回的叶子节点完成——这正是“检索器/合成器解耦”的典型体现。注意一个细节_retrieve_level中当当前层节点数不超过child_branch_factor时跳过 LLM 选路、直接全部保留能减少一次模型调用_select_nodes中的编号越界节点会被静默continue跳过而非像_query_level那样整体降级返回。child_branch_factor 的取舍child_branch_factor1每层只保留 1 条路径LLM 调用次数最少每层 1 次但一旦某层选错分支就无法回头child_branch_factor1每层保留多条路径做“束搜索”召回更稳但 LLM 调用与 refine 步骤随之增加且要求使用多分支提示词query_template_multiple。从源码结构看两类提示词的切换完全由if self.child_branch_factor 1分支决定select_leaf_retriever.py 第 188-224 行如果手动覆盖提示词应保证与所用分支因子匹配。TreeSelectLeafEmbeddingRetriever用向量相似度替代 LLM 选路TreeSelectLeafEmbeddingRetriever继承自TreeSelectLeafRetrieverselect_leaf_embedding_retriever.py 第 20 行docstring 说明这个类使用查询与节点文本之间的嵌入相似度来遍历索引图。它在父类基础上增加了一个embed_model参数Optional[BaseEmbedding]缺省回落到Settings.embed_model见 第 46-72 行并重写了选路的核心方法问答路径的重写——_query_level第 74-109 行不再调用 LLM 做选择而是对当前层所有节点计算与查询的相似度取前child_branch_factor个节点再复用父类的_query_with_selected_node完成“叶子问答 refine 聚合”。选路阶段的模型调用从“每层一次 LLM”变成了“每节点一次 embedding”对于宽树或高频查询成本特征明显不同。相似度计算与缓存第 111-154 行def _get_query_text_embedding_similarities(self, query_bundle, nodes) - List[float]: if query_bundle.embedding is None: query_bundle.embedding self._embed_model.get_agg_embedding_from_queries( query_bundle.embedding_strs ) similarities [] for node in nodes: if node.embedding is None: node.embedding self._embed_model.get_text_embedding( node.get_content(metadata_modeMetadataMode.EMBED) ) similarity self._embed_model.similarity(query_bundle.embedding, node.embedding) similarities.append(similarity) return similarities def _get_most_similar_nodes(self, nodes, query_bundle): similarities self._get_query_text_embedding_similarities(query_bundle, nodes) selected_nodes, selected_indices [], [] for node, _ in sorted(zip(nodes, similarities), keylambda x: x[1], reverseTrue): if len(selected_nodes) self.child_branch_factor: selected_nodes.append(node) selected_indices.append(nodes.index(node)) else: break return selected_nodes, selected_indices从源码结构看有三个值得注意的实现细节查询向量只算一次查询 embedding 缓存在query_bundle.embedding上同一查询在逐层下钻过程中复用节点向量则缓存在node.embedding字段上同一节点多次访问时不重复计算。节点取的是MetadataMode.EMBED模式下的内容即参与嵌入的是纯文本内容而非带元数据的 LLM 视图。排序选取对(node, similarity)按相似度降序排序取前child_branch_factor个语义上等价于每层 top-k 束搜索。检索路径的重写——_select_nodes第 156-163 行父类_retrieve_level在需要选路时调用_select_nodes子类把它替换为纯向量相似度的 top-k 选择。因此把select_leaf_embedding模式的检索器装进RetrieverQueryEngine时整个“逐层选路”过程零 LLM 调用只有 embedding 计算这是它与TreeSelectLeafRetriever最本质的性能差异。四种检索器对比与选型建议检索器选路依据是否调用 LLM 选路是否要求已建树build_treeTrue典型适用场景TreeAllLeafRetriever无返回全部叶子否否文档不长、希望全量上下文作答或build_treeFalse时唯一可选模式TreeRootRetriever无直接返回根节点摘要否是宏观/概括性问题根节点摘要已包含答案TreeSelectLeafRetrieverLLM 逐层选择子节点编号是每层 1 次是长文档精准问答默认模式精度优先TreeSelectLeafEmbeddingRetriever查询与节点文本的向量相似度 top-k否仅 embedding是宽树、高频查询、希望压低 LLM 成本的精准问答选型上可以按以下思路判断问题粒度越宏观越靠近根root越具体越需要下钻到叶子select_leaf/select_leaf_embedding文档总长度在单次上下文可容纳范围内且不需要分层定位时all_leaf最省事。若使用select_leaf系模式child_branch_factor是精度与成本的调节旋钮从1起步遇到明显“选错分支”的漏召回时再调大。小结与源码索引本文以官方 API 参考retrievers/tree.md列出的四个树检索器成员为主体结合仓库源码梳理了它们的实现与协作关系模式定义与工厂分发indices/tree/base.pyTreeRetrieverMode、as_retriever、_validate_build_tree_requiredTreeAllLeafRetrieverTreeRootRetrieverTreeSelectLeafRetrieverTreeSelectLeafEmbeddingRetriever公共导出retrievers 包、indices.tree 包选路提示词default_prompts.py工具函数节点排序、编号解析indices/utils.py掌握这四种检索器的差异与child_branch_factor、retriever_mode等参数的行为后你就可以在 LlamaIndex 的树索引 RAG 管线中针对文档规模、问题粒度与延迟成本做出有据可依的检索策略选择。【免费下载链接】llama_indexLlamaIndex is the leading document agent and OCR platform项目地址: https://gitcode.com/GitHub_Trending/ll/llama_index创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价