☰
XAgent 数据结构详解:TaskSearchTree 任务搜索树的实现原理与实战
2026/9/25 7:12:37 网站建设 项目流程
  • AI Agent
  • 大模型
  • 后端
  • 任务调度

【免费下载链接】XAgent

An Autonomous LLM Agent for Complex Task Solving

项目地址:https://gitcode.com/gh_mirrors/xa/XAgent
点击查看免费下载

TaskSearchTree 是 XAgent 内部用于组织"复杂任务求解过程"的核心树状数据结构,它以ToolNode为节点,记录 Agent 在解决每个子任务时逐步调用工具、产生思考与输出结果的完整链路。本文以 XAgent/data_structure/tree.py 与配套文档 Markdown_Docs/XAgent/data_structure/tree.md 为骨架,结合节点实现与 ReACT 搜索算法源码,深入讲解树的构造、深度/子树统计、父子关系建立,以及它在真实任务执行中的调用方式,帮助你理解 XAgent 是如何把一次次的 LLM 推理与工具调用"沉淀"成一棵可回溯、可统计、可提交的任务树。

TaskSearchTree 类概览:一棵承载任务搜索行为的树

在 XAgent 的代码库中,TaskSearchTree被定义在 XAgent/data_structure/tree.py,其类注释明确说明:TaskSearchTree 表示一棵具有特定任务搜索行为(specific task searching behavior)的树数据结构。它的职责不是通用意义上的"多叉树工具类",而是为内层循环搜索算法(inner loop search)提供一棵记录"当前子任务从开始到结束每一步操作"的链式树。

类定义如下:

class TaskSearchTree: """ TaskSearchTree represents a tree data structure with specific task searching behavior. Attributes: root (ToolNode): Root node of the tree. now_expand_num (int): Maintains current expanding number for nodes during traversal. """ def __init__(self): self.root: ToolNode = ToolNode() self.root.expand_num = 0 self.now_expand_num = 1

从属性设计上可以看到它的两个核心成员:

属性类型语义
rootToolNode树的根节点,默认是一个新建的空ToolNode,其expand_num被固定为 0
now_expand_numint遍历过程中维护的"当前扩展编号",用于给新加入的节点按扩展顺序编号

与 Plan 树的区别:两种树各司其职

XAgent 中还存在另一棵树——由 XAgent/data_structure/plan.py 中的Plan类构成的计划树(Plan Tree),它管理任务计划(含子任务 ID、状态、父子关系)。而TaskSearchTree管理的是每个子任务内部的执行过程:当某个子任务被真正执行时,Agent 每推理并调用一次工具,就会在树上新增一个节点。因此两者是"计划级"与"执行级"两个不同粒度的树结构,读者不应混淆。

构造方法__init__:初始化根节点与扩展编号

__init__方法不接收任何参数,内部只做三件事:

def __init__(self): self.root: ToolNode = ToolNode() self.root.expand_num = 0 self.now_expand_num = 1
  • 创建根节点:直接实例化一个ToolNode并赋给self.root。根节点代表"任务尚未开始"的初始状态,它不携带任何真实的 Agent 行为数据。
  • 根节点不参与扩展:self.root.expand_num = 0,将根节点的扩展编号固定为 0,表示根节点本身不会被当作一次"扩展"。
  • 从 1 开始计数:self.now_expand_num = 1表示当前"下一个将要被扩展的节点"编号为 1,即树中第一个真实操作节点将从编号 1 开始。

注意点(沿用文档说明并结合源码):

  • 该函数无参数,创建TaskSearchTree()即可完成初始化;
  • 初始化的根节点默认不会被扩展;如果业务上需要根节点也参与扩展,可以通过修改其expand_num属性实现(不过在当前 ReACT 实现中根节点始终只作为起始锚点);
  • now_expand_num表示"下一个可分配的扩展编号",它随每次建立父子关系自增,实际反映树上真实节点(不含根)的数量。

