第 2 章 · 01 MultiStepAgent 基类与 run/step 生命周期


第 2 章 · 01 MultiStepAgent 基类与 run/step 生命周期

本节摘要:本节精读 src/smolagents/agents.py(1813 行)的地基部分——MultiStepAgent 抽象基类。它是 ToolCallingAgent 与 CodeAgent 的共同父类,规定了 agent 的完整生命周期:run() 是任务入口(初始化记忆 → 写入系统提示步与任务步 → 进入 while 循环反复调 step() → 直到拿到 FinalAnswer 或撞上 max_steps);step() 是单步"思考-行动-观察"(组装消息 → LLM 生成 → 解析动作 → 执行 → ActionStep 写回记忆)。子类只需覆写 _step_streaminitialize_system_prompt 两个钩子。本节最后讲清 utils.py 的错误异常体系与重试器——agent 框架的一半工程量其实在"出错之后怎么办"。

内容来源:原项目源码 src/smolagents/agents.py(第 268-787 行为主)、src/smolagents/utils.py(错误体系)、src/smolagents/memory.py(ActionStep)。

⚠️ 注意:本版源码中 MultiStepAgent.max_steps 默认值是 20(不是旧文档写的 6),且 run(max_steps=...) 可按次覆盖。另外 _run_stream 是生成器实现,step() 只是"把生成器消费完取最后一个元素"的兼容壳——精读时以 _run_stream 为主线。

学习目标

阅读完本节,你应当能够:

  1. 说出 MultiStepAgent.__init__ 十余个参数中最常用的五个(tools/model/max_steps/instructions/planning_interval)。
  2. 逐行讲解 run() 从任务字符串到最终答案的全过程。
  3. 解释 _run_stream 的 while 循环何时退出、finally 块为什么保证 ActionStep 必然入记忆。
  4. 说清 write_memory_to_messages 如何把记忆转成 LLM 消息列表。
  5. 画出 AgentError 异常树,并解释 AgentGenerationError 为何被"上抛"而其它错误被"记录后继续"。

一、类声明与 init:agent 的配置中心

agents.py:268 起的类声明与初始化(节选):

268 class MultiStepAgent(ABC): 269 """ 270 Agent class that solves the given task step by step, using the ReAct framework: 271 While the objective is not reached, the agent will perform a cycle of action 272 (given by the LLM) and observation (obtained from the environment). 273 """ 294 def __init__( 295 self, 296 tools: list[Tool], 297 model: Model, 298 prompt_templates: PromptTemplates | None = None, 299 instructions: str | None = None, 300 max_steps: int = 20, 301 add_base_tools: bool = False, 302 verbosity_level: LogLevel = LogLevel.INFO, 303 managed_agents: list | None = None, 304 step_callbacks: list[Callable] | ... | None = None, 305 planning_interval: int | None = None, 306 name: str | None = None, 307 description: str | None = None, 308 provide_run_summary: bool = False, 309 final_answer_checks: list[Callable] | None = None, 310 return_full_result: bool = False, 311 logger: AgentLogger | None = None, 312 ):

关键参数解读:

参数 作用
tools / model 仅有的两个必填项:工具列表与 LLM 后端
max_steps(默认 20) 动作步数上限,防止无限循环烧 token
instructions 追加到系统提示的自定义指令
add_base_tools 是否补齐内置工具(web_search 等)
planning_interval 每 N 步插入一次规划步(第 4 章)
managed_agents 子 agent 列表(第 8 章),此处只做 name/description 校验
final_answer_checks 最终答案的校验函数列表,不通过则报 AgentError 继续

__init__ 主体做五件事:校验 prompt_templates 完整性(缺 key 断言失败)→ 初始化 self.memory = AgentMemory(self.system_prompt)_setup_tools(工具转 {name: tool} 字典并自动补 final_answer 工具)→ _validate_tools_and_managed_agents(工具/子 agent 名字不得重复)→ _setup_step_callbacks(注册每步回调与指标监控)。注意 self.state: dict[str, Any] = {}——这是贯穿 agent 的键值状态仓,run(additional_args=...) 传入的变量都存这里。

