源码深读 XAgent:6 个 Agent 怎么分工?工具失败不崩、死循环怎么防?

源码深读 XAgent:6 个 Agent 怎么分工?工具失败不崩、死循环怎么防?

本文基于 XAgent 主仓库源码,梳理多 Agent 架构的设计动机、工具调用可靠性机制,以及防死循环 / 防「扯皮」的工程手段。适合正在做 Agent 框架、或想从开源项目里抄作业的同学。


一、大纲

  1. 整体执行模型:双层循环(Outer Loop + Inner Loop)
  2. Agent 全景:6 个 LLM Agent + 工作流编排类
  3. 为什么这么拆:职责分离、认知任务解耦
  4. 工具调用可靠性:LLM 层 / Schema 层 / ToolServer 层三层防护
  5. 防死循环与防扯皮:硬上限、单向推进、提交驱动
  6. 关键配置参数速查
  7. 技术债与未实现能力

二、整体执行模型:双层循环

XAgent 的核心编排器是 TaskHandlerXAgent/workflow/task_handler.py),它把一次用户 Query 的处理拆成两层:

scss 复制代码
用户 Query
  → PlanAgent.initial_plan_generation()     [PlanGenerateAgent]
  → while (下一个 TODO 子任务):              [Outer Loop,中序遍历计划树]
       inner_loop()                          [Inner Loop,ReACT + ToolAgent]
         → 推理 → 工具调用 → 观察结果
         → 直到 subtask_submit 或达步数上限
       posterior_process()                    [ReflectAgent]
       working_memory.register_task()
       if need_for_plan_refine:
         plan_refine_mode()                  [PlanRefineAgent,最多 N 步]
       pop_next_subtask()                    [线性前进,不回溯]

Outer Loop 负责按子任务顺序推进全局计划;Inner Loop 负责在单个子任务内用 ReACT 范式反复「思考 → 行动 → 观察」。两层解耦是后续所有可靠性设计的基础:计划修改和工具执行不在同一个循环里打架。


三、Agent 全景:谁负责什么?

3.1 注册在 available_agents 的 4 个执行 Agent

XAgent/core.py 中注册:

Agent 能力枚举 核心职责
PlanGenerateAgent plan_generation 将用户 query 分解为 2--4 个子任务的初始计划树
PlanRefineAgent plan_refinement 子任务完成后,按 split/add/delete/exit 操作迭代修正未来子任务
ToolAgent tool_tree_search Inner Loop 内:推理 → 调工具 / subtask_submit
ReflectAgent reflection 子任务结束后提取后验知识(summary、plan reflection、tool reflection)

3.2 元 Agent:DispatcherAgent

DispatcherAgent 不执行任务本身 ,而是为其他 Agent 按当前子任务动态生成附加 user prompt 。它通过 XAgentDispatcher.dispatch() 被调用:

  1. 取目标 Agent 的 example prompt
  2. enable=True:调用 DispatcherAgent.parse() 生成定制化附加 prompt
  3. 解析失败则 fallback 到默认 prompt
  4. build_agent() 实例化目标 Agent

注意 :当前默认配置 enable=Falsecore.py),生产路径直接使用静态 prompt 模板,不经 Dispatcher 动态 refine。

3.3 工作流编排类(非 BaseAgent,但承担 Agent 职能)

路径 职责
PlanAgent workflow/plan_exec.py 计划生命周期:初始生成 →(可选)记忆 refine → plan_refine_mode
TaskHandler workflow/task_handler.py 主编排器:outer loop + inner loop + posterior_process
ReACTChainSearch inner_loop_search_algorithms/ReACT.py 单链 ReACT 执行器,驱动 ToolAgent
WorkingMemoryAgent workflow/working_memory.py 跨子任务记忆(chat_with_other_subtask 工具,handle 未完整实现)

3.4 摘要模块:summarize.py(函数式,非 Agent 类)

函数 调用时机 作用
summarize_action() ReACT 每步 make_message() 压缩工具调用历史,提取 key_actions
summarize_plan() ReACT / PlanRefine / Reflect / TaskHandler 多处 按 token 预算压缩计划树

与 ReflectAgent 的分工:summarize 压缩原始轨迹Reflect 输出结构化后验知识供下一子任务消费。

3.5 枚举存在但未实现的 Agent

  • task_evaluator:枚举在 utils.py无实现类
  • summarization:有 SummarizationTrieTree 骨架,未接入主流程