查询方法:get_depth与get_subtree_size

TaskSearchTree的深度与子树大小查询都采用"委托给根节点"的实现方式:

def get_depth(self): return self.root.get_depth() def get_subtree_size(self): return self.root.get_subtree_size()

这里的关键在于:树本身不维护任何统计信息,所有统计逻辑都定义在ToolNode上(见 XAgent/data_structure/node.py)。

ToolNode 上的深度计算

ToolNode.get_depth通过递归回溯父节点计算深度:

def get_depth(self): if self.father == None: return 0 return self.father.get_depth() + 1
  • 根节点的father为None,因此深度为0;
  • 每个子节点深度 = 父节点深度 + 1;
  • 由于TaskSearchTree.get_depth()委托给self.root.get_depth(),返回的正是整棵树的最大深度。

使用注意:get_depth依赖父子关系被正确建立(即father指针正确),否则计算结果会出现偏差;同时因为它采用递归实现,极端情况下过深的链可能引起递归开销,需要配合配置中的max_subtask_chain_length限制链长(详见后文)。

ToolNode 上的子树大小计算

ToolNode.get_subtree_size采用递归累加的方式统计以当前节点为根的子树节点总数:

def get_subtree_size(self): if self.children == []: return 1 now_size = 1 for child in self.children: now_size += child.get_subtree_size() return now_size
  • 叶子节点(children为空)子树大小为1;
  • 非叶子节点的大小 =自身 1+ 所有子节点子树大小的累加;
  • 对TaskSearchTree而言,调用get_subtree_size()即得到整棵任务树的总节点数。

值得注意的语义细节:在ToolNode层面,"子树大小"包含当前节点自身(叶子返回 1);而关联文档对TaskSearchTree.get_subtree_size的说明中提到"子树的节点数不包括根节点本身"这一说法,与node.py的实现存在表述差异。以源码为准:TaskSearchTree.get_subtree_size()返回的是root.get_subtree_size(),其中根节点计入统计(根没有子节点时返回 1)。读者在实际阅读旧文档或调试时,应以 XAgent/data_structure/node.py 的实际行为为准,避免被注释误导。

建边方法make_father_relation:建立父子关系并编号

make_father_relation(father, child)是树从"单节点"生长为"链/树"的唯一入口,源码如下:

def make_father_relation(self, father, child): if not (isinstance(father, ToolNode) and isinstance(child, ToolNode)): raise TypeError("Father and child both need to be instances of ToolNode.") child.expand_num = self.now_expand_num self.now_expand_num += 1 child.father = father father.children.append(child)

其执行流程分为三步:

  1. 类型校验:father与child必须同时是ToolNode实例,否则抛出TypeError,提示信息为"Father and child both need to be instances of ToolNode.";
  2. 分配扩展编号:把当前的now_expand_num写入child.expand_num,然后now_expand_num += 1,从而保证树中每个真实节点都拿到唯一的、按加入顺序递增的扩展编号;
  3. 双向建边:将child.father指向father,并把child追加到father.children列表中,完成"父认子、子认父"的双向关联。

注意:

  • 使用前必须确保father与child节点均已创建并存在于树中;
  • 传入非ToolNode类型会直接抛异常,因此调用方(如 ReACT 算法)总是用agent.message_to_tool_node(...)生成的ToolNode来调用;
  • expand_num不仅用于标识顺序,还能配合now_expand_num推导当前树上"真实扩展节点"的数量。

节点基石:ToolNode 的完整结构

要真正用好TaskSearchTree,必须理解其节点类型ToolNode。它继承自抽象基类Node(见 XAgent/data_structure/node.py),初始化时定义了如下字段:

self.father: ToolNode = None self.children: list[ToolNode] = [] self.expand_num = 0 self.data = { "content": "", "thoughts": { "properties": { "thought": "", "reasoning": "", "plan": "", "criticism": "", }, }, "command": { "properties": { "name": "", "args": "", }, }, "tool_output": "", "tool_status_code": ToolCallStatusCode.TOOL_CALL_SUCCESS, } self.history: MessageHistory = MessageHistory() self.workspace_hash_id = ""

