本节摘要:本节攀上 agency 阶梯的 ★★ 层——层级式多智能体。核心机制一句话:子 agent 即工具。manager agent 通过
managed_agents参数持有子 agent,_setup_managed_agents把每个子 agent 包装出task/additional_args两个输入(和 Tool 的 inputs 同构);run()里self.python_executor.send_tools({**self.tools, **self.managed_agents})把子 agent 与工具一起注入解释器命名空间,于是 manager 写的代码里可以直接search_agent(task="帮我查……");子 agent 有独立的 memory/model/max_steps,跑完后其最终答案经__call__包装成报告字符串返回。本节贴 agents.py 关键源码与 code_agent.yaml 的 managed_agent 提示词段,并对照官方 multiagents 教程与层级式/平铺式两种编排。
内容来源:原项目源码
src/smolagents/agents.py(1813 行)、prompts/code_agent.yaml、官方文档examples/multiagents.md与examples/multi_llm_agent.py,精读并套用体系化模板。
⚠️ 注意:被管理的子 agent 必须同时提供
name与description(agents.py:373 有断言),name 还必须是合法 Python 标识符(agents.py:364-366)——因为 name 会被直接用作解释器里的函数名与工具名,和已有工具重名会直接抛ValueError(agents.py:404-414)。
阅读完本节,你应当能够:
managed_agents=[...] 收编。_setup_managed_agents 如何把 agent"伪装"成 Tool(inputs/output_type 同构)。send_tools({**self.tools, **self.managed_agents}) 为何是"子 agent 即工具"的关键一行。__call__ → managed_agent.task 模板 → run() → 最终答案 → report 模板),并区分层级式与平铺式编排的适用场景。从构造函数看起(agents.py:294-340,MultiStepAgent 基类,CodeAgent/ToolCallingAgent 通吃):
294 def __init__( 295 self, 296 tools: list[Tool], 297 model: Model, ... 303 managed_agents: list | None = None, ... 306 name: str | None = None, 307 description: str | None = None, 308 provide_run_summary: bool = False, ... 312 ): ... 332 self.name = self._validate_name(name) 333 self.description = description 334 self.provide_run_summary = provide_run_summary 338 self._setup_managed_agents(managed_agents) 339 self._setup_tools(tools, add_base_tools) 340 self._validate_tools_and_managed_agents(tools, managed_agents)
注意传参形态:用户传 list[agent],内部立即转成字典(_setup_managed_agents,agents.py:369-387):
369 def _setup_managed_agents(self, managed_agents: list | None = None) -> None: 371 self.managed_agents = {} 372 if managed_agents: 373 assert all(agent.name and agent.description for agent in managed_agents), ( 374 "All managed agents need both a name and a description!" 375 ) 376 self.managed_agents = {agent.name: agent for agent in managed_agents} 377 # Ensure managed agents can be called as tools by the model: set their inputs and output_type 378 for agent in self.managed_agents.values(): 379 agent.inputs = { 380 "task": {"type": "string", "description": "Long detailed description of the task."}, 381 "additional_args": { 382 "type": "object", 383 "description": "Dictionary of extra inputs to pass to the managed agent, e.g., images, dataframes, ...", 384 "nullable": True, 385 }, 386 } 387 agent.output_type = "string"
L379-387 是伪装术的核心:给子 agent 动态贴上 inputs(task/additional_args 两个参数)和 output_type="string"——这正是第 5 章 Tool 基类的三个属性(name/description/inputs/output_type)。至此子 agent 与 Tool 在接口上完全同构,manager 的模型根本分不清"这是个工具"还是"这是个 agent"。
关键一行在 run() 里(agents.py:490-492):
490 if getattr(self, "python_executor", None): 491 self.python_executor.send_variables(variables=self.state) 492 self.python_executor.send_tools({**self.tools, **self.managed_agents})
send_tools 收到的字典被合并进解释器的 static_tools(local_python_executor.py:1763-1764),与 BASE_PYTHON_TOOLS(print/len/range 等)并列。于是第 6 章的 AST 解释器在解析名字引用时,search_agent 和 final_answer、print 走的是同一条解析路径——都是命名空间里的可调用对象。manager 生成的代码长这样:
report = search_agent( task="Find the 2024 GDP growth rate of the US, with source.", additional_args={"timeframe": "2024"}, ) print(report)
调用链穿过四层(见下一节源码):Agent.__call__ 拼装任务提示词 → self.run(full_task) 独立执行完整 ReAct/Code 循环 → 拿到最终答案 → 包上报告模板返回字符串。这个字符串作为观察结果进入 manager 的下一步推理。
每个子 agent 是全功能 agent:拥有独立的 memory(自己的 AgentMemory,不污染 manager 的上下文)、独立的 model(可以用便宜模型跑检索、贵模型做综合)、独立的 max_steps(浏览器 agent 可以给 20 步,总结 agent 只给 5 步)。上下文隔离是多智能体最实际的收益——子 agent 翻了 50 个网页也不会把 manager 的窗口撑爆,manager 只看到一份浓缩报告。
💡 阶梯要点:第 3 章说"代码即 action"让 agent 能组合函数、传递变量;本节让它升维——可组合的函数里如今多了"另一个完整 agent"。抽象没有增加:没有新的调用协议、没有新的消息总线,复用的正是 Tool 接口 + Python 函数调用。这是 smolagents 多智能体实现只有约 50 行的原因。
__call__ 四步链agents.py:868-890,子 agent 被 manager 调用时走这段:
868 def __call__(self, task: str, **kwargs): 869 """Adds additional prompting for the managed agent, runs it, and wraps the output. 870 This method is called only by a managed agent.""" 872 full_task = populate_template( 873 self.prompt_templates["managed_agent"]["task"], 874 variables=dict(name=self.name, task=task), 875 ) 876 result = self.run(full_task, **kwargs) 877 if isinstance(result, RunResult): 878 report = result.output 879 else: 880 report = result 881 answer = populate_template( 882 self.prompt_templates["managed_agent"]["report"], variables=dict(name=self.name, final_answer=report) 883 ) 884 if self.provide_run_summary: 885 answer += "\n\nFor more detail, find below a summary of this agent's work:\n<summary_of_work>\n" 886 for message in self.write_memory_to_messages(summary_mode=True): 887 answer += "\n" + truncate_content(str(content)) + "\n---" 889 answer += "\n</summary_of_work>" 890 return answer
四步:加壳(managed_agent.task 模板把裸任务包成"你叫 X,manager 交给你这个任务……要求详细汇报")→ 独立执行(自己的 run,自己的记忆)→ 剥壳(取最终答案)→ 再包装(report 模板"Here is the final answer from your managed agent 'X'" + 可选的执行摘要)。provide_run_summary=True 时还会附上子 agent 的工作纪要——open_deep_research 的搜索 agent 就开了这个开关,让 manager 能看到搜索过程的关键线索。
manager 怎么知道自己有哪些"队友"?系统提示词里有一段条件渲染(code_agent.yaml:139-160,节选):
139 {%- if managed_agents and managed_agents.values() | list %} 140 You can also give tasks to team members. 141 Calling a team member works similarly to calling a tool: provide the task description as the 'task' argument. Since this team member is a real human, be as detailed and verbose as necessary in your task description. 143 Here is a list of the team members that you can call: 145 {%- for agent in managed_agents.values() %} 146 def {{ agent.name }}(task: str, additional_args: dict[str, Any]) -> str: 147 """{{ agent.description }} ... 153 {% endfor %} 154 {{code_block_closing_tag}} 155 {%- endif %}
子 agent 被渲染成一个 Python 函数签名加 docstring——和工具的 to_code_prompt() 一模一样的呈现方式。有趣的是 L141 那句"Since this team member is a real human"——提示模型像给人派活一样写清楚完整上下文,而不是丢几个关键词。同一段模板在 planning 区(code_agent.yaml:269-285)还重复一遍,保证规划时也考虑得到队友。反向的模板在 code_agent.yaml:288-307:子 agent 收到的任务会被要求按"任务结果(短)/(极详)/补充上下文"三段式汇报,失败也要带上下文回传。
官方 multiagents 教程的标准架构——manager 拿代码解释器,子 agent 拿浏览器:Manager agent(CodeAgent,负责规划与计算)下挂 Web Search agent(ToolCallingAgent,配 WebSearchTool + visit_webpage 两件工具,max_steps=10)。核心代码(节选):
web_agent = ToolCallingAgent( tools=[WebSearchTool(), visit_webpage], model=model, max_steps=10, name="web_search_agent", description="Runs web searches for you.", ) manager_agent = CodeAgent( tools=[], model=model, managed_agents=[web_agent], additional_authorized_imports=["time", "numpy", "pandas"], ) answer = manager_agent.run("If LLM training continues to scale up ... Please provide a source for any numbers used.")
选型理由同样值得学:浏览是"单时间线"任务(点开一个页面读一个页面),用 ToolCallingAgent 就够;manager 需要规划与数值计算,用 CodeAgent 并放开 time/numpy/pandas 导入。examples/inspect_multiagent_run.py 用 Phoenix 遥测回放了这套系统,并打印 run_result.token_usage 与 timing——第 7 章的可观测性直接派上用场。
examples/multi_llm_agent.py 则演示模型层的多后端协作:LiteLLMRouterModel 把 gpt-4o-mini 与 bedrock Claude 塞进同一个 model-group-1,simple-shuffle 路由;同理,每个子 agent 也可以在构造时各持一个 Model 实例——搜索子 agent 用便宜快速的模型,综合子 agent 用旗舰模型,成本与质量各取所需。
| 维度 | 层级式(smolagents) | 平铺式(如消息总线型框架) |
|---|---|---|
| 拓扑 | 树:manager 派活、收报告 | 网:agent 互相广播消息 |
| 通信载体 | 函数调用 + 返回字符串 | 消息队列/共享黑板 |
| 上下文 | 天然隔离,子 agent 记忆不外泄 | 需刻意裁剪,易互相污染 |
| 复用 | 子 agent=Tool,可直接挂到任何 manager | 通常绑定特定编排框架 |
| 实现代价 | 约 50 行(send_tools 合并字典) | 需要一套消息路由子系统 |
| 适用 | 任务可分解派发(研究/检索/分析) | 对等协商、辩论类任务 |
smolagents 选层级式并非偷懒:当"派活-汇报"模型够用时,树结构是最可控、最易调试的(Phoenix 里就是一棵 trace 树);而且子 agent 的 manager 也可以再有自己的 managed_agents,树可以继续长——open_deep_research 就在两级之间还嵌了工具集合。若确需平铺协作,把两个 agent 互设为对方的 managed agent(注意避免循环调用)或在工具层共享状态,都是社区常见的变通。
list[agent],内部转 {name: agent} 字典;子 agent 必须有 name(合法标识符)与 description,且不能与工具/自己重名。_setup_managed_agents 给子 agent 贴上 inputs={task, additional_args} 与 output_type="string",接口与 Tool 同构。run() 里 send_tools({**self.tools, **self.managed_agents}) 把两者合并注入解释器命名空间,manager 代码中直接 sub_agent_name(task=...) 调用,与 print/final_answer 同路径解析。__call__ 四步链:task 模板加壳 → 独立 run → 取最终答案 → report 模板包装;provide_run_summary=True 附工作纪要。下一节:把本章机制拉满——精读 examples/open_deep_research(GAIA 基准复现、最大完整示例工程),再速览 text_to_sql/rag/async_agent/plan_customization 等实战案例。