四、为什么这么拆?设计哲学

4.1 能力分派,而非单体超级 Agent

每个 Agent 通过 RequiredAbilities 枚举声明单一能力集合,由 AgentDispatcher.agent_markets 按能力路由。好处:

  • Prompt 专精:PlanGenerate 专注全局分解,ToolAgent 专注逐步执行,互不污染
  • 可替换:同一能力可注册多个 Agent 实现,Dispatcher 选第一个
  • 可测试:每个 Agent 的输入输出边界清晰

4.2 计划生成 vs 计划修正:两种认知任务

PlanGenerate PlanRefine
时机 任务开始 子任务完成后
权限 创建完整初始计划 只能修改 subtask_id > now_dealing_task未来任务
操作 一次性分解 split / add / delete / exit,最多 3 步
约束 2--4 个子任务 树深 ≤ 3、树宽 ≤ 5

把「从零规划」和「局部修正」拆开,避免执行中的 Agent 随意改已完成任务,这是防扯皮的第一道防线。

4.3 执行与反思分离:ToolAgent 不自我辩论

ToolAgent 在 Inner Loop 只做三件事:思考、调工具、提交(subtask_submit)。它不修改计划 ,若发现后续子任务需要调整,通过 subtask_submit.suggestions_for_latter_subtasks_plan.need_for_plan_refine 把意图交给 PlanRefineAgent。

ReflectAgent 在子任务结束后单次调用 ,输出 action_list_summaryposterior_plan_reflection 等,供后续子任务读取。没有「ToolAgent 和 PlanRefine 同环对话」的设计,从根本上杜绝多 Agent 来回辩论。

4.4 FunctionHandler 统一中介

