Monty 是一个用 Rust 编写的最小化、安全的 Python 解释器,专为执行由 AI 代理(LLM)生成的代码而设计。它避免了传统容器沙箱的复杂性和延迟,让您能以微秒级的启动速度,安全地运行 LLM 生成的 Python 代码。同时,它提供了对文件系统、网络和资源的细粒度控制。


1. 什么是 Monty 以及它的设计目标

Monty 的设计目标是提供一个 轻量级、安全、可嵌入的 Python 解释器。它不是一个完整的 Python 实现,而是实现了 Python 的一个合理子集,足以让 AI 代理表达其意图(如数据处理、API 调用、逻辑判断)。

核心特性包括

  • 安全隔离:完全阻断对宿主机文件系统、环境变量和网络的直接访问,所有外部操作通过开发者控制的外部函数调用实现。
  • 极速启动:从代码到执行结果的启动时间 <1μs,性能与 CPython 相近(在 5 倍快至 5 倍慢之间)。
  • 状态可序列化:可以在外部函数调用时对解释器状态进行快照(snapshot),便于存储、恢复或迁移。
  • 资源控制:可追踪内存使用、栈深度和执行时间,并在超出预设限制时取消执行。
  • 多语言绑定:可从 Rust、Python 或 JavaScript 调用。

Monty 当前支持:基础 Python 语法、现代类型提示、asynciocollectionsjsonmathre 等部分标准库。

Monty 不支持:大部分标准库、第三方库(如 Pydantic)、类继承和元类、match 语句。


2. 安装与集成

Monty 的设计是作为您项目的一个组件被集成,而不是独立服务。

2.1 在 Python 项目中使用

安装

1
2
3
uv add pydantic-monty
# 或
pip install pydantic-monty

pydantic-monty 是一个元包,它会安装 pydantic-monty-client(Python 客户端库)和 pydantic-monty-runtime(Monty 工作进程二进制文件)。

基本用法(异步)
Monty 在工作进程池中执行代码,即使恶意代码导致崩溃,也不会影响您的主进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import asyncio
import pydantic_monty

async def main():
# 创建一个 Monty 异步池
async with pydantic_monty.AsyncMonty() as pool:
# 从池中签出一个会话
async with pool.checkout(
script_name='agent.py',
type_check=True, # 启用类型检查
type_check_stubs='''
from typing import Any
Messages = list[dict[str, Any]]
async def call_llm(prompt: str, messages: Messages) -> str | Messages:
raise NotImplementedError()
''', # 为外部函数提供类型存根
) as session:
# 定义您的代理逻辑
code = """
async def agent(prompt: str, messages: Messages):
while True:
print(f'messages so far: {messages}')
output = await call_llm(prompt, messages)
if isinstance(output, str):
return output
messages.extend(output)
await agent(prompt, [])
"""
# 注入外部函数和变量
async def call_llm(prompt: str, messages: list) -> str | list:
# ... 您的 LLM 调用逻辑 ...
return "模拟的 LLM 响应"

# 执行代码
output = await session.feed_run(
code,
inputs={'prompt': '你的提示词'}, # 注入输入变量
external_lookup={'call_llm': call_llm}, # 注册外部函数
)
print(output)

asyncio.run(main())

同步 API
如果您不需要异步,Monty 也提供了同步接口:

1
2
3
4
5
6
7
import pydantic_monty

with pydantic_monty.Monty() as pool:
with pool.checkout() as session:
session.feed_run('x = 21')
result = session.feed_run('x * 2')
print(result) # 输出 42

2.2 在 JavaScript / TypeScript 项目中使用

安装

1
npm install @pydantic/monty

此包提供原生(napi)绑定,并包含 monty 工作进程二进制文件。

基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Monty } from '@pydantic/monty'

// 创建池
await using pool = await Monty.create()
// 签出会话
await using session = await pool.checkout()

// 注入变量并执行
await session.feedRun('x = 21')
const result = await session.feedRun('x * 2')
console.log(result) // 42

// 使用外部异步函数
const result2 = await session.feedRun('await fetch_data()', {
externalLookup: { fetch_data: async () => 'data' },
})

在浏览器中使用(通过 WebAssembly):

1
2
import { Monty } from '@pydantic/monty/wasm'
// 注意:此模式下没有崩溃隔离,沙箱崩溃会导致宿主崩溃。

2.3 在 Rust 项目中使用

对于 Rust 项目,推荐使用 monty-pool 包来利用工作进程池的隔离优势。

在您的 Cargo.toml 中添加:

1
2
[dependencies]
monty-pool = "0.0.21"

基本用法(使用 monty-pool):

1
2
3
4
5
6
7
8
9
10
11
12
13
use monty_pool::{MontyPool, RuntimeConfig};

#[tokio::main]
async fn main() -> Result<(), monty_pool::Error> {
let pool = MontyPool::new(RuntimeConfig::default()).await?;
let mut session = pool.checkout().await?;

// 执行代码片段,状态会保持
let result = session.feed_run("x = 21", None).await?;
let result = session.feed_run("x * 2", None).await?;
// result 是 MontyObject::Int(42)
Ok(())
}

如果您需要更底层的、进程内的解释器,可以使用 monty crate:

1
2
3
4
5
6
use monty::{MontyRun, MontyObject};

let code = "def fib(n): return n if n <= 1 else fib(n-1) + fib(n-2); fib(x)";
let runner = MontyRun::new(code, "fib.py", vec!["x".to_owned()])?;
let result = runner.run(vec![MontyObject::Int(10)])?;
assert_eq!(result, MontyObject::Int(55));

Monty 还支持使用 dump()load() 对 REPL 会话状态进行序列化和恢复。


3. 与 Pydantic AI 集成(代码模式)

Monty 的典型应用场景之一是作为 Pydantic AI 的“代码模式”(Code Mode)后端。在这种模式下,LLM 不再进行传统的工具调用,而是直接编写 Python 代码,Monty 则安全地执行这些代码并调用您提供的工具函数。

1
2
3
4
5
6
7
8
9
10
11
from pydantic_ai import Agent
from pydantic_ai.toolsets.code_mode import CodeModeToolset

# 假设您已经定义了一个 toolset
weather_toolset = ...

agent = Agent(
'anthropic:claude-sonnet-4-5',
toolsets=[CodeModeToolset(weather_toolset)], # 启用代码模式
deps_type=AsyncClient,
)

之后,代理就能生成代码,通过 Monty 执行,并安全地调用 weather_toolset 中定义的工具。


4. 核心概念与高级用法

  • 池 (Pool) 与会话 (Session)Monty 对象管理一个工作进程池,每个 checkout() 可获得一个独立的会话,会话状态在执行多个 feed_run 调用间保持。
  • 外部函数 (External Functions):您可以将自己的同步或异步函数注册到 Monty 会话中,供沙箱内的 Python 代码调用。这是 Monty 实现可控外部交互的唯一方式。
  • 资源限制:您可以在创建会话时设置 max_memorymax_execution_time 等限制,防止恶意或错误的代码耗尽资源。
  • 序列化 (Snapshotting)dump()load() 功能允许您将整个解释器状态(包括变量、执行位置)序列化为字节,存储起来,后续再从该点恢复执行。

5. 替代方案对比(如何选择)

技术 适用场景 与 Monty 的对比
Monty 轻量、安全的 AI 代理代码执行 专为此场景设计,平衡了功能与安全。
Docker 完整的、隔离的环境,需要全功能 Python 安全但启动慢(~195ms),资源开销大。
Pyodide 在浏览器中运行 Python 启动慢(~2800ms),服务器端隔离能力弱。
Starlark 配置语言,非 Python 语言能力非常有限,不支持 Python 语法。
WASI / Wasmer 沙箱化 Python,接近全功能 启动稍慢(~66ms),生态成熟度仍在发展中。

6. 总结

Monty 是一个为特定场景(AI 代码执行)打造的高性能、嵌入式安全 Python 运行时。

核心使用路径

  1. 选择您的语言:在 Python、JavaScript 或 Rust 项目中安装对应的包。
  2. 创建 Monty 池Monty.create() (JS) 或 pydantic_monty.AsyncMonty() (Python)。
  3. 签出会话pool.checkout()
  4. 准备代码:编写(或让 LLM 生成)Python 代码字符串。
  5. 注入外部依赖:通过 inputsexternal_lookup 参数,安全地提供输入数据和可调用的函数。
  6. 执行:使用 feed_run() 执行代码片段,可多次调用以保持状态。
  7. (可选)序列化:使用 dump() 保存会话状态,以供后续恢复。

建议:开始使用前,请通过阅读项目示例和测试用例来了解其支持的语言子集。Monty 的功能集是高度聚焦的,请勿期待它能运行完整的、有复杂依赖的 Python 应用。它最适合作为 AI 代理执行逻辑和工具调用的高效、安全引擎。

项目地址:https://github.com/pydantic/monty