プログラミング
monty-go: PydanticのMonty Pythonインタープリターのための純粋Goラッパー
monty-go: Pure-Go wrapper for Pydantic's Monty Python Interpreter (github.com)
要約
monty-goは、Go言語から安全にLLM生成Pythonコードを実行するためのライブラリです。コンテナやCGO、サブプロセスを必要とせず、WebAssemblyにコンパイルされたPydanticのMonty Pythonインタープリターをwazero経由でロードします。これにより、Goエージェントが書いたPythonコードを、サブミリ秒の起動時間でサンドボックス化されたWASMインスタンス内で実行し、外部関数呼び出し時にはGoコードで処理を委ねることができます。
全文翻訳
monty-go
LLM生成Pythonをコンテナ、CGO、サブプロセスなしで安全に実行 — Goから。
PydanticのMonty Pythonインタープリターの純粋Goラッパーで、WebAssemblyにコンパイルされ、wazero経由でロードされます。
あなたの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)
3回のモデル呼び出しではなく、1回のモデル呼び出しです。
PythonコードはGo関数を呼び出し、Montyは各呼び出しで一時停止し、Goコードがそれを実行し、Montyは再開します。
コンテナなし。
サンドボックスサービスなし。
exec()なし。
単にGoバイナリに埋め込まれた2.9MBのWASMバイナリです。
動機については、以下を参照してください:
Anthropicからのプログラム可能なツール呼び出し
Anthropicからのコード実行
Cloudflareからのコードモード
Hugging FaceからのSmol Agents
クイックスタート
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")),
)
// result: "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インスタンスが停止します。
Printキャプチャ
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"
OS呼び出し
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は、GoのプロダクションエージェントフレームワークであるGollemのコードモードを強化するために設計されています。逐次的なツール呼び出しの代わりに、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", "知識ベースを検索", doSearch)
calcTool := gollem.FuncTool[CalcParams]("calculate", "計算を実行", doCalc)
// あなたのツールセットをMontyでラップするコードモードツールを作成します。
// LLMはPythonコードを書き、Montyはそれを実行し、外部関数呼び出しは
// あなたのGoツールにルーティングされます。
codeMode := NewCodeModeTool(runner, searchTool, calcTool)
agent := gollem.NewAgent[Analysis](anthropic.New(), gollem.WithTools[Analysis](codeMode),
gollem.WithSystemPrompt[Analysis](`あなたはコード実行ツールを持っています。利用可能な関数を呼び出すPythonコードを書いてください。
利用可能な関数:
- search(query: str) -> dict: 知識ベースを検索
- calculate(expression: str) -> float: 数式を評価
これらの関数を呼び出し、結果を返すコードを書いてください。
`),
)
result, _ := agent.Run(ctx, "Q3とQ4の収益を比較し、成長率を計算してください")
1回のモデル呼び出しで、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は3回一時停止します(検索2回、計算1回)、Go関数がそれぞれを処理し、最終結果はGollemの型付き出力パイプラインを通じて戻ります。1回のLLMラウンドトリップで3回のツール呼び出しです。
Gollem + monty-goの理由:
従来のツール呼び出し | コードモード(monty-go)
---|---
LLM呼び出し | ツール使用ごとに1回 | すべてのツールに対して1回
レイテンシ | N × モデルラウンドトリップ | 1 × モデルラウンドトリップ + μs実行
コスト | N × 入出力トークン | 1 × 入出力トークン
ロジック | LLMがステップバイステップで推論 | LLMがロジックを一度書く
制御フロー | なし(逐次のみ) | ループ、条件分岐、変数
エラー処理 | 各失敗にLLMが対応する必要がある | Pythonでのtry/except
セキュリティ | ✅(ツールはGo関数) | ✅(WASMサンドボックス + あなたのコールバック)
Gollemはコンパイル時の型安全性、構造化された出力、ガードレール、コスト追跡、ミドルウェア、マルチプロバイダーサポートを提供します。monty-goは安全な組み込みPython実行を提供します。これらを組み合わせることで、エージェントはモデル呼び出しごとに多くの作業をこなすことができます。
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 │ │
│ │ └──────────┬────────────────┘ │
│ │ │
│ │ │ pause on external call │
│ │ │ └─────────────┼───────────────────┘ │
│ │ │ ▼ │
│ │ ExternalFunc callback ──► あなたのGoコード ──► resume │
│ │ OsCallFunc callback ──► あなたのGoコード ──► resume │
│ │ PrintFunc callback ──► あなたのGoコード │
└─────────────────────────────────────────────────────────┘
CGOなし。wazeroは純粋GoのWebAssemblyランタイムです。
サブプロセスなし。WASMバイナリはgo:embedで埋め込まれ、起動時に一度コンパイルされます。
呼び出しごとに新しいインスタンス。各Execute()は分離されたWASMインスタンスを取得します。呼び出し間で状態が漏れることはありません。
境界でのJSON