Monty四种快照类型详解:FunctionSnapshot、NameLookupSnapshot、FutureSnapshot
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
Monty是一个用 Rust 编写的极简、安全的 Python 解释器,专为让 AI 安全运行代码而设计。它的杀手级特性是:执行过程中可以随时暂停,把整个解释器序列化成一段字节,之后在任意进程甚至另一台机器上恢复。当你用feed_start启动一段代码时,Monty 不会一路跑到底,而是在每个"暂停点"返回一个快照对象——本文详解 Monty 快照的四种类型:FunctionSnapshot、NameLookupSnapshot、FutureSnapshot和MontyComplete,以及各自的恢复方式。
为什么 Monty 的快照如此"轻"?
Monty 的沙箱不持有任何操作系统资源:没有活动的文件描述符、没有套接字、没有线程。当执行暂停时,所有重要状态都在解释器自己的堆上。这意味着快照不需要重建任何外部资源,dump()出来的字节流就是整个 worker 的完整状态。
理解这一点后,四种快照类型就很好理解了——它们只是"执行为什么停下"的四种答案:
| 快照类型 | 停止原因 | 恢复方式 |
|---|---|---|
FunctionSnapshot | 调用了外部函数或 OS 调用 | resume(result)、resume_not_handled()、resume_auto() |
NameLookupSnapshot | 读取了一个未定义的名称 | resume(value=...),或resume()抛出NameError |
FutureSnapshot | 沙箱内所有任务都阻塞在宿主机 Future 上 | resume({call_id: result}) |
MontyComplete | 没停——代码跑完了 | 无需恢复,直接读.output |
FunctionSnapshot:外部函数与 OS 调用
这是最常见的快照类型。沙箱里的 Python 代码调用了你提供的宿主函数(或文件系统这类 OS 调用),Monty 就会暂停,把调用细节"摆在桌面上"。
你能看到什么:function_name(函数名)、args(位置参数)、kwargs(关键字参数)、call_id(调用编号)、is_os_function(是否为 OS 调用)、is_method_call(是否为 dataclass 方法调用)。
如何恢复:resume()接受四种形态的回答——
{'return_value': value}:调用返回该值;{'exception': 异常实例}:调用抛出该异常;{'exc_type': 'ValueError', 'message': '...'}:按类型名构造异常,适合在别处(如另一语言、反序列化后)恢复快照时拿不到原始异常对象;{'future': ...}:调用返回一个待决 Future,沙箱可以await它,稍后在产生的FutureSnapshot上结算。
另外两个便捷方法:
resume_not_handled():仅对 OS 调用快照有效,按 Monty 默认的"未处理 OS 调用"行为继续;resume_auto():自动应答本次调用,再继续驱动到下一个快照。OS 调用会先交给 feed 的挂载目录,再回退到feed_start时捕获的os=处理器;外部调用则通过external_lookup=解析,查不到的名称会让沙箱抛出NameError(与feed_run行为一致)。
一个典型循环是:while not isinstance(snapshot, MontyComplete): snapshot = snapshot.resume_auto()。
类型定义可参考 crates/monty-python/python/pydantic_monty/_monty.pyi,Rust 侧实现在 crates/monty-python/src/snapshot.rs。
NameLookupSnapshot:未定义变量名
当代码读取了一个未定义的名称时产生。它只带一个关键属性:variable_name——那个"失踪"的变量名。
恢复方式二选一:
resume(value=...):为该名称绑定一个值(任何值,包括None都是合法绑定);resume()(不带参数):保持该名称未定义,沙箱将抛出NameError。
resume_auto()则会从feed_start时捕获的external_lookup=中自动查找该名称;查不到就抛出NameError。
这是构建"按需注入宿主值"模式的基石——你在external_lookup里注册哪些名称,沙箱就能"看到"哪些,完全由你掌控。
FutureSnapshot:所有任务都阻塞了
这是最特殊的一种。当沙箱内每一个任务都阻塞在宿主机 Future 上(即外部函数返回了待决 Future,且没有其他任务可以继续推进)时,执行才会整体暂停并返回它。
你能看到什么:pending_call_ids——所有待结算 Future 的调用编号列表。
如何恢复:resume(results)接收一个{call_id: 结果}字典,为一个或多个Future 提供已结算的结果。注意一条硬规则:Future 不能解析为另一个 Future——结算结果只能是返回值或异常,传入{'future': ...}会直接抛出TypeError。
两个容易踩的坑:
- 同步会话中
resume_auto()永远抛出RuntimeError——同步会话没有事件循环去驱动协程外部函数,必须手动resume({call_id: ...}),或改用AsyncMonty; - 恢复出来的
FutureSnapshot(通过load_snapshot反序列化而来)也不能用resume_auto(),因为那些待决协程活在上一个进程里,已经消失了,同样只能手动结算。
MontyComplete:执行完成的终点
前三种快照都意味着"暂停",而MontyComplete意味着代码跑完了。它没有resume方法,唯一要做的就是读取.output——代码末尾表达式的值,每次访问时从 Monty 内部表示转换回 Python 对象。所有resume调用的返回值链,最终都以它收尾。
快照的持久化与恢复:dump 与 load_snapshot
每个快照(除MontyComplete外)都有dump()方法,把整个挂起的 worker序列化成不透明字节;之后在一个全新会话上调用session.load_snapshot(blob)即可恢复,并返回一个可继续resume的快照。几个重要细节:
- 快照只能恢复一次:每个快照至多
resume一次,重复调用会报错; - 配置随 dump 走:
script_name、资源限制、类型检查状态都来自 dump 本身;累计的时间预算也一并迁移; - 挂载不随 dump 走:宿主路径永远不是 dump 的一部分,恢复时要传入相同的
mount=,否则文件系统调用会退化为未处理的 OS 调用; - dump 与版本绑定:字节携带格式版本,不同 Monty 版本之间互不兼容,请在同一版本内使用。
完整示例见官方文档 docs/snapshots.md 的 "Storing and restoring" 一节。
四大使用场景
- 长时间运行的 Agent:在工具调用处挂起,持久化字节,工具返回后(甚至换一台主机)恢复;
- 审批关卡:在敏感调用处暂停,存储快照,人类批准后恢复;
- 分叉探索:同一个快照恢复到多个会话,从同一状态探索不同分支;
- 跨越重启:服务器因部署而排空时,交出可在他处恢复的 dump。
如果需要同步/异步两套类型名,pydantic-monty包导出了全部 7 个类(含Async前缀的三个异步版本与MontyComplete),类型别名SyncSnapshot/AsyncSnapshot定义在 crates/monty-python/python/pydantic_monty/init.py,行为测试在 crates/monty-python/tests/test_feed_start.py。
小结
Monty 用四种快照对象完整描述了"沙箱为什么停下":外部调用(FunctionSnapshot)、未定义名称(NameLookupSnapshot)、全部阻塞(FutureSnapshot)和跑完了(MontyComplete)。配合dump()/load_snapshot,解释器状态变成可以存数据库、跨进程、跨机器搬运的字节流——这正是 Monty 作为 AI 代码执行引擎最核心的超能力。
【免费下载链接】montyA minimal, secure Python interpreter written in Rust for use by AI项目地址: https://gitcode.com/GitHub_Trending/monty3/monty
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考