二、run():任务入口的总调度

agents.py:436-538(核心脉络,节选):

436 def run(self, task, stream=False, reset=True, images=None, additional_args=None, max_steps=None, return_full_result=None): 468 max_steps = max_steps or self.max_steps 469 self.task = task 470 self.interrupt_switch = False 471 if additional_args: 472 self.state.update(additional_args) 473 self.task += f""" 474 You have been provided with these additional arguments, ...: 475 {str(additional_args)}.""" 477 478 if reset: 479 self.memory.reset() 480 self.monitor.reset() 488 self.memory.steps.append(TaskStep(task=self.task, task_images=images)) 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}) 494 if stream: 496 return self._run_stream(task=self.task, max_steps=max_steps, images=images) 498 steps = list(self._run_stream(task=self.task, max_steps=max_steps, images=images)) 502 assert isinstance(steps[-1], FinalAnswerStep) 503 output = steps[-1].output 538 return output

按顺序读:

  1. 注入附加变量(471-475):additional_argsself.state,并把变量说明文本追加到任务末尾——LLM 在任务里"看得见"这些变量名,而 CodeAgent 的解释器命名空间里也真有它们(491 行 send_variables,第 3 章呼应)。
  2. 重置记忆(478-480):reset=True 时清空历史步与监控指标,多轮对话时传 reset=False 保留记忆。
  3. 写入任务步(488):SystemPromptStepmemory.reset()/初始化时写入,这里追加 TaskStep——记忆前两步永远是"系统提示 + 任务"。
  4. 非流式路径(498-503):把 _run_stream 生成器消费成列表,断言最后一个元素是 FinalAnswerStep,返回其 outputstream=True 时则直接把生成器交给调用方逐步 yield。

三、_run_stream():while 循环与退出条件

agents.py:540-611(核心循环,节选):

540 def _run_stream(self, task, max_steps, images=None): 543 self.step_number = 1 544 returned_final_answer = False 545 while not returned_final_answer and self.step_number <= max_steps: 546 if self.interrupt_switch: 547 raise AgentError("Agent interrupted.", self.logger) # (可选)规划步:每 planning_interval 步插入 570 action_step = ActionStep(step_number=self.step_number, 571 timing=Timing(start_time=time.time()), observations_images=images) 577 try: 578 for output in self._step_stream(action_step): 580 yield output 582 if isinstance(output, ActionOutput) and output.is_final_answer: 583 final_answer = output.output 591 returned_final_answer = True 592 action_step.is_final_answer = True 594 except AgentGenerationError as e: 596 raise e # 实现层错误:直接上抛终止 597 except AgentError as e: 599 action_step.error = e # 模型层错误:记入记忆,下一步重试 600 finally: 601 self._finalize_step(action_step) 602 self.memory.steps.append(action_step) 603 yield action_step 604 self.step_number += 1 606 if not returned_final_answer and self.step_number == max_steps + 1: 607 final_answer = self._handle_max_steps_reached(task)

三个设计点值得咀嚼:

  • 双退出条件(545):returned_final_answer(LLM 主动调了 final_answer 工具)或步数超限。超限时走 _handle_max_steps_reached:再让 LLM 基于全部记忆"强行作答"一次,并给最后一步打上 AgentMaxStepsError 标记——即使失败也尽量给你一个答案。
  • finally 保证记忆完整(600-604):无论本步成功、解析失败还是执行报错,ActionStep 都会带着 error 字段写回记忆并 yield。下一轮 LLM 能"看到"自己上一步错在哪,自我纠错。
  • 错误分两类(594-599):AgentGenerationError 是代码实现问题(如消息格式错),重试无意义,上抛;其余 AgentError(解析错/工具错)是模型输出问题,记录后进入下一轮重试。这就是第 1 章等级表 ★★☆"LLM 控制循环"的鲁棒性来源。

