
monty v0.0.19
Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, zur Verwendung durch KI.
Monty
Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, für die Nutzung durch KI.
Experimentell – Dieses Projekt befindet sich noch in der Entwicklung und ist noch nicht bereit für den produktiven Einsatz.
Ein minimaler, sicherer Python-Interpreter, geschrieben in Rust, für die Nutzung durch KI.
Monty vermeidet die Kosten, Latenz, Komplexität und den allgemeinen Aufwand einer vollständigen containerbasierten Sandbox für die Ausführung von LLM-generiertem Code.
Stattdessen können Sie damit sicher Python-Code ausführen, der von einem LLM geschrieben wurde und in Ihrem Agenten eingebettet ist – mit Startzeiten im einstelligen Mikrosekundenbereich, nicht in Hunderten von Millisekunden.
Was Monty kann:
- Eine vernünftige Teilmenge von Python-Code ausführen – genug, damit Ihr Agent ausdrücken kann, was er tun möchte
- Den Zugriff auf die Host-Umgebung vollständig blockieren: Dateisystem, Umgebungsvariablen und Netzwerkzugriff werden alle über externe Funktionsaufrufe implementiert, die der Entwickler kontrollieren kann
- Funktionen auf dem Host aufrufen – nur Funktionen, auf die Sie ihm Zugriff gewähren
- Typprüfung ausführen – Monty unterstützt vollständige moderne Python-Typannotationen und enthält ty in einer einzigen Binärdatei, um die Typprüfung auszuführen
- An externen Funktionsaufrufen in Bytes gesnapshotet werden, sodass Sie den Interpreterzustand in einer Datei oder Datenbank speichern und später fortsetzen können
- Äußerst schnell starten (<1μs vom Code zum Ausführungsergebnis), und die Laufzeitleistung ist ähnlich wie bei CPython (in der Regel zwischen 5x schneller und 5x langsamer)
- Aus Rust, Python oder Javascript aufrufbar sein – da Monty keine Abhängigkeiten von CPython hat, können Sie es überall dort einsetzen, wo Sie Rust ausführen können
- Ressourcennutzung kontrollieren – Monty kann Speichernutzung, Stacktiefe und Ausführungszeit verfolgen und die Ausführung abbrechen, wenn sie voreingestellte Grenzwerte überschreitet
- stdout und stderr sammeln und an den Aufrufer zurückgeben
- Async- oder Sync-Code auf dem Host über Async- oder Sync-Code auf dem Host ausführen
- Eine kleine Teilmenge der Standardbibliothek verwenden:
sys,os,typing,asyncio,re,datetime,json,dataclasses(bald)
Was Monty nicht kann:
- Den Rest der Standardbibliothek verwenden
- Drittanbieterbibliotheken (wie Pydantic) verwenden; die Unterstützung externer Python-Bibliotheken ist kein Ziel
- Klassen definieren (Unterstützung sollte bald kommen)
- match-Anweisungen verwenden (auch hier sollte die Unterstützung bald kommen)
Kurz gesagt, Monty ist extrem begrenzt und für einen Anwendungsfall konzipiert:
Um von Agenten geschriebenen Code auszuführen.
Wenn Sie wissen möchten, warum Sie das tun sollten, siehe:
- Codemode von Cloudflare
- Programmatic Tool Calling von Anthropic
- Code Execution with MCP von Anthropic
- Smol Agents von Hugging Face
In einfachen Worten: Die Idee hinter all dem ist, dass LLMs schneller, kostengünstiger und zuverlässiger arbeiten können, wenn sie gebeten werden, Python- (oder Javascript-)Code zu schreiben, anstatt sich auf traditionelles Tool Calling zu verlassen. Monty macht das möglich, ohne die Komplexität einer Sandbox oder das Risiko, Code direkt auf dem Host auszuführen.
Hinweis: Monty wird (bald) zur Implementierung von codemode in Pydantic AI verwendet.
Verwendung
Monty kann aus Python, JavaScript/TypeScript oder Rust aufgerufen werden.
Python
Zur Installation:```bash uv add pydantic-monty
(Oder `pip install pydantic-monty` für die Boomer)
`pydantic-monty` ist ein Metapaket, das `pydantic-monty-client` (das
`pydantic_monty`-Modul) mit `pydantic-monty-runtime` (die `monty`-Worker-
Binärdatei) bündelt. Installiere `pydantic-monty-client` allein, wenn die Binärdatei bereits von
anderswo stammt.
Verwendung:```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())
Die Ausführung erfolgt in einem Pool von monty-Worker-Subprozessen, sodass selbst ein Speicherfehler, der durch adversarischen Code ausgelöst wird (Stack-Overflow, Allocator-Abbruch), niemals deinen Prozess zum Absturz bringen kann – der Worker stirbt, wirft MontyCrashedError und wird ersetzt. Es gibt auch eine vollständig synchrone 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
Zur Installation:```bash
npm install @pydantic/monty
Das JS-Paket ist eine native (napi) Bindung über denselben Rust-Worker-Pool, den das Python-Paket verwendet — die Bindung und die monty-Worker-Binärdatei werden über plattformspezifische npm-Pakete ausgeliefert:```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' }, })
Für Browser (oder überall dort, wo Subprozesse unmöglich sind) stellt dasselbe Paket einen In-Process-WebAssembly-Build unter dem `@pydantic/monty/wasm`-Unterpfad bereit (keine Crash-Isolation: Ein Sandbox-Absturz ist dort ein Host-Absturz).
### Rust
Für die Ausführung von nicht vertrauenswürdigem Code aus Rust empfehlen wir die
[`monty-pool`](https://crates.io/crates/monty-pool) Crate anstelle der unten beschriebenen In-Process-API.
`monty-pool` führt Code nur in `monty`-Worker-Subprozessen aus, was zusätzliche Schutzmaßnahmen bietet:
Ein Absturz, der durch bösartigen Code ausgelöst wird (Stack-Überlauf, Allocator-Abbruch), tötet nur den Worker —
der Pool erkennt den Tod und ersetzt den Worker — und ein Watchdog auf der Elternseite kann Worker beenden,
die ein hartes Timeout überschreiten. Es ist dieselbe Engine, auf der die oben genannten Python- und JavaScript-Pakete
basieren. Siehe [monty-pool README](https://github.com/pydantic/monty/tree/main/crates/monty-pool)
zur Verwendung.
Die `monty`-Crate selbst stellt den In-Process-Interpreter bereit:```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));
Serialisierung
Eine REPL-Sitzung kann mit dump() serialisiert und mit Dump::load() wiederhergestellt werden. Der Dump enthält die Sitzungsmetadaten (Skriptname, Typprüfungs-Stubs) zusammen mit dem Interpreterzustand, hinter einer Version, die der ladende Build prüft:```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` und `RunProgress` haben kein eigenes Dump-Format, aber beide implementieren `serde::Serialize`/`Deserialize`, sodass ein Host geparsten Code oder einen pausierten Lauf mit dem Format serialisieren kann, das er bereits verwendet.
## Speichergrenzen in Workern
Das `max_memory` einer Session wird vom Allocator des Workers gemessen. Der Interpreter
meldet einen sauberen `MemoryError`, nachdem die weiche Grenze überschritten wurde; eine höhere harte
Grenze beendet und ersetzt den Worker, wenn eine einzelne Allokation zwischen Checkpoints zu weit springt.
Siehe [`limitations/resource_limits.md`](https://github.com/pydantic/monty/blob/HEAD/limitations/resource_limits.md) dafür, wie
sich das Überschreiten einer Grenze für einen Host bemerkbar macht, und `monty-alloc` für den Allocator, unter dem sowohl
die Subprocess- als auch die WebAssembly-Worker laufen.
## PydanticAI-Integration
Monty wird den Code-Modus in
[Pydantic AI](https://github.com/pydantic/pydantic-ai) antreiben. Anstatt
sequenzielle Tool-Aufrufe durchzuführen, schreibt das LLM Python-Code, der Ihre Tools
als Funktionen aufruft, und Monty führt ihn sicher aus.```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())
Community-Anbindungen
- Go: gomonty - Go-Anbindungen für den Monty-Interpreter
- Dart/Flutter: dart_monty (github) (pub.dev)- Dart/Flutter-Anbindungen für Monty
Alternativen
Es gibt im Allgemeinen zwei Reaktionen, wenn man Menschen Monty zeigt:
- Oh mein Gott, das löst so viele Probleme, das will ich.
- Warum nicht X?
Wobei X für eine alternative Technologie steht. Seltsamerweise werden diese Reaktionen oft kombiniert, was darauf hindeutet, dass die Leute noch keine Alternative gefunden haben, die für sie funktioniert, aber ungläubig sind, dass es wirklich keine gute Alternative dazu gibt, eine komplette Python-Implementierung von Grund auf neu zu erstellen.
Ich werde versuchen, die offensichtlichsten Alternativen durchzugehen und zu erklären, warum sie für das, was wir wollten, nicht das Richtige sind.
HINWEIS: Alle diese Technologien sind beeindruckend und haben weit verbreitete Anwendungen; diese Anmerkungen zu ihren Einschränkungen für unseren Anwendungsfall sollten nicht als Kritik verstanden werden. Die meisten dieser Lösungen wurden nicht mit dem Ziel entwickelt, eine LLM-Sandbox bereitzustellen, weshalb sie darin nicht unbedingt großartig sind.
| Tech | Language completeness | Security | Start latency | FOSS | Setup complexity | File mounting | Snapshotting |
|---|---|---|---|---|---|---|---|
| Monty | teilweise | streng | 0.06ms | kostenlos / OSS | einfach | einfach | einfach |
| Docker | vollständig | gut | 195ms | kostenlos / OSS | mittel | einfach | mittel |
| Pyodide | vollständig | schlecht | 2800ms | kostenlos / OSS | mittel | einfach | schwierig |
| starlark-rust | sehr eingeschränkt | gut | 1.7ms | kostenlos / OSS | einfach | nicht verfügbar? | unmöglich? |
| WASI / Wasmer | teilweise, fast vollständig | streng | 66ms | kostenlos * | mittel | einfach | mittel |
| Sandboxing-Dienst | vollständig | streng | 1033ms | nicht kostenlos | mittel | schwierig | mittel |
| YOLO Python | vollständig | nicht vorhanden | 0.1ms / 30ms | kostenlos / OSS | einfach | einfach / beängstigend | schwierig |
Siehe ./scripts/startup_performance.py für das Skript, mit dem die Startleistungszahlen berechnet wurden.
Details zu jeder Zeile unten:
Monty
- Sprachvollständigkeit: Noch keine Klassen, eingeschränkte Standardbibliothek, keine Drittanbieter-Bibliotheken
- Sicherheit: Explizit kontrollierter Zugriff auf Dateisystem, Netzwerk und Umgebung, strenge Grenzen für Ausführungszeit und Speichernutzung
- Startlatenz: Startet in Mikrosekunden
- Einrichtungsaufwand: einfach
pip install pydantic-montyodernpm install @pydantic/monty, ~4.5MB Download - Dateimounting: Streng kontrolliert, siehe #85
- Snapshotting: Montys Pause- und Fortsetzungsfunktion mit
dump()undload()macht es trivial, die Ausführung zu pausieren, fortzusetzen und zu forken
Docker
- Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
- Sicherheit: Prozess- und Dateisystem-Isolierung, Netzwerkrichtlinien, aber Container-Escapes existieren, Speicherbegrenzung ist möglich
- Startlatenz: Container-Startoverhead (~195ms gemessen)
- Einrichtungsaufwand: Erfordert Docker-Daemon, Container-Images, Orchestrierung -
python:3.14-alpineist 50MB groß - Docker kann nicht von PyPI installiert werden - Dateimounting: Volume-Mounts funktionieren gut
- Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal oder durch Erstellen eines Snapshots eines Images und Speichern als Docker-Image.
Pyodide
- Sprachvollständigkeit: Vollständiges CPython, zu WASM kompiliert, fast alle Bibliotheken verfügbar
- Sicherheit: Verlässt sich auf die Browser-/WASM-Sandbox - nicht für serverseitige Isolierung gedacht - Python-Code kann beliebigen Code in der JS-Laufzeit ausführen, nur deno ermöglicht Isolierung, Speichergrenzen sind mit deno schwer/unmöglich durchzusetzen
- Startlatenz: Das Laden der WASM-Laufzeit ist langsam (~2800ms Kaltstart)
- Einrichtungsaufwand: Man muss die WASM-Laufzeit laden und die asynchrone Initialisierung handhaben - das pyodide-NPM-Paket ist ~12MB, deno ist ~50MB - Pyodide kann nicht nur mit PyPI-Paketen genutzt werden
- Dateimounting: Virtuelles Dateisystem über Browser-APIs
- Snapshotting: Vermutlich möglich mit Durable-Execution-Lösungen wie Temporal, aber schwierig
starlark-rust
Siehe starlark-rust.
- Sprachvollständigkeit: Konfigurationssprache, kein Python - keine Klassen, Ausnahmen, async
- Sicherheit: Deterministisch und hermetisch per Design
- Startlatenz: Läuft eingebettet im Prozess wie Monty, daher beeindruckende Startzeit
- Einrichtungsaufwand: In Python nutzbar über starlark-pyo3
- Dateimounting: Keine Dateibehandlung per Design, soweit ich weiß (AFAIK)?
- Snapshotting: Unmöglich, soweit ich weiß (AFAIK)?
WASI / Wasmer
Python in WebAssembly über Wasmer ausführen.
- Sprachvollständigkeit: Vollständiges CPython - reine Python-Externpakete funktionieren über Mounting, Externpakete mit C-Bindings funktionieren nicht
- Sicherheit: Prinzipiell sollte WebAssembly starke Sandbox-Garantien bieten.
- Startlatenz: Das wasmer-Python-Paket wurde seit 3 Jahren nicht aktualisiert, und ich konnte keine Dokumentation zum Aufrufen von Python in wasmer aus Python heraus finden, also habe ich es über einen Unterprozess (subprocess) aufgerufen. Die Startlatenz betrug 66ms.
- Einrichtungsaufwand: Der wasmer-Download ist 100mb groß, das „python/python"-Paket ist 50mb.
- FOSS: Ich habe dies als „kostenlos *" markiert, da die Kosten bei null liegen, aber nicht alles scheint Open Source zu sein. Stand 2026-02-10 hat das
python/python-wasmer-Paket keine README, keine Lizenz, keinen Quellcode-Link und keinen Hinweis darauf, wie es erstellt wird - die kürzlich hochgeladenen Versionen zeigen die Größe als „0B" an, obwohl der Download ~50MB beträgt - der Build-Prozess für die Python-Binärdatei ist nicht klar und transparent. (Falls ich hier falsch liege, erstellt bitte ein Issue, um mich zu korrigieren) - Dateimounting: Unterstützt
- Snapshotting: Unterstützt über Journaling
Sandboxing-Dienst
Dienste wie Daytona, E2B, Modal.
Es gibt ähnliche Herausforderungen, mehr Einrichtungsaufwand, aber geringere Netzwerklatenz, wenn man sein eigenes Sandbox-Setup mit k8s aufsetzt.
- Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
- Sicherheit: Professionell verwaltete Container-Isolierung
- Startlatenz: Netzwerk-Roundtrip und Container-Startzeit. Ich habe mit Daytona EU von London aus eine Kaltstartzeit von ~1s erreicht - Daytona bewirbt eine Latenz von unter 90ms, vermutlich für einen bestehenden Container, unklar, ob die Netzwerklatenz enthalten ist
- FOSS: Bezahlung pro Ausführung oder Rechenzeit, einige Implementierungen sind Open Source
- Einrichtungsaufwand: API-Integration, Authentifizierungs-Tokens - für Startups in Ordnung, aber für Unternehmen in der Regel ein No-Go
- Dateimounting: Hoch-/Herunterladen über API-Aufrufe
- Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal - die Dienste bieten auch einige Lösungen dafür an, ich glaube auf Basis von Docker-Containern
YOLO Python
Python direkt über exec() (~0.1ms) oder Unterprozess (~30ms) ausführen.
- Sprachvollständigkeit: Vollständiges CPython mit jeder Bibliothek
- Sicherheit: Keine - voller Zugriff auf Dateisystem, Netzwerk, Umgebungsvariablen, Systembefehle
- Startlatenz: Nahezu null für
exec(), ~30ms für Unterprozesse - Einrichtungsaufwand: Keiner
- Dateimounting: Direkter Dateisystemzugriff (das ist das Problem)
- Snapshotting: Möglich mit Durable-Execution-Lösungen wie Temporal
Teil des Pydantic-Stacks
Der Pydantic-Stack ist alles, was du brauchst, um produktionsreife KI-Agenten auszuliefern:
- Pydantic AI - Typsicheres Agent-Framework
- Pydantic Logfire - KI-first, Full-Stack-Observability
- Logfire AI Gateway - Einheitlicher LLM-Proxy