研究日期:2026-07-23(Asia/Shanghai)
仓库:huggingface/smolagents
源码快照:e3a5b8994b301983b91c0325546e9dc82eab8cf0
main版本:1.27.0.dev0
最新稳定版:1.26.0
Python:>=3.10
许可证:Apache-2.0
版本与实时校准
本文研究的是 2026-07-23 从 main 快进后固定的源码:
e3a5b8994b301983b91c0325546e9dc82eab8cf0
2026-07-11T12:44:42+02:00
Update security policy (#2495)
当日 GitHub API 快照:
- 28,504 stars;
- 2,804 forks;
- 719 个 open issues + PRs;
- Apache-2.0;
- 默认分支
main。
GitHub Release 与 PyPI 的最新稳定版都是 1.26.0,发布 / 上传于
2026-05-29;当前 main 的 pyproject.toml 已进入 1.27.0.dev0。因此本文
讲解的是固定源码快照,而不是承诺所有细节都已进入 PyPI 稳定包。
README 仍把 agent 逻辑描述为“约 1,000 行”,但快照中的
src/smolagents/agents.py 已有 1,813 行。这个变化不影响项目追求小核心的
方向,却提醒我们:产品边界、executor 类型、streaming 与 serialization 已经
比早期口号丰富,不能再只靠 README 的极简叙述理解现在的 runtime。
结论先行
smolagents CodeAgent 的关键抽象不是“LLM 会写 Python”,而是把 Python 作为
一轮 action policy 的中间语言:
MODEL-VISIBLE LANE
TaskStep / PlanningStep / ActionStep / error
→ to_messages()
→ model
→ one Python program
→ synthetic ToolCall("python_interpreter", code)
EXECUTOR LANE
persistent Python namespace + tools + imports
→ execute program
→ logs + last value / final_answer
→ one observation returns to transcript
这形成两套相关但不等价的状态:
- typed transcript:模型可见、可转成 messages、可记录错误和输出;
- executor heap:变量、对象、imports 与 tool references 在 Python namespace 中继续存活。
run(reset=True) 只清空前者的 memory.steps 与 monitor,不重建
python_executor,也不清空 executor namespace。错误和 timeout 会进入
transcript,却不会回滚 heap 或外部副作用。save() 也只保存 agent 配置、
模型、tools 与 prompts,不保存这两套 runtime state。
所以 Python 不只是一种动作格式,也是 agent 的第二块、目前不可 checkpoint 的记忆。
对旧稿最重要的修正
-
Python 不只是比 JSON 更能写循环。
变量和对象会跨 step 存活,甚至在reset=True的新 run 里继续存在;这会 直接改变 reset、resume、replay 和测试隔离的语义。 -
ActionStep并不逐个记录 Python 内部的 tool call。
CodeAgent._step_stream()只记录一个合成调用:ToolCall(name="python_interpreter", arguments=code_action)。代码内部可能 调用search()、fetch()、save()多次,但 transcript 的顶层事件仍只有 一次 interpreter call。ToolCallingAgent才会逐次记录工具调用与结果。 -
错误反馈不是事务回滚。
evaluator 在代码出错前对 namespace 或外部系统造成的修改依然存在。下一轮 模型看见错误,不代表系统已恢复到 action 前状态。 -
本地 timeout 不是可抢占的 deadline。
当前 decorator 用ThreadPoolExecutor和future.result(timeout=...); 抛出 timeout 后,context manager 退出仍会等待 worker。worker 也可能继续 修改共享 state。开放 issue #2197 与 #2263 正在讨论这个问题。 -
restricted evaluator 不是 security sandbox。
import、dunder、operation、loop 与 print 限制能减少意外,却不能隔离不可信 代码。源码和官方安全教程都要求高风险执行使用 remote executor。 -
remote isolation 不等于 durable replay。
E2B、Modal、Blaxel、Docker 提供更强的进程 / 容器边界,但 kernel 仍是 stateful 的;框架没有把每步 heap 做成确定性 checkpoint。 -
序列化边界本身是设计。
remote executor 默认通过SafeSerializer只接受 JSON-safe 数据;allow_pickle=False是安全默认。启用 pickle 能传更多对象,也会引入任意 代码执行风险。 -
最终答案检查发生在 action 之后。
final_answer_checks验证的是 executor 已运行之后的答案;callbacks 也在_finalize_step()中 action 后执行。它们不是 CodeAgent 的 pre-tool approval hook。 -
持久化缺口是项目当前已知问题。
issue #1216 仍在请求保存 / 恢复 agent memory;#2176、#2172 与 #1883 分别 讨论 tool governance、audit trail 与 lifecycle hooks。它们说明治理面还在 生长,不能把 callback 等同于完整审计或人工批准系统。
一次真实请求怎样运行
场景:
查询五个城市的天气,把摄氏温度归一化,找出异常值,再给出最终结果。
0. 构造阶段
CodeAgent(...):
- 继承
MultiStepAgent,持有 model、tools、managed agents、memory、monitor; - 根据
executor_type创建 local / E2B / Modal / Blaxel / Docker executor; - local executor 得到 authorized imports、operation / loop / print / timeout 限制;
- remote executor 得到额外 imports、logger 与 serialization 设置;
- 把 tool 描述、authorized imports 和 code tags 填进 system prompt。
1. run() 只重置对话 lane
MultiStepAgent.run(task, reset=True):
- 更新
self.task; - 把
additional_args写入self.state; - 重新生成 system prompt step;
- 若
reset=True,调用memory.reset()和monitor.reset(); - 追加新的
TaskStep; - 把
self.state和 tools 发送到已存在的python_executor; - 进入
_run_stream()。
源码没有在这里调用 python_executor.cleanup()、重建 executor,或清空其
state。
2. typed memory 被编译成 messages
_run_stream() 可按 planning_interval 插入 PlanningStep,随后为每轮创建
ActionStep。CodeAgent._step_stream() 调用
write_memory_to_messages(),把 system、task、planning、model output、
tool call、observation 与 error 转成 provider-neutral messages。
ActionStep 还能保存:
- model input / output;
- parsed code;
- synthetic tool calls;
- observations / images;
- error;
- action output;
- token usage 与 timing。
它是可观测的 event record,但不是 executor heap snapshot。
3. 模型返回一段小程序
模型可能返回:
cities = ["北京", "上海", "成都", "深圳", "哈尔滨"]
rows = [get_weather(city) for city in cities]
avg = sum(row["temp_c"] for row in rows) / len(rows)
outliers = [row for row in rows if abs(row["temp_c"] - avg) > 8]
final_answer({"average": avg, "outliers": outliers})
parse_code_blobs() 解析 code,fix_final_answer_code() 做兼容修正。框架随后
只追加一个:
ToolCall(
name="python_interpreter",
arguments=code_action,
)
get_weather() 被调用五次的事实只能从 code、executor logs 或下游 tool 自己
的 telemetry 复原,不会自动变成五个标准化 ToolCall events。
4. executor 执行并保留 heap
LocalPythonExecutor.__call__() 把代码交给 AST evaluator,后者在
self.state namespace 中解释执行。执行后返回:
CodeOutput(
output = 最后一个表达式或 final answer,
logs = captured print output,
is_final_answer = bool
)
如果没有 final answer,logs 与 last output 被写进 ActionStep.observations,
下一轮模型据此继续;variables 仍在 executor state。若调用
final_answer(),local executor 通过专用 final-answer mechanism 结束;remote
executor 则把特殊异常序列化回 host,再恢复值。
5. error 成为下一轮上下文
parse、import、execution 等 agent errors 会落到 ActionStep.error 并进入
后续 messages。这个反馈机制适合自我修正,但它只追加事实,不撤销已经发生的
写入、网络调用或 tool 副作用。
6. action 后才做 checks 与 callbacks
executor 返回 final answer 后,_run_stream() 才运行
final_answer_checks。finally 中 _finalize_step() 再计算 timing、监控并
调用 step callbacks,随后把 step 追加进 memory。它们适合观测和 post-check,
不是“执行每个 tool 前必须批准”的边界。
三个可复现实验
实验都在固定源码快照、临时 uv 环境中运行,没有网络工具,也没有外部副作用。
实验 A:reset=True 后 executor 仍记得旧变量
用一个确定性 scripted model 分两次返回:
# run 1
x = 41
final_answer(x)
# run 2, reset=True
final_answer(x + 1)
结果:
run_1 = 41
executor_has_x = 41
run_2 = 42
memory_after_run_2 = [TaskStep, ActionStep]
memory_contains_first_code = false
executor_x_after_run_2 = 41
第二次 transcript 中已经没有 x = 41,模型却能通过 executor heap 读到它。
这不是“conversation continuation”,而是两套 reset 语义不一致。
实验 B:异常不会回滚 state
执行:
ledger = []
ledger.append("charged")
1 / 0
结果:
exception = InterpreterError
ledger_after_error = ["charged"]
这说明即使错误被记录,action 前半段造成的 state mutation 仍然生效。若
ledger.append() 换成支付、发邮件或数据库写入,恢复责任必须由 tool / 业务层
负责。
实验 C:timeout 抛出后仍等待并可晚写 state
构造 timeout_seconds=0.2、授权 time 的 local executor,执行:
import time
time.sleep(1.2)
y = 7
结果:
exception = ExecutionTimeoutError
wall_time = 1.21s
state_y = 7
名义 timeout 是 0.2 秒,调用却约 1.21 秒后返回,worker 还写入了 y。官方
test_local_executor_custom_timeout 同样通过,但 sleep(2) /
timeout_seconds=1 的测试用时约 2.01 秒;该测试只验证异常类型,没有验证
deadline 响应时间。
验证记录
Agent / memory 定向测试
uv run --with pytest python -m pytest -v \
tests/test_memory.py tests/test_agents.py \
-k 'fake_code_agent or reset_conversations or fails_max_steps or
error_saves_previous_print_outputs or full_result'
8 passed, 101 deselected
Local / remote executor 定向测试
第一次收集因临时环境缺少 numpy 与 docker 依赖失败;补齐测试声明依赖后:
uv run --with pytest --with numpy --with pandas --with docker \
--with websocket-client python -m pytest -v \
tests/test_memory.py tests/test_local_python_executor.py \
tests/test_remote_executors.py \
-k 'not Integration and (memory or state_name or
local_executor_custom_timeout or send_variables or
deserialize_final_answer or serialization)'
18 passed, 434 deselected
1 FutureWarning(insecure pickle fallback)
合计 26 个聚焦测试通过。此结果不等于全仓测试通过,也没有启动真实的 E2B / Modal / Blaxel / Docker 服务。
关键源码入口
src/smolagents/agents.py::MultiStepAgentsrc/smolagents/agents.py::MultiStepAgent.runsrc/smolagents/agents.py::MultiStepAgent._run_streamsrc/smolagents/agents.py::MultiStepAgent._finalize_stepsrc/smolagents/agents.py::MultiStepAgent.write_memory_to_messagessrc/smolagents/agents.py::ToolCallingAgent._step_streamsrc/smolagents/agents.py::CodeAgentsrc/smolagents/agents.py::CodeAgent.create_python_executorsrc/smolagents/agents.py::CodeAgent._step_streamsrc/smolagents/memory.py::ActionStepsrc/smolagents/memory.py::AgentMemorysrc/smolagents/local_python_executor.py::evaluate_python_codesrc/smolagents/local_python_executor.py::timeoutsrc/smolagents/local_python_executor.py::LocalPythonExecutorsrc/smolagents/remote_executors.py::RemotePythonExecutorsrc/smolagents/remote_executors.py::RemotePythonExecutor.send_variablessrc/smolagents/remote_executors.py::_patch_final_answer_with_exceptionsrc/smolagents/serialization.py::SafeSerializersrc/smolagents/models.py::Modelsrc/smolagents/tools.py::Tool
官方资料与外部比较
- 固定源码快照
- 固定快照 README
- 1.26.0 Release
- PyPI
smolagents - 官方文档首页
- Agent API
- Python executor API
- 官方 Secure code execution 教程
- CodeAct 论文
- DynaSaur 论文
- Code as Agent Harness 论文
- Issue #2197:local timeout 等待 worker 完成
- PR #2263:避免 timeout 后继续等待
- Issue #1216:保存 / 加载 agent memory
- Issue #2176:tool execution governance
- Issue #2172:audit trail / governance callback
- Issue #1883:CodeAgent lifecycle hooks
- PR #2442:callback 持久化 memory 示例
- LangGraph persistence
- LangGraph interrupts
写作边界
- CodeAct 论文报告的“最高 20% success rate 提升”属于论文任务集结果,不应 写成 smolagents 在所有生产任务上的普遍收益。
- 本文没有宣称 remote executor 完全安全;隔离能力仍取决于 provider、镜像、 network / filesystem policy、secrets 与 cleanup。
- 本文没有运行真实外部工具,不声称验证了远端 executor 的完整生命周期。
- GitHub stars、issue 状态、
main版本和代码路径都可能继续变化;引用固定 commit 是为了让结论可复查。