一般 tool-calling agent 每次只能产出“调用哪个工具、参数是什么”。smolagents 的 CodeAgent 做了一个看似大胆、其实很工程化的替换:让模型把一轮动作写成 Python。
关键不在于 agent 会写代码,而在于 Python 成了动作的中间语言。
为什么代码比一组 JSON 调用更有表达力
假设任务是搜索五个城市、过滤温度、计算平均值,再只对异常项查询详情。JSON tool call 往往要经历多轮模型往返;Python 可以在一段动作里写循环、条件和局部变量,直接组合多个 tool function。
CodeAgent 沿用 MultiStepAgent 的 ReAct loop:task 与历史 step 先变成 messages,模型输出 code block,parser 抽出 Python,executor 执行后把 logs 和最后一个值写回 ActionStep。如果代码调用 final_answer(),executor 通过特殊终止信号结束循环;否则 observation 回到 memory,进入下一轮。
这种协议很薄。状态中的 DataFrame、image 或中间对象可以跨 step 保留,不必每次序列化成文字;错误也以执行 observation 回给模型,让它在同一语言里修正。相比“一个工具调用就是一步”,代码把多个原子动作编成了一个小程序。
真正的分界线是 executor
smolagents 提供 LocalPythonExecutor,会限制 import、危险 built-in、dunder 访问、循环次数和执行时间。但源码注释非常坦率:它不是 security sandbox。受限解释器能减少误操作,不能承担不可信代码的隔离责任。
需要真正边界时,应换成 E2B、Modal、Blaxel 或 Docker executor。CodeAgent 只依赖统一的 PythonExecutor 接口:发送 tools、发送 variables、执行 code,返回 output、logs 和是否完成。动作语言不变,运行场所可以替换。
这也指出 code-as-action 的最大代价。JSON schema 能精确列出每个工具参数;一段代码则可能循环调用、动态构造参数、消耗更多资源。要获得表达力,就必须加强 sandbox、超时、资源预算和审计。
顺便说一句,README 仍用“约 1000 行”表达极简追求,但当前 agents.py 已经超过这个精确数字。smolagents 值得学的不是行数纪录,而是少数稳定抽象:model、memory step、tool、executor 和 loop。
我的思考
smolagents 会与数据分析师、研究者和喜欢 notebook 式工作流的开发者特别共鸣。他们的任务本来就包含循环、变换和中间对象,代码比一串孤立按钮更接近真实思考方式。
市场上,code agent 会让“自动化”与“软件生成”之间的边界继续消失:过去用户先写程序再运行,未来模型在任务过程中临时生成程序。价值会从单个 tool 转移到安全 executor、可观测运行和可复用状态。
我额外喜欢它带来的诚实:能力与危险同时暴露。代码让 agent 更少受限于预设工作流,也让我们无法再假装 prompt 就是安全边界。真正成熟的 agent 平台,应该把 executor 当成和模型同等重要的一等组件。