从简单处理器到代码 Agent:smolagents 中"Agent 能力"谱系与 ReAct 实现原理全解析
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
本篇技术指南围绕 smolagents 官方《Agent 简介》概念文档展开,系统讲解"什么是 Agent"、Agent 能力从弱到强的连续谱系、多步 Agent 的循环(ReAct)工作结构、何时该用/不该用 Agent 的工程判断,以及 smolagents 为何选择"让 LLM 用代码写动作"的代码 Agent(Code Agent)路线。读完本文,你将掌握 Agent 能力的量化评估框架、smolagents 的核心抽象组成(LLM 引擎、工具列表、系统提示、动作解析器、记忆),并能对照 agents.py 与 code_agent.yaml 的源码实现,亲手搭建一个可运行的最小 Agent。
什么是 Agent:LLM 输出控制工作流的程序
任何高效使用 AI 的系统,都需要为 LLM 提供某种访问现实世界的途径:例如调用搜索工具获取外部信息,或操作某些程序以完成任务。换句话说,LLM 应当具备Agent 能力(agency)。Agent 程序就是 LLM 通往外部世界的门户。
[!TIP] AI Agent 是LLM 输出控制工作流的程序。
任何利用 LLM 的系统,都会把 LLM 的输出集成进代码。LLM 输出对代码工作流的影响程度,就是该系统赋予 LLM 的 Agent 能力级别。
需要特别强调的是:按照这一定义,"Agent" 并不是一个非 0 即 1 的离散概念。相反,Agent 能力是一个连续谱系——取决于你在工作流中赋予 LLM 多少权力,系统可以处在谱系上的任意位置。
Agent 能力级别一览
下表展示了 Agent 能力在不同系统中的典型变化:
| Agent 能力级别 | 描述 | 名称 | 示例模式 |
|---|---|---|---|
| ☆☆☆ | LLM 输出对程序流程没有影响 | 简单处理器 | process_llm_output(llm_response) |
| ★☆☆ | LLM 输出决定 if/else 分支 | 路由 | if llm_decision(): path_a() else: path_b() |
| ★★☆ | LLM 输出决定函数执行 | 工具调用者 | run_function(llm_chosen_tool, llm_chosen_args) |
| ★★★ | LLM 输出控制迭代和程序继续 | 多步 Agent | while llm_should_continue(): execute_next_step() |
| ★★★ | 一个 agent 工作流可以启动另一个 agent 工作流 | 多 Agent | if llm_trigger(): execute_agent() |
| ★★★ | LLM 直接用代码行动,可自定义工具、启动其他 agent | 代码 Agent | def custom_tool(args): ... |
从表中可以清晰看出演进脉络:从"输出完全不影响流程"的简单处理器,到"决定分支"的路由器,再到"决定调用哪个函数"的工具调用者,最终进化到"控制循环迭代"的多步 Agent、可嵌套启动其他工作流的多 Agent,以及直接在代码层面行动的代码 Agent。Agent 能力越强,LLM 对程序流程的控制权越大,系统能处理的任务也越复杂。
多步 Agent 的核心循环结构
多步 Agent 具有以下代码结构:
memory = [user_defined_task] while llm_should_continue(memory): # 这个循环是多步部分 action = llm_get_next_action(memory) # 这是工具调用部分 observations = execute_action(action) memory += [action, observations]这个系统在一个循环中运行:每一步执行一个新动作(该动作可能涉及调用一些预定义的工具,这些工具本质上只是函数),直到观察结果表明已达到解决给定任务的满意状态。
值得注意的是,这个伪代码并非凭空设计——它就是 smolagents 中MultiStepAgent的真实执行骨架。在 agents.py 中,MultiStepAgent的类注释明确指出它基于ReAct 框架:目标未达成时,Agent 会持续执行"思考(Reflection/Thought)→ 行动(Action)→ 观察(Observation)"的循环。其_run_stream方法(agents.py)正是:
while not returned_final_answer and self.step_number <= max_steps: ...循环上限由max_steps控制(默认 20,见 agents.py 中MultiStepAgent.__init__的参数默认值)。每一步中,_step_stream依次完成"生成模型输出 → 解析动作 → 执行工具/代码 → 把观察结果写回记忆"的全流程,与文档中的伪代码一一对应。
何时使用 Agent,何时避免
当你需要 LLM 来决定应用程序的工作流时,Agent 很有用。但它们通常有些过度设计。关键问题是:我真的需要工作流的灵活性来有效解决手头的任务吗?
如果预定义的工作流经常不足,那才意味着你需要更多灵活性。
确定性工作流优先:一个冲浪旅行网站的例子
假设你在开发一个处理冲浪旅行网站客户请求的应用程序。你可以提前知道请求将属于两个类别之一(基于用户选择),并且为这两种情况都准备好了预定义工作流:
- 想要了解旅行信息?⇒ 给他们访问搜索栏以搜索你的知识库
- 想与销售交谈?⇒ 让他们填写联系表单
如果这个确定性工作流能覆盖所有查询,那就直接编码实现吧!这将为你提供一个 100% 可靠的系统,不会因为让不可预测的 LLM 干预工作流而引入错误风险。为了简单和稳健起见,工程上建议默认不启用任何 Agent 行为。
工作流不可预知时:Agent 登场
但如果工作流不能提前很好地确定呢?例如用户问:
"I can come on Monday, but I forgot my passport so risk being delayed to Wednesday, is it possible to take me and my stuff to surf on Tuesday morning, with a cancellation insurance?"
这个问题涉及许多因素(到达时间、天气、装备运输、取消保险),上述预定义的标准很可能都不足以满足这个请求。这正是 Agent 设置发挥作用的地方:你可以创建一个多步 Agent,让它访问天气 API 获取天气预报、Google Maps API 计算旅行距离、员工在线仪表盘以及构建在知识库上的 RAG 系统,自主编排这些工具来完成复杂的请求。
从工程史的角度看,直到最近,计算机程序仍局限于预定义工作流,试图通过堆积 if/else 分支来处理复杂性,只能处理"计算这些数字的总和"或"找到这个图中的最短路径"这类极其狭窄的任务。但实际上,大多数现实生活中的任务都不适合预定义工作流。Agent 系统为程序打开了现实世界任务的大门。
为什么选择 smolagents
对于链(chain)或路由器(router)这类低阶 Agent 用例,你可以自己编写全部代码,这样反而更好——因为你能更好地控制和理解自己的系统。
但一旦你开始追求更复杂的行为,比如让 LLM 调用函数(即"工具调用")或让 LLM 运行 while 循环("多步 Agent"),一些抽象就变得必要:
- 工具调用需要解析:你需要解析 Agent 的输出,因此输出需要预定义格式,如
"Thought: I should call tool 'get_weather'. Action: get_weather(Paris).",并用预定义函数解析它;同时给 LLM 的系统提示应当告知它这个格式。 - 多步循环需要记忆:当 LLM 输出决定循环时,你需要根据上一次循环迭代中发生的情况给 LLM 不同的提示——所以你需要某种记忆能力。
从这两个例子出发,一个功能完整的 Agent 系统至少需要以下要素:
- 一个作为系统引擎的LLM
- Agent 可以访问的工具列表
- 从 LLM 输出中提取工具调用的解析器
- 与解析器同步的系统提示
- 记忆能力
此外,既然我们给了 LLM 决策空间,它们必然会犯错:所以还需要错误日志记录和重试机制。所有这些元素都需要紧密耦合才能形成一个功能良好的系统——这正是 smolagents 提供基础构建块让它们协同工作的原因。
源码印证:五大要素在仓库中的落点
这些抽象并非停留在文档概念层面,在仓库中都有具体实现:
- LLM 引擎:
Model抽象基类及其众多子类(InferenceClientModel、TransformersModel、LiteLLMModel、AzureOpenAIModel、MLXModel等)定义在 models.py,负责generate与generate_stream。 - 工具列表:
Tool/BaseTool类与@tool装饰器定义在 tools.py,默认工具箱(网页搜索、Python 解释器、语音转录等)在 default_tools.py。 - 解析器:
MultiStepAgent.extract_action(agents.py)按分隔 token(如<code>/</code>)切分 LLM 输出得到 rationale 与 action,缺少分隔符时抛出AgentParsingError;代码块提取工具parse_code_blobs、extract_code_from_text位于 utils.py。 - 系统提示:由 Jinja 模板渲染,
populate_template(agents.py)将工具列表、授权导入、代码块标签等变量注入 code_agent.yaml,错误类型AgentError家族(解析/生成/执行/工具调用错误)也定义在 utils.py。 - 记忆:
AgentMemory、ActionStep、SystemPromptStep、TaskStep、FinalAnswerStep等内存步骤类全部位于 memory.py,write_memory_to_messages(agents.py)负责把记忆转换为发送给 LLM 的聊天消息列表。
代码 Agent:为什么让 LLM 用代码写动作
在多步 Agent 中,每一步 LLM 都可以编写一个动作,形式为调用外部工具。常见的动作书写格式(Anthropic、OpenAI 等广泛使用)通常是"将动作写成工具名称和参数的 JSON,然后解析以确定执行哪个工具、使用哪些参数"的不同变体。
多项研究论文(如《Executable Code Actions Elicit Better LLM Agents》等)表明,让 LLM 以代码片段形式书写动作更加自然、灵活,效果更好。
原因很简单:我们专门设计了编程语言,使其成为表达计算机执行动作的最佳方式。如果 JSON 片段是更好的表达方式,JSON 早就成为顶级编程语言了。换句话说,Agent 将编写程序来解决用户的问题——用 Python 代码块表达程序,显然比用 JSON 容易得多。
与 JSON 动作相比,代码动作的四大优势
- 可组合性(Composability):你能像定义 Python 函数一样,把 JSON 动作嵌套在一起,或定义一组 JSON 动作供以后复用吗?代码天然支持函数定义与复用。
- 对象管理(Object management):你如何在 JSON 中存储像
generate_image这样的动作的输出?代码中的变量可以自然地持有任意复杂对象(图像、音频、数据帧等)。 - 通用性(Generality):代码被构建为可以简单地表达任何你能让计算机做的事情,从循环、条件、数据处理到任意第三方库调用。
- LLM 训练数据中的表示(Representation in training data):大量高质量代码动作已经包含在 LLM 的训练数据中,意味着 LLM 已经为代码形式的动作完成了预训练。
代码 Agent 在 smolagents 中的落地形态
smolagents 提供两种 Agent 形态(详见 guided_tour):
CodeAgent(默认推荐,agents.py):动作由 LLM 以代码格式书写,然后被解析并执行。其类注释原话是 "In this agent, the tool calls will be formulated by the LLM in code format, then parsed and executed."ToolCallingAgent(agents.py):使用 JSON 风格的工具调用,借助model.get_tool_call利用 LLM 引擎自带的工具调用能力。它与CodeAgent工作方式相似,但由于不执行代码,也没有additional_authorized_imports参数。
CodeAgent的系统提示加载自 code_agent.yaml,其中定义了完整的Thought → Code → Observation循环规范:每一步必须先写Thought:推理,再用代码块标签(默认<code>与</code>,可通过code_block_tags参数改为 markdown 风格)包裹 Python 代码,并用print()输出重要信息供下一步的Observation使用,最终通过final_answer工具返回答案。该提示还内置了 11 条硬性规则,例如:只使用自己定义过的变量;工具参数必须直接传参而不能用 dict(wikipedia_search(query="...")而非wikipedia_search({'query': "..."}));只能导入授权列表内的模块;代码执行之间的状态会持久保留;不要放弃任务等。
在 smolagents 中落地:从概念到可运行
快速上手:构建你的第一个代码 Agent
首先安装(默认工具包):
pip install "smolagents[toolkit]"然后初始化一个最小 Agent。你至少需要两个参数:model(驱动 Agent 的文本生成模型)和tools(Agent 可用的工具列表,可以为空列表,也可通过add_base_tools=True叠加默认工具箱)。
以 Hugging Face Inference API 为例(guided_tour 提供了TransformersModel、LiteLLMModel、AzureOpenAIModel、MLXModel、Ollama 等多种接入方式):
from smolagents import CodeAgent, InferenceClientModel model = InferenceClientModel(model_id="meta-llama/Llama-3.3-70B-Instruct", token="<YOUR_HUGGINGFACEHUB_API_TOKEN>") agent = CodeAgent(tools=[], model=model, add_base_tools=True) agent.run("Could you give me the 118th number in the Fibonacci sequence?")CodeAgent的核心构造参数(agents.py)包括:
| 参数 | 默认值 | 说明 |
|---|---|---|
tools | 必填 | Agent 可用的Tool列表 |
model | 必填 | 生成动作的Model |
additional_authorized_imports | None | 额外授权导入的模块名列表,如['requests', 'bs4'] |
planning_interval | None | 每 N 步运行一次规划(planning)步骤的间隔 |
executor_type | "local" | 代码执行器类型:"local"/"blaxel"/"e2b"/"modal"/"docker" |
max_print_outputs_length | None | print输出的最大长度 |
stream_outputs | False | 是否流式输出(要求模型实现generate_stream) |
use_structured_outputs_internally | False | 是否在每步使用结构化生成(对许多模型可提升性能,对应 structured_code_agent.yaml) |
code_block_tags | ("<code>", "</code>") | 代码块开闭标签,可传"markdown"使用 ```python 风格 |
系统提示与动作解析的协同机制
CodeAgent.initialize_system_prompt(agents.py)把工具定义、managed_agents、授权导入列表、自定义指令以及代码块开闭标签注入 code_agent.yaml 模板,生成完整的系统提示。执行时,_step_stream(agents.py)以["Observation:", "Calling tools:"]等作为停止序列调用模型,再把模型输出按代码块标签切分、提取、交给执行器运行——系统提示中声明的格式与解析器严格同步,这正是文档强调"解析器与系统提示需要紧密耦合"的工程体现。
代码执行安全与沙箱化
默认情况下,代码在本地环境执行,local_python_executor.py 通过 AST 级别的安全检查(evaluate_ast等)限制可调用函数与可导入模块——唯一可调用的是你提供的工具和一组预定义的安全函数(如print和math模块),默认禁止安全列表之外的导入。你可以通过additional_authorized_imports参数授权额外导入:
from smolagents import CodeAgent model = InferenceClientModel() agent = CodeAgent(tools=[], model=model, additional_authorized_imports=['requests', 'bs4']) agent.run("Could you get me the title of the page at url 'https://huggingface.co/blog'?")[!WARNING] LLM 可以生成任意代码然后执行:不要添加任何不安全的导入!
如果生成的代码尝试非法操作或出现常规 Python 错误,执行将停止。需要更强隔离时,可以通过executor_type="e2b"(需先设置E2B_API_KEY环境变量)或executor_type="docker"切换到远程沙箱执行,对应实现位于 remote_executors.py 的E2BExecutor、DockerExecutor、BlaxelExecutor、ModalExecutor(注意:从源码看,托管子 Agent 目前尚不支持远程代码执行)。若希望追踪每一步的完整日志,可以查看agent.logs,或用agent.write_memory_to_messages()将运行记忆转成聊天消息列表。
小结
本文从"Agent 是 LLM 输出控制工作流的程序"这一核心定义出发,梳理了 Agent 能力的五级连续谱系,论证了"确定性工作流优先、Agent 用于工作流不可预知场景"的工程原则,并解释了 smolagents 选择代码 Agent 路线的四大理由(可组合性、对象管理、通用性、训练数据表示)。对照 agents.py、code_agent.yaml、memory.py 与 local_python_executor.py 的源码,可以看到文档所述的 LLM 引擎、工具列表、解析器、系统提示、记忆与错误重试六大要素在仓库中均有对应的真实实现,ReAct 循环也以while not returned_final_answer and self.step_number <= max_steps的形式真实运行在每一轮 Agent 任务中。理解这层概念与实现的对应关系,是配置好、调试好、扩展好你自己的 Agent 系统的起点。
【免费下载链接】smolagents🤗 smolagents: a barebones library for agents that think in code.项目地址: https://gitcode.com/gh_mirrors/smo/smolagents
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考