monty-go:Pydantic 的 Monty Python 解释器的纯 Go 封装
摘要
monty-go 是一款纯 Go 工具,支持在沙箱化的 WebAssembly 环境中运行 LLM 生成的 Python 代码,实现高效、安全的代码执行,并与 Go 集成。
查看缓存全文
缓存时间: 2026/08/30 15:46
fugue-labs/monty-go
来源:https://github.com/fugue-labs/monty-go
monty-go
从Go安全运行LLM生成的Python代码——无需容器、CGO或子进程。
一个纯Go语言的Pydantic Monty (https://github.com/pydantic/monty) Python解释器封装器,该解释器被编译为WebAssembly,并通过wazero (https://wazero.io)加载。你的Go代理编写Python代码,monty-go在一个启动时间小于毫秒的沙盒化WASM实例中执行该代码,并在代码调用外部函数时暂停,以便你的Go代码处理该调用。
go get github.com/fugue-labs/monty-go
为什么需要它?
当LLM编写代码而非进行顺序工具调用时,它们工作得更快、更便宜、也更可靠。与其:
代理 -> tool_call("search", {query: "weather london"}) -> 结果
代理 -> tool_call("search", {query: "weather tokyo"}) -> 结果
代理 -> tool_call("compare", {a: result1, b: result2}) -> 结果
不如让LLM编写:
london = search(query="weather london")
tokyo = search(query="weather tokyo")
compare(a=london, b=tokyo)
一次模型调用代替了三次。Python代码调用你的Go函数,Monty在每次调用时暂停,你的Go代码执行调用,然后Monty继续。没有容器。没有沙盒服务。没有 exec()。只是一个嵌入到你Go二进制文件中的2.9MB WASM二进制文件。
关于动机,请参阅:
- Anthropic的《可编程工具调用》(https://platform.claude.com/docs/en/agents-and-tools/tool-use/programmatic-tool-calling)
- Anthropic的《使用MCP执行代码》(https://www.anthropic.com/engineering/code-execution-with-mcp)
- Cloudflare的《代码模式》(https://blog.cloudflare.com/code-mode/)
- Hugging Face的《Smol Agents》(https://github.com/huggingface/smolagents)
快速开始
package main
import (
"context"
"fmt"
"log"
montygo "github.com/fugue-labs/monty-go"
)
func main() {
runner, err := montygo.New()
if err != nil {
log.Fatal(err)
}
defer runner.Close()
result, err := runner.Execute(context.Background(),
"x * 2 + y",
map[string]any{"x": 10, "y": 5},
)
if err != nil {
log.Fatal(err)
}
fmt.Println(result) // 25
}
外部函数(暂停/恢复)
真正的力量在于外部函数调用。每当Python代码调用你声明的函数时,Monty会暂停执行,你的Go回调函数处理该调用,然后Monty使用返回值恢复执行:
result, err := runner.Execute(ctx,
`
london = get_weather("London")
tokyo = get_weather("Tokyo")
f"{london['city']}: {london['temp']}°C, {tokyo['city']}: {tokyo['temp']}°C"
`,
nil,
montygo.WithExternalFunc(func(ctx context.Context, call *montygo.FunctionCall) (any, error) {
city, _ := call.Args["city"].(string)
// 你的实际实现——HTTP调用、数据库查询等等。
return map[string]any{"city": city, "temp": 22}, nil
}, montygo.Func("get_weather", "city")),
)
// 结果: "London: 22°C, Tokyo: 22°C"
多个函数的工作方式相同——注册它们所有并按名称分派:
result, err := runner.Execute(ctx, code, nil,
montygo.WithExternalFunc(func(ctx context.Context, call *montygo.FunctionCall) (any, error) {
switch call.Name {
case "search":
return doSearch(call.Args)
case "calculate":
return doCalculate(call.Args)
case "store":
return doStore(call.Args)
default:
return nil, fmt.Errorf("unknown function: %s", call.Name)
}
},
montygo.Func("search", "query"),
montygo.Func("calculate", "expression"),
montygo.Func("store", "key", "value"),
),
)
资源限制
使用内存、时间、分配和递归限制来防止代码失控:
result, err := runner.Execute(ctx, code, inputs,
montygo.WithLimits(montygo.Limits{
MaxDuration: 5 * time.Second,
MaxMemoryBytes: 10 * 1024 * 1024, // 10 MB
MaxAllocations: 100000,
MaxRecursionDepth: 100,
}),
)
无限循环、内存炸弹和深度递归都会以 *MontyError 干净地终止。Go的 context.Context 超时也会被遵守——取消上下文,WASM实例就会停止。
输出捕获
捕获Python print() 的输出:
var output strings.Builder
_, err := runner.Execute(ctx, `print("step 1 done")`, nil,
montygo.WithPrintFunc(func(s string) { output.WriteString(s) }),
)
fmt.Print(output.String()) // "step 1 done\n"
系统调用
Python文件系统和环境访问通过你的Go回调函数进行路由:
result, err := runner.Execute(ctx,
`
from pathlib import Path
data = Path("/config/settings.json").read_text()
data
`,
nil,
montygo.WithOsCallFunc(func(ctx context.Context, call *montygo.OsCall) (any, error) {
switch call.Function {
case "Path.read_text":
path, _ := call.Args[0].(string)
return readFromYourStorage(path)
case "Path.exists":
path, _ := call.Args[0].(string)
return existsInYourStorage(path), nil
default:
return nil, fmt.Errorf("blocked: %s", call.Function)
}
}),
)
除非你的回调函数允许,否则不会发生文件系统访问。
Gollem集成
monty-go旨在为Gollem (https://github.com/fugue-labs/gollem)(Go的生产级代理框架)中的代码模式提供支持。LLM不是进行顺序工具调用,而是编写调用你的工具作为函数的Python代码——Monty安全地执行它,而Gollem编排整个过程。
这是与Gollem结合使用的示例:
import (
"github.com/fugue-labs/gollem"
"github.com/fugue-labs/gollem/provider/anthropic"
montygo "github.com/fugue-labs/monty-go"
)
// 你现有的Gollem工具——search、calculate、store等。
searchTool := gollem.FuncTool[SearchParams]("search", "Search the knowledge base", doSearch)
calcTool := gollem.FuncTool[CalcParams]("calculate", "Run calculations", doCalc)
// 创建一个代码模式工具,用Monty封装你的工具集。
// LLM编写Python代码,Monty执行它,外部函数调用路由到你的Go工具。
codeMode := NewCodeModeTool(runner, searchTool, calcTool)
agent := gollem.NewAgent[Analysis](anthropic.New(),
gollem.WithTools[Analysis](codeMode),
gollem.WithSystemPrompt[Analysis](`You have a code execution tool.
Write Python code to call the available functions. Available functions:
- search(query: str) -> dict: Search the knowledge base
- calculate(expression: str) -> float: Evaluate math expressions
Write code that calls these functions and returns the result.`),
)
result, _ := agent.Run(ctx, "Compare Q3 and Q4 revenue and calculate the growth rate")
通过一次模型调用,LLM编写:
q3 = search(query="Q3 revenue")
q4 = search(query="Q4 revenue")
growth = calculate(expression=f"({q4['revenue']} - {q3['revenue']}) / {q3['revenue']} * 100")
{"q3": q3, "q4": q4, "growth_rate": growth}
Monty暂停三次(两次搜索,一次计算),你的Go函数处理每次调用,最终结果通过Gollem的类型化输出管道流回。在一个LLM往返中完成三次工具调用。
为什么选择Gollem + monty-go:
| 传统工具调用 | 使用monty-go的代码模式 | |
|---|---|---|
| LLM调用 | 每个工具使用一次 | 所有工具一次 |
| 延迟 | N × 模型往返 | 1 × 模型往返 + 微秒级执行 |
| 成本 | N × 输入/输出token | 1 × 输入/输出token |
| 逻辑 | LLM逐步推理 | LLM一次性编写逻辑 |
| 控制流 | 无(仅顺序) | 循环、条件、变量 |
| 错误处理 | LLM必须对每个故障作出反应 | Python中的try/except |
| 安全性 | ✅(工具是Go函数) | ✅(WASM沙盒 + 你的回调) |
Gollem为你提供编译时类型安全、结构化输出、防护栏、成本跟踪、中间件和多提供商支持。monty-go为你提供安全的嵌入式Python执行。两者结合,让你的代理在每次模型调用中完成更多工作。
github.com/fugue-labs/gollem (https://github.com/fugue-labs/gollem) — Go的生产级代理框架。
工作原理
┌─────────────────────────────────────────────────────────┐
│ 你的Go应用程序 │
│ │
│ runner, _ := montygo.New() │
│ result, _ := runner.Execute(ctx, code, inputs, opts) │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────┐ │
│ │ wazero(纯Go WASM运行时) │ │
│ │ │ │
│ │ ┌───────────────────────────┐ │ │
│ │ │ monty.wasm(2.9 MB) │ │ ◄── go:embed │
│ │ │ Monty Python解释器 │ │ │
│ │ │ 编译为wasm32-wasi │ │ │
│ │ └──────────┬────────────────┘ │ │
│ │ │ │ │
│ │ 在外部调用时暂停 │ │
│ │ │ │ │
│ └─────────────┼────────────────────┘ │
│ │ │
│ ▼ │
│ ExternalFunc回调 ──► 你的Go代码 ──► 恢复 │
│ OsCallFunc回调 ──► 你的Go代码 ──► 恢复 │
│ PrintFunc回调 ──► 你的Go代码 │
└─────────────────────────────────────────────────────────┘
- 无需CGO。 wazero是一个纯Go的WebAssembly运行时。
- 无子进程。 WASM二进制文件通过
go:embed嵌入,并在启动时编译一次。 - 每次调用全新实例。 每次
Execute()调用都获得一个隔离的WASM实例。调用之间没有状态泄漏。 - 边界处为JSON。 所有跨越Go↔WASM边界的数据都是JSON。Go类型自然映射:
int→float64,string→string,bool→bool,nil→None,[]any→list,map[string]any→dict。
API
// 创建一个可重用的运行器。编译WASM模块一次。
runner, err := montygo.New()
defer runner.Close()
// 使用输入和选项执行Python代码。
result, err := runner.Execute(ctx, code, inputs, opts...)
// 选项:
montygo.WithExternalFunc(fn, // 注册可调用函数
montygo.Func("search", "query", "limit"), // 带命名参数
montygo.Func("calculate", "expression"),
)
montygo.WithOsCallFunc(fn) // 处理文件系统/环境访问
montygo.WithLimits(montygo.Limits{...}) // 资源限制
montygo.WithPrintFunc(fn) // 捕获print输出
// FunctionCall提供命名参数(位置参数按参数名映射):
call.Args["query"].(string) // 按参数名访问
call.ArgsJSON() // 预序列化的JSON字符串
类型
| Python | Go(结果) | Go(输入) |
|---|---|---|
int | float64 | int,float64 |
float | float64 | float64 |
str | string | string |
bool | bool | bool |
None | nil | nil |
list,tuple | []any | []any |
dict | map[string]any | map[string]any |
set | []any | — |
错误
Python异常变为 *montygo.MontyError:
result, err := runner.Execute(ctx, "1 / 0", nil)
var me *montygo.MontyError
if errors.As(err, &me) {
fmt.Println(me.Message) // "Traceback... ZeroDivisionError: division by zero"
}
Monty能做什么
跟踪上游Monty v0.0.11 (https://github.com/pydantic/monty/releases/tag/v0.0.11)。
- 算术、字符串操作、f-字符串、切片
- 函数、lambda、闭包、生成器
for/while循环,if/elif/else,break/continuetry/except/finally/else,raise,异常层次结构- 列表/字典/集合推导式,字典/集合视图操作符
range,len,sum,min,max,sorted,reversed,enumerate,zip,map,filter,all,any,getattrisinstance,type,int(),float(),str(),bool(),abs()- 带有
sep和end关键字参数的print() - PEP 448 泛化解包(调用、字面量等中的
*args,**kwargs) - 嵌套和增强下标赋值(
a[i][j] = v,a[i] += 1) - 元组比较(
<,>,<=,>=) - 多模块导入(
import a, b, c) - 标准库模块:
math(所有函数),re,datetime,json,以及sys/typing/asyncio的子集 import os,from pathlib import Path(通过OsCallFunc路由)- 数据类 实例 通过外部函数调用传递(参数、返回值和方法调用在
method_call=true时呈现) - 资源限制:时间、内存、分配、递归深度
Monty不能做什么
- 类定义(只能通过外部I/O创建数据类实例;上游Monty标记类
def为“即将推出”) match语句(上游即将推出)- 上下文管理器(
with ...) - 标准库的其余部分和所有第三方库
float('inf')/float('nan')(此桥接中的JSON序列化限制)
测试
97个端到端测试,覆盖了Monty核心测试套件中所有可测试场景:
make test
涵盖:基本表达式、打印变体、所有异常类型、数据类型往返、外部函数(参数、关键字参数、混合、复杂类型、链式、循环)、输入处理和作用域、资源限制(超时、递归、内存、分配)、系统调用、内置函数、控制流、lambda/闭包,以及执行隔离。
从源码构建
需要带有 wasm32-wasip1 目标的Rust和Go 1.23+:
rustup target add wasm32-wasip1
make build # 编译 Rust → WASM,复制到 monty.wasm
make test # 构建并运行 Go 测试
致谢
monty-go的存在源于Monty (https://github.com/pydantic/monty),由Samuel Colvin (https://github.com/samuelcolvin)和Pydantic (https://github.com/pydantic)团队创建。Monty是一个真正新颖的工程作品——一个从零开始用Rust编写的最小化、安全的Python解释器,专为AI代理构建。LLM应该编写代码而不是进行顺序工具调用的洞见,以及需要一个安全的解释器(而不是容器)来执行它,使得代码模式成为可能。
Samuel和Pydantic团队有着构建整个生态系统所依赖的基础工具的记录——Pydantic (https://github.com/pydantic/pydantic)、Pydantic AI (https://github.com/pydantic/pydantic-ai)、Logfire (https://github.com/pydantic/logfire),现在还有Monty。这个项目是他们工作的Go语言桥梁,我们感谢他们构建了它。
许可证
MIT
相似文章
pydantic-monty 调查
对 pydantic-monty 的调查,这是一个用 Rust 编写的用于沙盒执行的最小化 Python 解释器,确认其安全限制(持续时间、内存、分配、递归)按预期工作。
@GitTrend0x: AI Agent 安全 Python 执行器 7.5k star 杀手级开源神器 https://github.com/pydantic/monty 这就是 Monty,Pydantic 官方出品的 Rust 极致轻量安全 Python …
Monty 是一个用 Rust 编写的极简安全 Python 解释器,专为 AI Agent 设计,提供微秒级启动和近原生 CPython 性能,同时严格限制对主机文件系统、网络和环境变量的访问。
micropython-wasm 0.1a2
MicroPython 移植到 WebAssembly,作为在浏览器中沙箱化执行 Python 的工具。
mvm - 一个快速的 Go 虚拟机
mvm 是一个快速、可移植的 Go 虚拟机,支持直接从源码运行 Go 程序、嵌入 Go 解释器,并包含 REPL、调试器和标准库。
使用MicroPython和WASM在沙盒中运行Python代码
Simon Willison介绍了micropython-wasm,一个alpha版本包,它使用编译为WebAssembly的MicroPython在沙盒中运行Python代码,从而在Datasette等Python应用中实现安全的插件执行。