远程执行、安全边界与横向对比
03讲的本地执行器是「减害」不是「隔离」。真要跑不可信代码,得把它送出本进程、进真沙箱。本章讲远程执行器家族怎么做,坦白 smolagents 的安全边界到底在哪,并和兄弟项目横向对比,最后给总代码地图。
1. 远程执行器:同一个接口,四种后端
所有执行器都实现 PythonExecutor 抽象接口(local_python_executor.py:1677):send_tools / send_variables / __call__。CodeAgent 只认这个接口,不关心代码是在本进程解释还是在远端容器跑——create_python_executor(agents.py:1598)按 executor_type 选实现。
远程的共同基类是 RemotePythonExecutor(remote_executors.py:53),四个后端继承它:
| 后端 | 隔离形态 | 类 |
|---|---|---|
| E2B | 云端代码沙箱(微 VM) | E2BExecutor(remote_executors.py:335) |
| Docker | 本地/自管容器 | DockerExecutor(remote_executors.py:551) |
| Modal | Modal 云沙箱 | ModalExecutor(remote_executors.py:726) |
| Blaxel | Blaxel 云沙箱 | BlaxelExecutor(remote_executors.py:859) |
每个子类主要实现 run_code_raise_errors(把代 码送进去跑、取回输出/日志/错误)和 cleanup(销毁沙箱)。
2. 关键机制:隔着进程怎么回传「最终答案」
本地执行器靠 FinalAnswerException 在同进程内穿透(02 §5)。远程隔着进程边界,异常穿不过来。解法在 _patch_final_answer_with_exception(remote_executors.py:143):
怎么读:难点是「沙箱里 final_answer 拿到的值,要能安全地跨进程带回宿主」。
宿主进程 沙箱进程
│ 把 FinalAnswerTool 的 forward │
│ 改写成:序列化入参后 │
│ raise FinalAnswerException │
│ ——这段 forward 源码被抽取——→ │ 在沙箱里执行
│ │ final_answer(x)
│ │ → 把 x 序列化成 "safe:..."/"pickle:..."
│ │ → 打印/抛出带这个串的信号
│ ◄── 拿到序列化串 ──────────────│
│ 按前缀反序列化 → 真正的答案 │
└─ _deserialize_final_answer │
注意 _patch_final_answer_with_exception 里把序列化逻辑内联进 forward(而非引用外部函数),注释说明原因:这段源码会被抽出来送到远端,远端没有外部引用(remote_executors.py:163-165)。反序列化用前缀区分 safe:(JSON)和 pickle:,后者需显式 allow_pickle 开关,默认不启用——因为 pickle 反序列化本身有风险(_deserialize_final_answer,:307)。
3. 安全边界:诚实地画一条线
把四种执行路径按「隔离强度」摆一起:
| 执行方式 | 隔离强度 | 依赖 | 适用 |
|---|---|---|---|
LocalPythonExecutor | 弱(同进程,白名单减害) | 无 | 较可信代码、要低延迟 |
DockerExecutor | 中(容器) | 本地 Docker | 自管环境、可接受容器逃逸风险 |
E2B/Modal/Blaxel | 强(云端微 VM/沙箱) | 对应云服务 | 真·不可信代码 |
这条边界要记牢: 本地执行器的价值是「大幅缩小攻击面 + 零依赖 + 快」,不是「可以放心跑任意恶意代码」。库自己的 docstring 就写着它「不是安全沙箱」(local_python_executor.py:1693)。谁在什么威胁模型下用哪档,是使用者的判断,不是框架替你兜底。
远程也非兼容一切:用远程执行器时挂 managed agents 会直接报错(agents.py:1608-1609),因为子 agent 的编排还没跨进程打通。
4. 横向对比:smolagents 在 agent 框架里的取舍
结论先行:smolagents 用「代码即行动 + 手写受限解释器」换来了表达力和极简内核,代价是把安全责任更多推给使用者去选隔离档位。
和同货架其它 agent 框架相比,几个有辨识度的取舍:
| 维度 | smolagents 的选择 | 换来什么 / 代价 |
|---|---|---|
| 行动表达 | 默认写 Python 代码(CodeAgent) | 一步能组合多工具+控制流;但需要一个能跑代码的执行层 |
| 代码执行 | 自研逐节点 AST 解释器,而非直接 exec 或只靠容器 | 零依赖、低延迟、可细粒度管控;但黑名单「非穷举」,强隔离要另上远程 |
| 内核规模 | 刻意压到约千行,抽象极简 | 好读好改;但高级编排(复杂多 agent 图)需自己搭 |
| 模型耦合 | 统一 Model 接口,连「不会 function calling 的模型」也能用(靠文本解析工具调用) | 广兼容;独有能力靠 kwargs 透传,非一等公民 |
| 工具生态 | 一个 Tool 抽象 + 多来源(装饰器/Hub/MCP/LangChain/Space) | 复用广;引入外部工具带来信任问题,仅轻量静态审查 |
一句话定位: 如果你要的是「一个能读懂、能改、让模型用代码干活」的小内核,smolagents 很合适;如果你要开箱即用的强隔离或复杂编排图,得自己补齐执行档位和编排层。
5. 巧妙之处(跨章汇总,可借鉴)
- 执行器接口统一:本地解释器和四种远程沙箱共用
PythonExecutor三方法,CodeAgent 换后端零改动(agents.py:1598)。 - 同一终止语义、两套实现:同进程用
BaseException穿透,跨进程用「序列化 + 前缀」回传,概念一致、实现按边界变形。 - 内联源码以适配远程抽取:patch 里把序列化逻辑内联,因为源码会被送到无外部依赖的远端——一个很实际的分布式约束意识(
remote_executors.py:163)。 - pickle 默认关:强大但危险的反序列化要显式开,默认走 JSON——安全默认值。
6. 边界与局限(全局)
- 本地执行器是减害非隔离(见
03§6)。 - 远程执行器不支持 managed agents。
- 上下文只增不减,长任务会逼近窗口上限(见
05§3)。 - 工具跨来源引入信任面,静态审查非安全保证(见
04§6)。
7. 总代码地图(全库导航)
| 主题 | 文件 | 符号 |
|---|---|---|
| ReAct 循环骨架 | src/smolagents/agents.py | MultiStepAgent / _run_stream |
| CodeAgent(代码即行动) | src/smolagents/agents.py | CodeAgent / CodeAgent._step_stream |
| ToolCallingAgent(JSON 工具调用) | src/smolagents/agents.py | ToolCallingAgent / process_tool_calls |
| 本地受限解释器 | src/smolagents/local_python_executor.py | evaluate_ast / evaluate_call / LocalPythonExecutor |
| 安全清单 | src/smolagents/local_python_executor.py | DANGEROUS_MODULES / BASE_PYTHON_TOOLS |
| final_answer 终止 | src/smolagents/local_python_executor.py | FinalAnswerException / evaluate_python_code |
| 执行器统一接口 | src/smolagents/local_python_executor.py | PythonExecutor |
| 远程执行器家族 | src/smolagents/remote_executors.py | RemotePythonExecutor / E2BExecutor / DockerExecutor |
| 跨进程回传答案 | src/smolagents/remote_executors.py | _patch_final_answer_with_exception / _deserialize_final_answer |
| 工具抽象 | src/smolagents/tools.py | Tool / tool / to_code_prompt |
| 模型统一接口 | src/smolagents/models.py | Model / generate / parse_tool_calls |
| 记忆 | src/smolagents/memory.py | AgentMemory / ActionStep |
| 代码抠取 | src/smolagents/utils.py | parse_code_blobs |
| 系统提示 | src/smolagents/prompts/code_agent.yaml | system_prompt |