各字段含义:

字段类型说明
fatherToolNode父节点指针
childrenlist[ToolNode]子节点列表
expand_numint扩展顺序编号,由make_father_relation分配
datadict节点核心数据:内容、thoughts(思考/推理/计划/批评)、command(命令名与参数)、工具输出、工具调用状态码
historyMessageHistory该节点对应的消息历史(见 XAgent/message_history.py)
workspace_hash_idstr工作区哈希 ID,用于关联文件系统快照

此外ToolNode还提供两个对树的运行至关重要的方法:

  • process(属性):从当前节点一路回溯到根节点,把沿途每个节点的data按"根→当前"顺序拼成一个列表,供 ReACT 算法构造"你已经完成的步骤"提示词使用;
  • to_json:对data做深拷贝,并把tool_status_code枚举值转换成其名称字符串(如"TOOL_CALL_SUCCESS"),得到 JSON 兼容格式,便于持久化或回放展示。

ToolNode的详细字段说明与示例可见配套文档 Markdown_Docs/XAgent/data_structure/node.md。

实战:TaskSearchTree 在 ReACT 内层搜索中的调用链

TaskSearchTree并非孤立存在,它被内层循环搜索算法ReACTChainSearch直接使用,实现在 XAgent/inner_loop_search_algorithms/ReACT.py 中。该算法继承自 XAgent/inner_loop_search_algorithms/base_search.py 的BaseSearchMethod,在初始化时维护了一个树列表:

class ReACTChainSearch(BaseSearchMethod): def __init__(self, xagent_core_components: XAgentCoreComponents): super().__init__() self.tree_list = [] self.finish_node = None self.xagent_core_components = xagent_core_components

每轮尝试生成一棵新树

在generate_chain方法中,每次尝试(attempt)都会追加一棵全新的TaskSearchTree:

self.tree_list.append(TaskSearchTree()) now_attempt_tree = self.tree_list[-1] now_node = now_attempt_tree.root

也就是说,tree_list中每棵树对应一次完整的"链式搜索尝试",多次尝试(max_try)失败或成功后由run方法统一判定搜索状态(SearchMethodStatusCode.HAVE_AT_LEAST_ONE_ANSWER/FAIL,见 XAgent/utils.py 中的枚举定义)。

循环生长:深度受限的链式扩展

树的生长发生在while循环中,其终止条件直接使用树的深度:

while now_node.get_depth() < config.max_subtask_chain_length: ... new_tree_node = agent.message_to_tool_node(new_message) ... tool_output, tool_output_status_code, need_for_plan_refine, using_tools = \ self.xagent_core_components.function_handler.handle_tool_call(new_tree_node) ... now_attempt_tree.make_father_relation(now_node, new_tree_node) ... now_node = new_tree_node

关键点:

  1. 深度即进度:now_node.get_depth()表示当前链已走了多少步,当它达到配置的max_subtask_chain_length时循环停止,防止无限生长;
  2. 节点来源:new_tree_node由agent.message_to_tool_node(new_message)生成——该方法(见 XAgent/agent/tool_agent/agent.py)把 LLM 返回的 message(含content、arguments、function_call)转换为一个携带思考与命令的ToolNode,其中data["command"]["properties"]["name"]就是 Agent 决定调用的工具名;
  3. 边即操作记录:make_father_relation(now_node, new_tree_node)把上一步节点与新节点连成链,expand_num按 1、2、3……依次分配;
  4. 状态即结束信号:当tool_output_status_code为SUBMIT_AS_SUCCESS或SUBMIT_AS_FAILED时中断循环,self.finish_node = now_node记录终点节点,供上层(如 XAgent/workflow/working_memory.py 中注册子任务并记录finish_node.get_depth()作为处理长度)使用。

节点数据如何回放给 LLM