step() 方法(782-787)是历史兼容壳:return list(self._step_stream(memory_step))[-1]。子类真正要实现的是抽象钩子 _step_stream(单步)与 initialize_system_prompt(系统提示)。

四、write_memory_to_messages():记忆 → LLM 消息

agents.py:758-770:

758 def write_memory_to_messages(self, summary_mode: bool = False) -> list[ChatMessage]: 767 messages = self.memory.system_prompt.to_messages(summary_mode=summary_mode) 768 for memory_step in self.memory.steps: 769 messages.extend(memory_step.to_messages(summary_mode=summary_mode)) 770 return messages

每轮 _step_stream 的第一件事就是调它:把 SystemPromptStep + 各个 TaskStep/ActionStep/PlanningStep 各自的 to_messages() 串成一条消息列表交给 LLM。ActionStep 是 dataclass(memory.py:51),字段即单步全记录:model_input_messages/model_output/tool_calls/code_action/observations/error/token_usage/timing 等。summary_mode=True 时压缩输出(规划步更新与 run 摘要用,第 4 章)。

五、错误体系与重试器(utils.py)

92 class AgentError(Exception): # 基类:构造时自动 logger.log_error(message) 104 class AgentParsingError(AgentError): ... # LLM 输出解析失败(JSON/代码块不合法) 110 class AgentExecutionError(AgentError): ... # 动作执行失败(代码运行异常/工具异常) 116 class AgentMaxStepsError(AgentError): ... # 步数耗尽 122 class AgentToolCallError(AgentExecutionError): ... # 工具参数不合法 128 class AgentToolExecutionError(AgentExecutionError): ... # 工具执行异常 134 class AgentGenerationError(AgentError): ... # 模型生成层实现错误(上抛不重试)

一棵两层的异常树:解析错/执行错/步数错并列,执行错再细分工具调用错与工具执行错。AgentError.__init__ 里那句 logger.log_error(message) 让"抛异常"与"红字日志"原子化——你在终端看到的错误提示就是它打的。

模型层的瞬态故障(限流/超时)由 utils.py 的两个类兜底:RateLimiter(按"每分钟请求数"在调用间强制 sleep)与 Retrying(受 tenacity 启发的重试控制器,支持指数退避 + 抖动 + retry_predicate 谓词),它们在 models.py 的各个后端里包裹 generate 调用,与本节的 agent 级重试(错误写入记忆下一步再试)形成两层防线。

💡 阶梯要点:MultiStepAgent 把"多步 agent"抽象成三个正交件——生命周期(run/_run_stream)、记忆格式(AgentMemory 各 Step)、动作语义(留给子类的 _step_stream)。第 2 章下节把 _step_stream 填成 JSON 工具调用,第 3 章把它换成 Python 代码:框架不变,只换"动作的语言"。

本节要点回顾

  1. MultiStepAgent(ABC) 是两类 agent 的共同地基,必填仅 tools + model;max_steps 默认 20。
  2. run() 流程:附加变量入 state → 重置记忆 → 写 TaskStep → 非流式时消费 _run_streamFinalAnswerStep.output
  3. _run_stream 双退出(final_answer / 超步数),finally 保证 ActionStep 必然入记忆,错误可在下一步被 LLM 看见并自纠。
  4. write_memory_to_messages 把各 Step 的 to_messages() 串成 LLM 输入;ActionStep dataclass 是单步全记录。
  5. 错误树:AgentError → Parsing/Execution/MaxSteps/Generation;模型层另有 RateLimiter/Retrying 两层兜底。
  6. AgentGenerationError 上抛终止,其余 AgentError 记录后继续——重试策略按"错在谁"分流。

下一节,给 _step_stream 填上第一套动作语义:ToolCallingAgent 的 JSON 工具调用与完整 ReAct 循环,以及它的提示词模板 toolcalling_agent.yaml。


作者与出处
原作者: 灏天文库
整理: 灏天文库整理
本站整理收录,版权归原作者/开源协议所有;欢迎通过原文链接访问源仓库。
发布者: 作者: 灏天文库 转发
评论区 (0)
U