アップデート一覧に戻る
New releaseJul 25, 2026

monty v0.0.19

AI向けの、Rustで書かれた最小限で安全なPythonインタプリタ

共有

Monty

AIが使用するための、Rustで書かれた最小限で安全なPythonインタープリター。

CI Codspeed Coverage PyPI versions license Join Slack

実験的 - このプロジェクトはまだ開発中であり、本番環境での使用には耐えられません。

AIが使用するための、Rustで書かれた最小限で安全なPythonインタープリター。

Montyは、LLMが生成したコードを実行するために完全なコンテナベースのサンドボックスを使う際のコスト、レイテンシ、複雑さ、そして一般的な手間を回避します。

その代わりに、エージェントに組み込まれたLLMが書いたPythonコードを安全に実行できます。起動時間は数百ミリ秒ではなく、一桁マイクロ秒単位です。

Montyができること:

  • 合理的なPythonコードのサブセットを実行する - エージェントが実行したいことを表現するのに十分な範囲
  • ホスト環境へのアクセスを完全にブロックする: ファイルシステム、環境変数、ネットワークアクセスはすべて、開発者が制御できる外部関数呼び出しを介して実装される
  • ホスト上の関数を呼び出す - アクセスを許可した関数のみ
  • 型チェックを実行する - Montyは完全なモダンPython型ヒントをサポートし、型チェックを実行するためのtyを単一バイナリに同梱しています
  • 外部関数呼び出し時にバイト列としてスナップショット化できる - インタープリターの状態をファイルやデータベースに保存し、後で再開できます
  • 極めて高速に起動する(コードから実行結果まで1μs未満)、またCPythonに匹敵する実行時パフォーマンスを持つ(一般的に5倍高速から5倍低速の間)
  • Rust、Python、JavaScriptから呼び出せる - MontyはCPythonに依存しないため、Rustを実行できる場所ならどこでも使用できます
  • リソース使用量を制御する - Montyはメモリ使用量、スタックの深さ、実行時間を追跡し、事前設定された制限を超えた場合に実行をキャンセルできます
  • 標準出力と標準エラー出力を収集し、呼び出し元に返す
  • ホスト上で非同期または同期コードを介して、非同期または同期コードを実行する
  • 標準ライブラリの小さなサブセットを使用する: sysostypingasyncioredatetimejsondataclasses(近日対応予定)

Montyができないこと:

  • 標準ライブラリの残りを使用する
  • サードパーティライブラリ(Pydanticなど)を使用する - 外部Pythonライブラリのサポートは目標ではありません
  • クラスを定義する(近日対応予定)
  • matchステートメントを使用する(これも近日対応予定)

要するに、Montyは非常に制限されており、1つのユースケースのために設計されています:

エージェントが書いたコードを実行すること。

なぜこれを行う必要があるのかについての動機は、以下を参照してください:

非常に簡単に言うと、上記すべての考え方は、従来のツール呼び出しに依存する代わりにPython(またはJavaScript)コードを書くように求められれば、LLMはより速く、より安く、より確実に動作できるというものです。Montyは、サンドボックスの複雑さやホスト上で直接コードを実行するリスクなしに、それを可能にします。

注記: Montyは(近い将来)Pydantic AIcodemodeを実装するために使用される予定です。

使用方法

MontyはPython、JavaScript/TypeScript、またはRustから呼び出すことができます。

Python

インストールするには:```bash uv add pydantic-monty

(または、古い世代向けには `pip install pydantic-monty`)

`pydantic-monty` は、`pydantic-monty-client`(
`pydantic_monty` モジュール)と `pydantic-monty-runtime`(`monty` ワーカー
バイナリ)をペアにするメタパッケージです。バイナリが他の場所から既に
入手できる場合は、`pydantic-monty-client` だけをインストールしてください。

使用方法:```python
from typing import Any

import pydantic_monty

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, [])
"""

type_definitions = """
from typing import Any

Messages = list[dict[str, Any]]

async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    raise NotImplementedError()