树的链式结构还被用于构造下一轮推理的上下文:make_message(now_node, ...)读取now_node.process(即从根到当前节点的所有data序列),并在config.enable_summary开启时用summarize_action压缩后作为"你已经完成的步骤"注入用户消息。这样 LLM 每走一步都能"看到"整条历史链,而历史链正是由TaskSearchTree一步步累积起来的。

配置联动:用max_subtask_chain_length约束树高

树的高度上限来自全局配置项max_subtask_chain_length,默认配置见 assets/gpt-3.5-turbo_config.yml:

max_subtask_chain_length: 15

配套的常用配置还包括:

max_plan_refine_chain_length: 3 # 计划精炼链长度 max_plan_tree_depth: 3 # 计划树最大深度 max_plan_tree_width: 5 # 计划树最大宽度 enable_ask_human_for_help: False # 是否允许向人类求助

同一套配置也出现在 assets/xagentllama.yml。从源码看,max_subtask_chain_length在 ReACT.py 中被三处使用:作为while循环终止条件、判断是否强制调用subtask_submit(当now_node.get_depth() == config.max_subtask_chain_length - 1时,最后一步必须提交子任务)、以及作为提示词中的max_length占位符。由此可见,调大该值可让 Agent 在单个子任务内执行更多步骤(链更深),但也意味着更长的上下文与更多工具调用;调小则会更快进入subtask_submit收尾。

最小可运行示例:手动搭建一棵任务树

综合 Markdown_Docs/XAgent/data_structure/tree.md 的示例输出与源码实现,可以手动构造一棵任务树并验证各方法行为:

from XAgent.data_structure.node import ToolNode from XAgent.data_structure.tree import TaskSearchTree # 1. 初始化一棵任务树 tree = TaskSearchTree() print(tree.get_depth()) # 0:初始只有根节点 print(tree.get_subtree_size()) # 1:根节点自身计为 1 # 2. 构造两个真实操作节点并建立父子关系 father = ToolNode() child = ToolNode() tree.make_father_relation(tree.root, father) # father 的 expand_num = 1 tree.make_father_relation(father, child) # child 的 expand_num = 2 print(tree.get_depth()) # 2:root -> father -> child print(tree.get_subtree_size()) # 3:三个节点 print(father.expand_num, child.expand_num) # 1 2 print(child.father is father, father.children) # True [child] # 3. 类型校验:非 ToolNode 会抛 TypeError try: tree.make_father_relation(father, "not a node") except TypeError as e: print(e) # Father and child both need to be instances of ToolNode.

小结:TaskSearchTree 的设计要点

  • 组合而非继承:TaskSearchTree内部持有ToolNode根节点,统计逻辑全部下沉到节点层,树类只做转发,职责清晰;
  • 编号机制:now_expand_num与expand_num配合,为每个操作节点提供全局唯一的扩展顺序号;
  • 深度受限:树的生长深度由配置max_subtask_chain_length控制,从源头规避了递归统计与上下文无限膨胀的风险;
  • 贯穿执行主链路:从 ReACT 搜索到工作记忆注册,TaskSearchTree提供的深度、终点节点与process链式数据,是 XAgent 实现"复杂任务多步求解、可回溯、可总结、可提交"的底层支撑。

如果需要进一步了解节点细节与搜索算法整体流程,可继续阅读仓库内的 Markdown_Docs/XAgent/data_structure/node.md 与 Markdown_Docs/XAgent/inner_loop_search_algorithms/ReACT.md。

  • AI Agent
  • 大模型
  • 后端
  • 任务调度

【免费下载链接】XAgent

An Autonomous LLM Agent for Complex Task Solving

项目地址:https://gitcode.com/gh_mirrors/xa/XAgent
点击查看免费下载
上一篇:flow-to-typescript-codemod与React:从React.Node到React.ReactNode的转换技巧
下一篇:【亲测免费】 使用node-neo4j连接Neo4j数据库教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询