本节摘要:全书最后一节,先补齐三块"产品化"能力:一是 CLI——
cli.py(294 行)提供smolagent命令交互式向导(选 agent 类型 Code/ToolCalling → 选工具 → 选模型 → 对话)与webagent视觉网页命令;二是界面——GradioUI 把 agent 步骤流式渲染成聊天消息,stream_to_gradio是核心转换器;三是多模态——agent_types.py的 AgentText/AgentImage/AgentAudio 让工具可以返回图像音频,vision_web_browser.py(247 行)用 helium/Selenium 每步截图喂给视觉模型。随后沿 agency 阶梯把八章串成一条线做全书回顾,提炼 smolagents 三点核心哲学,并给出下一步学习建议。
内容来源:原项目源码
src/smolagents/cli.py、gradio_ui.py、agent_types.py、vision_web_browser.py,pyproject.toml 入口配置;并回顾全书八章脉络。
⚠️ 注意:
webagent默认headless=False——它需要可见的浏览器窗口供截图识别;smolagent交互向导里的 Space 工具(如username/spacename)经Tool.from_space远程加载,运行即消耗 Space 配额。
阅读完本节,你应当能够:
smolagent 命令不写一行代码跑起一个 agent,并说出向导四步各对应 cli.py 哪段。stream_to_gradio 如何把 ActionStep/PlanningStep/delta 翻译成聊天消息。to_string/to_raw 双向转换。pyproject.toml 注册了两个命令行入口:
139 [project.scripts] 140 smolagent = "smolagents.cli:main" 141 webagent = "smolagents.vision_web_browser:main"
cli.py 的 main 分两路:带位置参数 prompt 直接跑(--model-type/--tools/--action-type 等全可命令行指定);不带 prompt 进入交互式向导(cli.py:110-185)。向导四步:
124 action_type = Prompt.ask( 125 "[bold white]What action type would you like to use? 'code' or 'tool_calling'?[/]", 128 choices=["code", "tool_calling"], 129 ) 135 for tool_name, tool_class in TOOL_MAPPING.items(): # rich 表格列出全部内置工具 138 tool_instance = tool_class() 140 description = getattr(tool_instance, "description", "No description available") 150 tools_input = Prompt.ask("[bold white]Select tools for your agent[/]", default="web_search") 155 model_type = Prompt.ask("[bold]Model type[/]", 158 choices=["InferenceClientModel", "OpenAIServerModel", "LiteLLMModel", "TransformersModel"]) 161 model_id = Prompt.ask("[bold white]Model ID[/]", default="Qwen/Qwen2.5-Coder-32B-Instruct") 182 prompt = Prompt.ask("[bold white]Now the final step; what task ...[/]", default=leopard_prompt)
选 agent 类型(对应 --action-type code/tool_calling,即 CodeAgent 还是 ToolCallingAgent)→ 选工具(rich 表格列出 TOOL_MAPPING 全部内置工具,也接受 username/spacename 形式的 Space)→ 选模型(四选一 + model_id)→ 输任务。工具装载在 run_smolagent(cli.py:236-245):名字带 / 走 Tool.from_space(第 5 章的生态集成),否则查 TOOL_MAPPING,都不是就报错。一个小彩蛋:选 OpenAIModel 时默认 api_base 指向 Fireworks(cli.py:196-200)——OpenAI 兼容协议的又一次胜利。
gradio_ui.py(464 行)的枢纽是 stream_to_gradio:输入 agent 的步骤流,输出 gr.ChatMessage 流。关键逻辑(gradio_ui.py:82-163,节选):
82 def stream_to_gradio(agent, task: str, reset_agent: bool = False, **kwargs): ... 96 for step_log in agent.run(task, stream=True, **kwargs): ... 101 if hasattr(step_log, "model_output") and step_log.model_output is not None: 101 yield gr.ChatMessage(role=MessageRole.ASSISTANT, content=model_output, ...) ... 128 yield parent_message_tool 135 yield gr.ChatMessage(role="tool", content=str(step_log.observations), ...) ... 158 yield gr.ChatMessage(role=MessageRole.ASSISTANT, content=f"**Final answer:**\n{step_log.output}...")
agent.run(stream=True) 逐个产出 ActionStep/PlanningStep/FinalAnswerStep/流式 delta(第 4、7 章的机制),这里逐一翻译:思考渲染成 assistant 气泡、工具观察渲染成 tool 气泡、计划单独成块、每步附 footnote(时长+token 数,取自 step_log.timing 与 token_usage)。GradioUI(agent).launch() 一行起 Web 服务,agent 类只需传进去——界面与 agent 完全解耦,这与 agent.save() 自动生成 app.py 的设计一脉相承:任何 agent 都能一键变成可分享的 Web 应用。
工具不该只能返回字符串——生成图像、读音频怎么办?agent_types.py 的答案是多重继承:
62 class AgentText(AgentType, str): 63 """Text type returned by the agent. Behaves as a string.""" 74 class AgentImage(AgentType, PIL.Image.Image): 74 """Image type returned by the agent. Behaves as a PIL.Image.Image.""" 176 class AgentAudio(AgentType, str):
AgentText 混入 str、AgentImage 混入 PIL.Image.Image——返回值直接就是原生对象,.resize()、切片、拼接全部可用;同时实现 to_string()(序列化成文件路径,供写进代码观察或喂给文本模型)与 to_raw()(还原成 PIL 对象/torch 张量)双出口。AgentImage 内部维护三态(_path/_raw/_tensor,agent_types.py:79-109),按需懒转换:传路径不加载、传张量先转 numpy。出口分发在 handle_agent_output_types(agent_types.py:263-281):工具声明 output_type="image" 就强制映射,否则按 isinstance 自动嗅探。
多模态输入则在 models.py 侧:get_clean_message_list(convert_images_to_image_urls=True) 把 PIL 图像 base64 编码成 image_url 块(gradio_ui 与云端模型走这条路);本地 TransformersModel 走 processor 的原生图像输入。第 5 章生成图像的教程、open_deep_research 的 visualizer,底座都在这里。
vision_web_browser.py(247 行)是"看屏幕操作浏览器"的完整样本,三件套:
其一,工具集(vision_web_browser.py:105-136):search_item_ctrl_f(XPath 找文本并滚动聚焦)、go_back、close_popups(ESC 关弹窗)——只补 Selenium/helium 不方便做的三个动作,其余导航交给 helium 本身。
其二,截图回调(vision_web_browser.py:66-84):
66 def save_screenshot(memory_step: ActionStep, agent: CodeAgent) -> None: 67 sleep(1.0) # Let JavaScript animations happen before taking the screenshot 68 driver = helium.get_driver() 71 for previous_memory_step in agent.memory.steps: # 只保留最近两步截图 72 if isinstance(previous_memory_step, ActionStep) and previous_memory_step.step_number <= current_step - 2: 73 previous_memory_step.observations_images = None 74 png_bytes = driver.get_screenshot_as_png() 77 memory_step.observations_images = [image.copy()] 80 url_info = f"Current url: {driver.current_url}" 81 memory_step.observations = ... + url_info
这是 step_callbacks 的教科书用法:每步执行后截屏存进 observations_images(多模态观察!视觉模型下一步就能"看见"页面)、清掉两步前的旧截图省 token、附上当前 URL。其三,提示词:helium_instructions(160-214 行)教模型 go_to('github.com/trending')、click("Top products") 的用法,并反复叮嘱"每步只做一个动作,看截图再说"。装配与启动(vision_web_browser.py:148-157、236-237):
150 return CodeAgent( 151 tools=[WebSearchTool(), go_back, close_popups, search_item_ctrl_f], 152 model=model, 153 additional_authorized_imports=["helium"], 154 step_callbacks=[save_screenshot], 155 max_steps=20, verbosity_level=2, 156 ) 236 agent.python_executor("from helium import *") # 预热解释器命名空间 237 agent.run(prompt + helium_instructions)
additional_authorized_imports=["helium"] + 预执行 from helium import *——模型写的代码里 click()、scroll_down() 就是普通 Python 函数。视觉能力不需要多智能体,一个 CodeAgent 加一个截图回调即可;open_deep_research 的 visual_vs_text_browser.ipynb 正是拿它与文本浏览器做对照实验。
💡 阶梯要点:vision agent 的配方 = 工具(三个补丁函数)+ 回调(每步截图入记忆)+ 提示词(helium 用法说明书)。注意截图是观察不是行动——模型"看"上一步的结果再决定下一步,这正是 ReAct 循环在视觉域的自然延伸。
TokenUsage 与成本意识:vision agent 每步一张截图,多模态 token 单价数倍于文本,20 步下来成本可观——这正是 Monitor 逐步打印 token 数、GradioUI 在 footnote 展示、return_full_result=True 汇总 RunResult.token_usage 的意义:每个层面都能看见成本,才能在"多截一张图"与"省一毛钱"之间做工程取舍。
我们终于走完 smolagents 源码精读的全程。把八章串起来,主线就是那张 agency 等级表:
第 1 章 全貌与 agency 阶梯(☆~★★★ 主线):认识了 smolagents——HF 官方、barebones 极简(18 文件约 1.28 万行、6 个核心依赖、测试约 1:1)、口号 "Agents that think in code"。agency 等级表从"单次程序调用"到"ReAct 循环"到"代码即 action"再到"多智能体编排",这张表是全书攀登路线图。
第 2 章 ToolCallingAgent 与 ReAct(☆☆):MultiStepAgent 基类的 run/step/write_memory_to_messages 生命周期;JSON 工具调用;Thought→Action→Observation 循环;观察(含报错)回流驱动下一步——这是此后一切章节的地基。
第 3 章 CodeAgent 代码即 Action(☆☆☆,全书灵魂):LLM 把 action 写成 Python 代码片段而非 JSON,代码可组合函数、声明变量、写循环,一步顶 JSON 多步,推理步数少约 30%;parse_code_blobs 解析、工具作为 Python 函数注入解释器;code_agent.yaml 四段提示词。全书最重要的一个范式判断。
第 4 章 记忆与规划:AgentMemory 与五种步骤类型;CallbackRegistry 把日志、监控、人为干预全部挂到步骤生命周期上;planning_interval 周期性重审事实与计划;write_memory_to_messages 把记忆渲染回提示词。
第 5 章 工具体系:Tool 基类(name/description/inputs/output_type + forward);@tool 装饰器从类型提示自动生成 JSON schema;PipelineTool 包 transformers;生态四件套 from_hub/from_space/from_mcp/from_langchain 把 HF 与 MCP 生态直接变成工具箱。
第 6 章 AST 解释器与安全沙箱(★):local_python_executor.py 1768 行自研 AST 解释器——不 exec 而是 NodeVisitor 遍历语法树;黑名单与 dunder 禁令、操作数/时长限额;但它不是安全沙箱(官方明示);真正的防线是四种远程执行器(E2B/Docker/Modal/Blaxel);serialization 的 pickle 警告。
第 7 章 多模型后端:Model 基类 generate/generate_stream 统一接口、ChatMessage 统一消息、10 个 Model 类覆盖本地三兄弟与云端六家;流式 delta 聚合、工具 schema 自动生成、TokenUsage、AgentLogger 与 OpenTelemetry 遥测、RateLimiter/Retrying。换模型=换一行构造代码。
第 8 章 多智能体与实战(★★攀顶):managed_agents 把子 agent 伪装成 Tool 注入解释器,"代码即 action"升维成"代码即派活";open_deep_research 用两级多智能体在 GAIA 拿下 55%;CLI/UI/多模态补齐产品化拼图——你正读到的这一节。
八章的因果链清晰可见:第 3 章的范式(代码即 action)要求第 6 章的执行器,执行器定义了工具的存在形式(命名空间里的函数),于是第 5 章的工具和第 8 章的子 agent 才能以同一种方式被调用;第 2、4 章的循环与记忆让长任务可行;第 7 章让这一切跑在任何模型上。没有一章是孤岛。
smolagent 交互向导四步(动作类型→工具表→模型→任务),Space 工具经 from_space 装载;webagent 启动视觉浏览器 agent。stream_to_gradio 把步骤流翻译成 gr.ChatMessage(思考/工具观察/计划/终答),界面与 agent 解耦,save 自动生成 Space 应用。to_string(路径)与 to_raw(对象)双出口,handle_agent_output_types 按声明或类型嗅探分发。from helium import * 预热命名空间,视觉能力一个 CodeAgent 就够。恭喜你读完《smolagents 中文源码精读教程》。从第 1 章的等级表,到第 6 章的 AST 解释器,再到第 8 章的 open_deep_research,你已经把这 1.28 万行核心源码的主要部分逐段拆解过。现在你应当能够:读懂仓库里任何模块(包括未成章的 serialization.py、remote_executors.py 细节);为自己的业务写工具、配沙箱、接任意模型;组装层级式多智能体系统并用遥测调优它。
下一步的四个方向,按投入产出排序:
agent 框架会不断迭代,范式会来回摇摆(JSON 与代码、平铺与层级),但 smolagents 展示的判断力不会过时:在表达能力与工程复杂度之间找到那个最小支点。代码即 action,极简即强大。祝你构建的 agent 步步收敛、工具顺手、账单可控。我们后会有期。