prompt: str = ''
"""


Messages = list[dict[str, Any]]


async def call_llm(prompt: str, messages: Messages) -> str | Messages:
    if len(messages) < 2:
        return [{'role': 'system', 'content': 'example response'}]
    else:
        return f'example output, message count {len(messages)}'


async def main():
    async with pydantic_monty.AsyncMonty() as pool:
        async with pool.checkout(
            script_name='agent.py',
            type_check=True,
            type_check_stubs=type_definitions,
        ) as session:
            output = await session.feed_run(
                code,
                inputs={'prompt': 'testing'},
                external_lookup={'call_llm': call_llm},
            )
    print(output)
    #> example output, message count 2


if __name__ == '__main__':
    import asyncio

    asyncio.run(main())

実行は monty ワーカーサブプロセスのプールで行われるため、敵対的なコードによって引き起こされるメモリエラー(スタックオーバーフロー、アロケータのアボート)でも、あなたのプロセスをクラッシュさせることは決してありません。ワーカーが終了し、MontyCrashedError を発生させ、そして置き換えられます。完全に同期するAPIもあります。```python import pydantic_monty

with pydantic_monty.Monty() as pool: with pool.checkout() as session: # session state persists between feed_run calls session.feed_run('x = 21') print(session.feed_run('x * 2')) #> 42

### JavaScript / TypeScript

インストールするには:```bash
npm install @pydantic/monty

JS パッケージは、Python パッケージが使用するのと同じ Rust ワーカープールに対するネイティブ (napi) バインディングであり、 バインディングと monty ワーカーバイナリは、プラットフォーム固有の npm パッケージとして 配布されます:```ts import { Monty } from '@pydantic/monty'

await using pool = await Monty.create() await using session = await pool.checkout()

// session state persists between feedRun calls await session.feedRun('x = 21') console.log(await session.feedRun('x * 2')) // 42

// external functions may be async const result = await session.feedRun('await fetch_data()', { externalLookup: { fetch_data: async () => 'data' }, })

ブラウザ(またはサブプロセスが不可能な任意の場所)では、同じパッケージは
`@pydantic/monty/wasm` サブパス配下にインプロセスの WebAssembly ビルドを公開します
(クラッシュ分離なし: サンドボックスのクラッシュはそこではホストのクラッシュです)。

### Rust

Rust から信頼できないコードを実行する場合、以下に示すインプロセス API ではなく、
[`monty-pool`](https://crates.io/crates/monty-pool) クレートを推奨します。
`monty-pool` は `monty` ワーカーのサブプロセスでのみコードを実行するため、追加の保護が得られます:
敵対的なコードによって引き起こされるクラッシュ(スタックオーバーフロー、アロケータのアボート)はワーカーのみを終了させます —
プールはその終了を検出してワーカーを置き換えます — また、親側のウォッチドッグは、ハードタイムアウトを超えた
ワーカーを強制終了できます。これは上記の Python パッケージと JavaScript パッケージが
基づいているのと同じエンジンです。使用法については
[monty-pool README](https://github.com/pydantic/monty/tree/main/crates/monty-pool) を参照してください。

`monty` クレート自体はインプロセスのインタプリタを提供します:```rust
use monty::MontyRun;
use monty_types::{CompileOptions, ResourceTracker, MontyObject, PrintWriter, ResourceLimits};

let code = r#"
def fib(n):
    if n <= 1:
        return n
    return fib(n - 1) + fib(n - 2)

fib(x)
"#;

let runner = MontyRun::new(code.to_owned(), "fib.py", vec!["x".to_owned()], CompileOptions::default()).unwrap();
let result = runner.run(vec![MontyObject::Int(10)], ResourceTracker::default(), PrintWriter::Stdout).unwrap();
assert_eq!(result, MontyObject::Int(55));

シリアライゼーション

REPL セッションは dump() でシリアライズでき、Dump::load() で復元できます。ダンプには、読み込み側のビルドが確認するバージョンが付与され、インタープリタの状態とともにセッションのメタデータ(スクリプト名、型チェック用スタブ)が格納されます。```rust use monty::{Dump, MontyRepl, Session, SessionRef, dump}; use monty_types::{CompileOptions, MontyObject, PrintWriter, ResourceTracker};

// Snapshot a session between snippets let mut repl = MontyRepl::new("main.py", ResourceTracker::default(), CompileOptions::default()); repl.feed_run("x = 41", vec![], PrintWriter::Stdout).unwrap(); let bytes = dump("main.py", None, SessionRef::Idle(&repl)).unwrap();

// Later, restore and carry on feeding let Session::Idle(mut restored) = Dump::load(&bytes).unwrap().state else { panic!("dumped an idle session") }; let result = restored.feed_run("x + 1", vec![], PrintWriter::Stdout).unwrap(); assert_eq!(result, MontyObject::Int(42));

`MontyRun` と `RunProgress` には独自のダンプ形式はありませんが、どちらも `serde::Serialize`/`Deserialize` を実装しているため、ホストは解析済みコードや一時停止中の実行を、すでに使用している任意の形式でシリアライズできます。

## ワーカーにおけるメモリ制限

セッションの `max_memory` はワーカーのアロケータによって測定されます。インタプリタは
ソフトリミットを超えるとグレースフルな `MemoryError` を報告します。より高いハード
リミットは、チェックポイント間で1つの割り当てが大きくなりすぎた場合、ワーカーを強制終了して置き換えます。

[`limitations/resource_limits.md`](https://github.com/pydantic/monty/blob/HEAD/limitations/resource_limits.md) に、制限の超過が
ホストにどのように現れるかが説明されています。また、`monty-alloc` は、
サブプロセスとWebAssemblyの両方のワーカーが実行時に使用するアロケータです。

## PydanticAI 統合

Monty は [Pydantic AI](https://github.com/pydantic/pydantic-ai) の code-mode を
強化します。逐次的なツール呼び出しを行う代わりに、LLM はあなたのツールを
関数として呼び出す Python コードを書き、Monty がそれを安全に
実行します。```python test="skip"
import asyncio
import json

import logfire
from httpx import AsyncClient
from pydantic_ai import Agent, RunContext
from pydantic_ai.toolsets.code_mode import CodeModeToolset
from pydantic_ai.toolsets.function import FunctionToolset
from typing_extensions import TypedDict

logfire.configure()
logfire.instrument_pydantic_ai()


class LatLng(TypedDict):
    lat: float
    lng: float


weather_toolset: FunctionToolset[AsyncClient] = FunctionToolset()


@weather_toolset.tool
async def get_lat_lng(
    ctx: RunContext[AsyncClient], location_description: str
) -> LatLng:
    """Get the latitude and longitude of a location."""
    # NOTE: the response here will be random, and is not related to the location description.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/latlng',
        params={'location': location_description},
    )
    r.raise_for_status()
    return json.loads(r.content)


@weather_toolset.tool
async def get_temp(ctx: RunContext[AsyncClient], lat: float, lng: float) -> float:
    """Get the temp at a location."""
    # NOTE: the responses here will be random, and are not related to the lat and lng.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/number',
        params={'min': 10, 'max': 30},
    )
    r.raise_for_status()
    return float(r.text)


@weather_toolset.tool
async def get_weather_description(
    ctx: RunContext[AsyncClient], lat: float, lng: float
) -> str:
    """Get the weather description at a location."""
    # NOTE: the responses here will be random, and are not related to the lat and lng.
    r = await ctx.deps.get(
        'https://demo-endpoints.pydantic.workers.dev/weather',
        params={'lat': lat, 'lng': lng},
    )
    r.raise_for_status()
    return r.text


agent = Agent(
    'gateway/anthropic:claude-sonnet-4-5',
    # toolsets=[weather_toolset],
    toolsets=[CodeModeToolset(weather_toolset)],
    deps_type=AsyncClient,
)


async def main():
    async with AsyncClient() as client:
        await agent.run('Compare the weather of London, Paris, and Tokyo.', deps=client)


if __name__ == '__main__':
    asyncio.run(main())

コミュニティバインディング

  • Go: gomonty - MontyインタプリタのGoバインディング
  • Dart/Flutter: dart_monty (github) (pub.dev)- MontyのDart/Flutterバインディング

代替案

人々にMontyを見せると、一般的に2つの反応があります:

  1. なんて素晴らしいんだ、これで多くの問題が解決できる、欲しい。
  2. なぜXじゃないのか?

Xは何らかの代替技術です。不思議なことに、この2つの反応が組み合わされることがよくあります。つまり、人々は自分たちに合う代替技術をまだ見つけていない一方で、Python実装全体をゼロから作ることに本当に良い代替手段がないとは信じられない、ということです。

最もわかりやすい代替技術と、それらが私たちの求めるものに適していない理由を順に見ていきます。

注記:これらの技術はすべて素晴らしく、広く使われています。私たちのユースケースにおける制限に関するこのコメントは、批判と見なすべきではありません。これらのソリューションのほとんどはLLMサンドボックスの提供を目的として考案されたものではなく、それが必ずしも得意ではない理由です。

技術言語の完全性セキュリティ起動レイテンシFOSSセットアップの複雑さファイルマウントスナップショット
Monty部分厳格0.06ms無料 / OSS簡単簡単簡単
Docker完全良好195ms無料 / OSS中程度簡単中程度
Pyodide完全不良2800ms無料 / OSS中程度簡単困難
starlark-rust非常に限定的良好1.7ms無料 / OSS簡単利用不可?不可能?
WASI / Wasmer部分的、ほぼ完全厳格66ms無料 *中程度簡単中程度
sandboxing service完全厳格1033ms有料中程度困難中程度
YOLO Python完全存在しない0.1ms / 30ms無料 / OSS簡単簡単 / 危険困難

起動パフォーマンス数値の算出に使用したスクリプトについては、./scripts/startup_performance.py を参照してください。

各項目の詳細は以下の通りです:

Monty

  • 言語の完全性:クラスなし(現時点では)、標準ライブラリは限定、サードパーティライブラリなし
  • セキュリティ:ファイルシステム、ネットワーク、環境変数へのアクセスを明示的に制御。実行時間とメモリ使用量に厳格な制限
  • 起動レイテンシ:マイクロ秒単位で起動
  • セットアップの複雑さpip install pydantic-monty または npm install @pydantic/monty のみ、ダウンロード約4.5MB
  • ファイルマウント:厳格に制御、#85 を参照
  • スナップショット:Montyのdump()load()による一時停止・再開機能により、実行の一時停止、再開、フォークが簡単にできる

Docker

  • 言語の完全性:任意のライブラリを備えた完全なCPython
  • セキュリティ:プロセスとファイルシステムの分離、ネットワークポリシーはあるが、コンテナエスケープは存在する。メモリ制限は可能
  • 起動レイテンシ:コンテナ起動のオーバーヘッド(測定値 約195ms)
  • セットアップの複雑さ:Dockerデーモン、コンテナイメージ、オーケストレーションが必要。python:3.14-alpine は50MB - dockerはPyPIからインストールできない
  • ファイルマウント:ボリュームマウントは問題なく動作
  • スナップショット:Temporalのような永続実行ソリューション、またはイメージをスナップショットしてDockerイメージとして保存することで可能。

Pyodide

  • 言語の完全性:WASMにコンパイルされた完全なCPython、ほぼすべてのライブラリが利用可能
  • セキュリティ:ブラウザ/WASMサンドボックスに依存 - サーバーサイドの分離を想定していない。PythonコードはJSランタイム内で任意のコードを実行できる。分離を可能にするのはdenoのみで、denoでのメモリ制限の強制は困難/不可能
  • 起動レイテンシ:WASMランタイムの読み込みが遅い(コールドスタート 約2800ms)
  • セットアップの複雑さ:WASMランタイムの読み込み、非同期初期化の処理が必要。pyodideのNPMパッケージは約12MB、denoは約50MB - PyodideはPyPIパッケージだけでは呼び出せない
  • ファイルマウント:ブラウザAPI経由の仮想ファイルシステム
  • スナップショット:Temporalのような永続実行ソリューションならおそらく可能だが、困難

starlark-rust

starlark-rust を参照。

  • 言語の完全性:設定言語であり、Pythonではない - クラス、例外、asyncなし
  • セキュリティ:設計上、決定論的かつ密閉的(hermetic)
  • 起動レイテンシ:Montyと同様にプロセスに埋め込まれて実行されるため、起動時間は優れている
  • セットアップの複雑さstarlark-pyo3 経由でPythonから利用可能
  • ファイルマウント:私の知る限りでは設計上ファイル処理はない?
  • スナップショット:私の知る限りでは不可能?

WASI / Wasmer

Wasmer を介してWebAssemblyでPythonを実行する。

  • 言語の完全性:完全なCPython。純粋なPython外部パッケージはマウントで動作するが、Cバインディングを持つ外部パッケージは動作しない
  • セキュリティ:原則としてWebAssemblyは強力なサンドボックス保証を提供するはず。
  • 起動レイテンシwasmer Pythonパッケージは3年間更新されておらず、wasmer内のPythonをPythonから呼び出すドキュメントが見つからなかったため、subprocess経由で呼び出した。起動レイテンシは66msだった。
  • セットアップの複雑さ:wasmerのダウンロードは100MB、「python/python」パッケージは50MB。
  • FOSS:コストはゼロだがすべてがオープンソースとは限らないため「無料 *」とした。2026-02-10現在、python/python wasmerパッケージにはREADME、ライセンス、ソースリンク、ビルド方法の記載がない。最近アップロードされたバージョンではサイズが「0B」と表示されるが、ダウンロードは約50MB - Pythonバイナリのビルドプロセスは明確かつ透過的ではない。(ここで間違っていたら、issueを作成して訂正してください)
  • ファイルマウント:対応
  • スナップショット:ジャーナリングにより対応

sandboxing service

DaytonaE2BModal などのサービス。

k8sで独自のサンドボックス環境を構築する場合も同様の課題があり、セットアップの複雑さは増すが、ネットワークレイテンシは低くなる。

  • 言語の完全性:任意のライブラリを備えた完全なCPython
  • セキュリティ:専門的に管理されたコンテナ分離
  • 起動レイテンシ:ネットワークのラウンドトリップとコンテナ起動時間。ロンドンからDaytona EUでコールドスタート約1秒だった。Daytonaは90ms未満のレイテンシを宣伝しているが、おそらく既存のコンテナに対するもので、ネットワークレイテンシを含むかは不明
  • FOSS:実行またはコンピュート時間ごとの課金。一部の実装はオープンソース
  • セットアップの複雑さ:API統合、認証トークン - スタートアップには問題ないが、一般的にエンタープライズでは採用されにくい
  • ファイルマウント:API呼び出しによるアップロード/ダウンロード
  • スナップショット:Temporalのような永続実行ソリューションで可能。また、各サービスもこのためのソリューションを提供している。Dockerコンテナベースだと思う

YOLO Python

exec()(約0.1ms)またはsubprocess(約30ms)でPythonを直接実行する。

  • 言語の完全性:任意のライブラリを備えた完全なCPython
  • セキュリティ:なし - ファイルシステム、ネットワーク、環境変数、システムコマンドへの完全なアクセス
  • 起動レイテンシexec()ではほぼゼロ、subprocessでは約30ms
  • セットアップの複雑さ:なし
  • ファイルマウント:ファイルシステムへの直接アクセス(それが問題)
  • スナップショット:Temporalのような永続実行ソリューションで可能

Pydanticスタックの一部

Pydanticスタックは、本番環境レベルのAIエージェントをリリースするために必要なすべてを提供します:

カテゴリ