所有工具调用(含 intrinsic tools:subtask_submitask_human_for_help)都经 FunctionHandler.handle_tool_call() 路由,集中做:

  • 命令分发
  • 状态码映射(ToolCallStatusCode
  • ToolServer 超时重试
  • 长结果压缩(long_result_summary

Agent 层不直接碰 HTTP,降低耦合。

4.5 summarize 独立:控制 Token 预算

ReACT 每步可能积累大量工具输出。summarize_action / summarize_plan 在写入 message history 前做压缩(clip_text、LLM 摘要),防止上下文爆炸导致 LLM 调用失败或成本失控。


五、工具调用可靠性:三层防护

5.1 LLM 层:重试 + 上下文降级

位置 机制 配置
ToolAgent.parse() @retry(stop=stop_after_attempt(max_retry_times)) max_retry_times: 3
openai.py chatcompletion_request tenacity 指数退避 61--293s;不重试 Auth/Permission/BadRequest max_retry_times + 3
obj_generator.py chatcompletion Schema 校验失败重试 3 次 内置

上下文超长时自动降级到更大模型(gpt-4gpt-4-32k 等),见 openai.py 第 69--90 行。

此外,obj_generator.chatcompletion() 通过 recorder.query_llm_inout() 查缓存;experiment.redo_action: false 时优先用缓存,避免重复 LLM 调用。

5.2 Schema 层:幻觉工具名与坏 JSON 修复

obj_generator.py

  • function_call_refine():校验 function name 是否在允许列表;幻觉工具名时注入 system error message 再抛 FunctionCallSchemaError 触发重试
  • dynamic_json_fixes():broken JSON 时让 LLM 修复后重新校验
  • load_args_with_schema_validation():参数 schema 失败时自动修复一次

ToolAgent.parse()(OpenAI 模式):

python 复制代码
# tool_agent/agent.py 第 116--125 行
jsonschema.validate(tool_call_args, schema)
# 失败 → objgenerator.dynamic_json_fixes() → 再 validate

双重校验确保 LLM 输出的 function call 结构合法,减少「调了个不存在的工具」的情况。

5.3 ToolServer 层:状态码映射 + 超时重试

HTTP 状态码 → ToolCallStatusCode

HTTP 状态码 含义
200 TOOL_CALL_SUCCESS 成功
404 HALLUCINATE_NAME 工具名不存在
422 FORMAT_ERROR 参数格式错误
450 TIMEOUT_ERROR 超时
500 TOOL_CALL_FAILED 执行失败
503 SERVER_ERROR 服务不可用(抛异常)

超时重试function_handler.py 第 219--231 行):

python 复制代码
MAX_RETRY = 10
while retry_time < MAX_RETRY and status == TIMEOUT_ERROR and detail['type'] == 'retry':
    time.sleep(3)
    # 用 detail['next_calling'] + detail['arguments'] 重试

ToolServer 可通过异常 type='retry' 告知客户端「这次超时但可重试」,最多 10 次、每次间隔 3 秒。超过上限则返回友好错误信息,不抛异常阻断主流程

关键设计:失败不阻断 ReACT 循环

工具失败时,tool_status_code 写入 ToolNode.data,结果作为 system message 追加到 history,ReACT 继续下一步 (除非触发 subtask_submit)。Agent 可以从错误中学习,而不是一失败就 crash。

5.4 长结果处理

  • long_result_summary():网页类工具调用 parse_web_text 压缩;字符串 >2000 字符有 summarize 占位
  • clip_text(file_archi, 1000) 限制文件系统结构展示
  • summary.single_action_max_length: 4096max_return_length: 8192 控制摘要输出

5.5 尚未实现的可靠性机制

  • 无 Circuit Breaker:ToolServer 503 直接抛异常
  • TIME_LIMIT_EXCEEDED 枚举存在但未使用
  • tool_call_count 在 TaskHandler 中初始化为 0 但从未递增
  • ToolServer 请求 timeout 硬编码 10--20s,非统一配置

六、防死循环与防扯皮

6.1 ReACT Inner Loop:硬步数上限

ReACT.py 第 220--312 行:

python 复制代码
while now_node.get_depth() < config.max_subtask_chain_length:  # 默认 15
    ...
    if now_node.get_depth() == config.max_subtask_chain_length - 1:
        function_call = {"name": "subtask_submit"}  # 最后一步强制提交

    if tool_output_status_code == SUBMIT_AS_SUCCESS:
        break
    elif tool_output_status_code == SUBMIT_AS_FAILED:
        break
机制 值/行为
硬循环上限 max_subtask_chain_length: 15
最后一步强制提交 不允许无限调工具
Prompt 预算提醒 "You can at most use {{max_length}} steps"
多链重试 max_try=1(默认不重试整条链)
退出条件 subtask_submit 的 success/failed

尽管类名含 Search 且维护 TaskSearchTree,实际是单链而非树搜索分支------没有 backtracking,不会在同一子任务内反复探索多条路径。

6.2 Plan Refine:有限修正 + 显式退出

plan_exec.py plan_refine_mode()

python 复制代码
while modify_steps < max_step:  # max_plan_refine_chain_length: 3
    ...
    if operation == 'exit':
        output_status_code = PLAN_REFINE_EXIT
    if output_status_code in (PLAN_REFINE_EXIT, MODIFY_SUCCESS):
        return
    modify_steps += 1
  • 最多 3 步 refine
  • exit 操作可随时退出
  • 一次 MODIFY_SUCCESS 即 return
  • 计划树深度 ≤ 3、宽度 ≤ 5 在代码中强制校验

6.3 子任务单向推进:不回溯、不辩论

  • 中序遍历 + 单向前进Plan.pop_next_subtask() 只取当前任务之后第一个 TODO 子任务,不回溯已完成任务
  • Plan Refine 编辑边界deal_subtask_split/delete/addcan_edit 逻辑限制只能改未来子任务
  • 无多 Agent 对话环:Reflect 仅在子任务结束后单次调用;ToolAgent 不与 PlanRefine 同环运行

这三条合在一起,解决了 Agent 系统里最常见的两类「扯皮」:

  1. 执行中反复改计划 → 执行与修正分离,修正有步数上限
  2. 多 Agent 互相推翻结论 → 单向流水线,后验知识只写一次

6.4 人机交互中断(非死循环,但可打断)

  • interaction.interrupt 时用户可改写 goal(outer loop)或 thoughts(inner loop)
  • ask_human_for_help 是同步阻塞等待用户输入,然后继续循环------这是「等人」,不是 Agent 间辩论

6.5 上下文压缩(间接防无限膨胀)

enable_summary: true 时 ReACT 每步用 summarize_action / summarize_plan 压缩历史,避免 message 无限增长导致 LLM 反复因 context 超长而失败重试,形成隐性死循环。


七、关键配置参数速查

来源:assets/gpt-3.5-turbo_config.yml

yaml 复制代码
max_retry_times: 3                    # LLM 调用重试
max_subtask_chain_length: 15          # ReACT 单链子任务最大步数
max_plan_refine_chain_length: 3       # 计划修正最大迭代
max_plan_tree_depth: 3                # 计划树深度上限
max_plan_tree_width: 5                # 计划树宽度上限
max_plan_length: 4096
enable_summary: true                  # 上下文压缩开关
summary:
  single_action_max_length: 4096
  max_return_length: 8192
enable_ask_human_for_help: false
experiment:
  redo_action: false                  # false = 使用 LLM/Tool 缓存

八、架构关系图

flowchart TB subgraph Entry[&#34;入口&#34;] Server[&#34;XAgentServer&#34;] end subgraph Core[&#34;XAgentCoreComponents&#34;] Dispatcher[&#34;XAgentDispatcher<br/>(enable=False)&#34;] FH[&#34;FunctionHandler&#34;] TSI[&#34;ToolServerInterface&#34;] WM[&#34;WorkingMemoryAgent&#34;] end subgraph OuterLoop[&#34;Outer Loop --- TaskHandler&#34;] PA[&#34;PlanAgent&#34;] PGen[&#34;PlanGenerateAgent&#34;] PRef[&#34;PlanRefineAgent&#34;] Refl[&#34;ReflectAgent&#34;] end subgraph InnerLoop[&#34;Inner Loop --- ReACTChainSearch&#34;] TA[&#34;ToolAgent&#34;] Sum[&#34;summarize.py&#34;] end Server --> TH[&#34;TaskHandler&#34;] TH --> PA PA -->|plan_generation| Dispatcher --> PGen TH -->|per subtask| InnerLoop InnerLoop --> Dispatcher --> TA TA --> FH --> TSI InnerLoop --> Sum TH -->|posterior| Refl TH -->|plan_refine| PRef TH --> WM

九、技术债与展望

缺口 影响
SummarizationTrieTree 未接入主流程 Trie 增量摘要能力闲置
task_evaluator 无实现 无法自动评估子任务质量
tool_call_count 未使用 无法做全局工具调用预算
无 Circuit Breaker ToolServer 故障可能拖垮整条链
Dispatcher 默认关闭 动态 prompt 能力未在生产路径启用
ReACTChainSearch 实为单链 命名与实现不符,无真正树搜索
WorkingMemoryAgent handle 未完整 跨子任务对话能力预留但未落地

十、总结

XAgent 的多 Agent 拆分遵循一条清晰原则:不同认知任务用不同 Agent,用硬上限和单向流水线约束协作边界

  • 可靠性:LLM 重试 + Schema 修复 + ToolServer 超时重试 + 失败写入 history 继续执行
  • 防死循环:ReACT 15 步硬上限 + 最后一步强制 submit + Plan Refine 3 步上限
  • 防扯皮:执行/修正/反思三阶段分离 + 子任务单向推进 + 只能改未来计划

如果你在做自己的 Agent 框架,最值得抄的三件事是:FunctionHandler 统一中介subtask_submit 驱动计划变更summarize 控制 token 预算。其余部分(Dispatcher 动态 prompt、WorkingMemory 跨任务通信)在源码里还是半成品,正好留给你改进的空间。


相关推荐
触底反弹1 小时前
🔥 2026 年爆火的 Harness Engineering 到底是什么?从原理到实战一文讲透
javascript·人工智能·程序员
魏祖潇1 小时前
SDD 完整指南——Spec 端打底、Story 端交付、留白区
人工智能·后端
常丛丛1 小时前
5.9 式输出:实时查看 LangGraph Agent 思考过程
人工智能
Token炼金师1 小时前
从节点图到低秩矩阵:ComfyUI 推理引擎与 LoRA 适配机制拆解
人工智能·aigc
武子康1 小时前
调查研究-210 Netflix 用 AI 复刻 Gene Wilder 的声音:语音克隆的下半场,不是模型,而是权利
人工智能·aigc·openai
Quz1 小时前
在 Obsidian 中嵌入 Claude Code 的实践记录
人工智能·claude
雪隐2 小时前
个人电脑玩AI-10让5060 Ti给你打工——部署 Odysseus:终于有个能打的"AI管家"了
人工智能·后端
武子康2 小时前
调查研究-209 Apptronik Robot Park 深度解析:人形机器人竞争,开始拼“真实世界数据工厂“
人工智能·google·llm
IT_陈寒2 小时前
Vite打包时踩的坑:静态资源为啥突然404了?
前端·人工智能·后端