実験的 - このプロジェクトはまだ開発中であり、本番環境での使用には耐えられません。
AIが使用するための、Rustで書かれた最小限で安全なPythonインタープリター。
Montyは、LLMが生成したコードを実行するために完全なコンテナベースのサンドボックスを使う際のコスト、レイテンシ、複雑さ、そして一般的な手間を回避します。
その代わりに、エージェントに組み込まれたLLMが書いたPythonコードを安全に実行できます。起動時間は数百ミリ秒ではなく、一桁マイクロ秒単位です。
Montyができること:
sys、os、typing、asyncio、re、datetime、json、dataclasses(近日対応予定)Montyができないこと:
要するに、Montyは非常に制限されており、1つのユースケースのために設計されています:
エージェントが書いたコードを実行すること。
なぜこれを行う必要があるのかについての動機は、以下を参照してください:
非常に簡単に言うと、上記すべての考え方は、従来のツール呼び出しに依存する代わりにPython(またはJavaScript)コードを書くように求められれば、LLMはより速く、より安く、より確実に動作できるというものです。Montyは、サンドボックスの複雑さやホスト上で直接コードを実行するリスクなしに、それを可能にします。
注記: Montyは(近い将来)Pydantic AIでcodemodeを実装するために使用される予定です。
MontyはPython、JavaScript/TypeScript、またはRustから呼び出すことができます。
インストールするには:```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();