Python-Codeausführung (Monty)
ADK-Rust führt vom Modell geschriebenes Python im Prozess über den Interpreter Pydantic Monty aus — kein Container, kein Unterprozess, Start in Mikrosekunden. Die Funktionalität wird in zwei Ebenen bereitgestellt:
adk-code(embedded-python-Funktion) —MontyExecutorBuildersowie die beiden Executor-ProdukteMontyOneShotExecutorundMontyReplExecutor, die beideCodeExecutorimplementieren.adk-tool(code-embedded-python-Funktion) —MontyPythonCodeTool(monty_python_code), das agentenseitige Tool für diese Executor.
Dies ergänzt das containerbasierte PythonCodeTool (python_code), das
vollständiges CPython in Docker ausführt — verwenden Sie dieses, wenn Skripte
das echte Python-Ökosystem benötigen (pip-Pakete, C-Erweiterungen, die
vollständige Standardbibliothek). Monty implementiert eine Teilmenge von
Python und bietet dafür Geschwindigkeit innerhalb des Prozesses,
serialisierbaren Interpreter-Zustand sowie eine konstruktionsbedingte
Garantie ohne Netzwerk und Unterprozesse.
[dependencies]
adk-tool = { version = "2.1.0", features = ["code-embedded-python"] }
Oder über den übergeordneten Crate:
[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "code-embedded-python"] }
Einmalige Ausführung vs. REPL
Ein Builder erzeugt beide Produkte; der Modus ist im Typ und nicht in einem Flag codiert:
| Modus | Build | Zustand | Nebenläufigkeit |
|---|---|---|---|
| Einmalig | build_one_shot() | Frischer Interpreter pro Aufruf | Nebenläufigkeitssicher |
| REPL | build_repl() | Variablen, Funktionen und Importe bleiben über Aufrufe hinweg erhalten | Aufrufe werden pro Sitzung serialisiert |
use adk_code::{MontyExecutorBuilder, PathAccess};
let builder = MontyExecutorBuilder::new()
.allow_path("/data", "/srv/agent/data", PathAccess::ReadOnly)
.allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
.environ_var("PROJECT", "acme")
.system_clock();
let one_shot = builder.clone().build_one_shot()?;
let repl = builder.build_repl()?;
Der REPL-Executor speichert den serialisierten Interpreter zwischen den Aufrufen. Monty bewahrt die Sitzung über Ausnahmen auf Python-Ebene hinweg, sodass ein fehlgeschlagenes Snippet den angesammelten Zustand nicht zerstört. Die Lebenszyklusmethoden CodeExecutor verwalten die Sitzung: start() initialisiert sie, stop() verwirft sie, restart() setzt sie zurück, und execute() initialisiert sie vor start() bei Bedarf.
Sicherheitsmodell
Die Isolation kombiniert explizite Richtlinien mit einer Durchsetzung durch Weglassen:
- Dateisystem. Nur mit
allow_pathgewährte Verzeichnisse sind erreichbar, jeweils schreibgeschützt oder mit Lese- und Schreibzugriff, überpathlib.Pathgegenüber dem virtuellen Einhängepfad. Montys Einhängetabelle setzt die Grenze durch (Kanonisierung und Erkennung von Entkommen über symbolische Verknüpfungen). Jeder andere Pfad löst einen abfangbarenOSErroraus (Existenzprüfungen gebenFalsezurück). - Umgebung.
os.getenv/os.environlesen ausschließlich die bei der Erstellung gewährte explizite Zuordnung — die Prozessumgebung des Hosts wird niemals offengelegt. - Uhr.
date.today()/datetime.now()funktionieren nur, wenn.system_clock()gewährt wurde; andernfalls lösen sieOSErroraus. - Netzwerk und Unterprozesse. Monty verfügt über keine Schnittstelle für beides — unabhängig von der Konfiguration unmöglich.
- Zeitüberschreitungen.
SandboxPolicy::timeoutwird auf MontysResourceLimits::max_durationabgebildet (echte Preemption innerhalb der VM, pro Aufruf). Eine Speicherbegrenzung (standardmäßig 256 MiB) begrenzt den Heap; im REPL-Modus begrenzt sie den kumulativen Sitzungsspeicher.
Berechtigungen vs. Anfrage-Richtlinie. Die Berechtigungen des Builders stellen den maximalen Zugriff dar, den ein Skript haben kann. Die Anfrage SandboxPolicy darf diesen Umfang nur einschränken – eine Anfrage, die die Berechtigungen überschreitet, wird mit ExecutionError::UnsupportedPolicy, das den Überschuss benennt, ausfallsicher abgelehnt, bevor irgendein Code ausgeführt wird.
Eine Berechtigung gilt für den gesamten Verzeichnis-Unterbaum: Das Anfordern eines berechtigten Mounts oder eines beliebigen Unterverzeichnisses davon ist erfolgreich, und der effektive Mount ist der angeforderte Pfad, der durch das entsprechende Host-Unterverzeichnis unterstützt wird. Verwende granted_policy(), um genau das anzufordern, was der Executor anbietet.
Die effektive Richtlinie einer REPL-Sitzung darf zwischen Aufrufen nicht variieren; ein Aufruf, dessen Richtlinie von der für die Sitzung festgelegten Richtlinie abweicht, wird abgelehnt, mit dem Hinweis, restart() zu verwenden.
Host-Funktionen
Registrierte Rust-Funktionen (synchron oder asynchron) werden zu aufrufbaren Python-Funktionen und sind für Skripte unter ihrem einfachen Namen sichtbar:
use adk_code::MontyExecutorBuilder;
use serde_json::json;
let executor = MontyExecutorBuilder::new()
.function_fn("row_count", "Count rows in the loaded dataset.", |args, _kwargs| async move {
Ok(json!(args.len()))
})
.build_one_shot()?;
Für die vollständige Trait-Form implementiere HostFunction (name, description, optional signature für die Eingabeaufforderung LLM sowie asynchron call mit in JSON umgewandelten Positions- und Schlüsselwortargumenten). Die Validierung der Registry erfolgt bei build_*(): Namen müssen gültige Python-Bezeichner sein, eindeutig sein und dürfen nicht mit integrierten Python-Namen kollidieren.
Innerhalb eines Skripts werden Host-Funktionen synchron aufgerufen – niemals mit await. Ein zurückgegebenes Err wird zu einer abfangbaren Python-Ausnahme, die die Nachricht enthält; der Aufruf eines nicht registrierten Namens löst eine korrigierende Ausnahme aus, in der die registrierten Namen aufgeführt sind. Die Ausführung von Host-Funktionen hat ein eigenes Zeitlimit (host_function_timeout, standardmäßig 30 s), sodass eine hängende Funktion execute() nicht blockieren kann.
Hinweis: Host-Funktionen werden als Host-Code ausgeführt. Sie bilden die eigene Vertrauensgrenze des Benutzers, nicht die von Monty – die Interpreter-Sandbox enthält ihre Nebeneffekte nicht.
Selbstdokumentierende Executoren
Beide Executors implementieren CodeExecutor::prompt_snippet() und stellen ihre
aufgebauten Fähigkeiten dar: Modussemantik, Dateisystem-Stammverzeichnisse mit
Zugriffsebenen, Namen von Umgebungsvariablen (Werte werden niemals dargestellt),
Uhrverfügbarkeit, die Garantie ohne Netzwerkzugriff und ohne Subprozesse, den
Ausgabevertrag sowie einen Python-Stub-Block für registrierte Hostfunktionen.
MontyPythonCodeTool hängt das Snippet an seine an LLM gerichtete Beschreibung an, sodass
Prompt und Verhalten im Interpreter aus derselben Konfiguration abgeleitet werden
und nicht voneinander abweichen können.
MontyPythonCodeTool
Das agentenseitige Tool (monty_python_code, Geltungsbereich code:execute) spiegelt
JavaScriptCodeTool wider: Fehler als Information JSON, camelCase-Ausgabeschlüssel und
ein strukturierter "rejected"-Fallback, wenn das Feature deaktiviert ist.
use adk_code::PathAccess;
use adk_tool::MontyPythonCodeTool;
use serde_json::json;
use std::sync::Arc;
let tool = MontyPythonCodeTool::builder()
.allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
.environ_var("PROJECT", "acme")
.system_clock()
.function_fn("get_weather", "Current weather for a city.", |args, _kwargs| async move {
Ok(json!({ "temp_c": 21 }))
})
.build_repl()?;
let agent = LlmAgentBuilder::new("data_agent")
.instruction("Use monty_python_code for calculations and data work.")
.model(model)
.tool(Arc::new(tool))
.build()?;
MontyPythonCodeTool::new() erstellt ein vollständig sandboxed One-Shot-Tool;
MontyPythonCodeTool::repl() ein vollständig sandboxed REPL-Tool.
Sitzungsgeltungsbereich
Im REPL-Modus werden Interpretersitzungen anhand der vollständigen Identität der
ADK-Sitzung bestimmt – App-Name, Benutzer-ID und Sitzungs-ID –, sodass
Zustand niemals zwischen Benutzern durchsickert, selbst wenn sich
Sitzungs-ID-Zeichenfolgen bei verschiedenen Benutzern wiederholen. Alle Sitzungen
teilen sich dieselben Berechtigungen und dieselbe Hostfunktionsregistrierung –
nur der Interpreterzustand ist sitzungsspezifisch. Die Sitzungszuordnung ist durch
ein LRU-Limit begrenzt (max_sessions, standardmäßig 100; 0 wird als 1 behandelt);
beim nächsten Aufruf einer entfernten Sitzung wird transparent ein neuer
Interpreter gestartet.
Toolargumente
| Argument | Typ | Beschreibung |
|---|---|---|
code | string (erforderlich) | Python-Quellcode zur Ausführung |
input | any | Optionaler JSON-Wert, der an die Variable input gebunden ist |
timeout_secs | Ganzzahl | Zeitbudget des Interpreters (Standardwert 30, begrenzt auf 1–300) |
reset | Boolescher Wert | Nur im REPL-Modus: Persistente Sitzung vor der Ausführung verwerfen |
Ausgabehülle
{ "status": "success", "stdout": "", "stderr": "", "output": {"n": 42},
"stdoutTruncated": false, "stderrTruncated": false, "durationMs": 3 }
Es gibt kein exitCode — die Ausführung erfolgt innerhalb des Prozesses, es wird kein Prozess gestartet;
status ist das Signal für Erfolg bzw. Fehlschlag. stdoutTruncated / stderrTruncated
melden, wenn die erfasste Ausgabe am Byte-Limit der Sandbox-Richtlinie gekürzt wurde (standardmäßig jeweils 1 MB).
Der Wert des abschließenden Ausdrucks des Skripts wird als output zurückgegeben; die
print()-Ausgabe wird als stdout erfasst. Fehlerstatus: "failed" (Python-
Ausnahme — Traceback in stderr, einschließlich von Hostfunktionen ausgelöster
Ausnahmen), "timeout" (Zeitbudget überschritten), "rejected" (ungültige Argumente oder
Funktion deaktiviert). Niemals ein ToolError.
Die Hülle ist in beiden Modi identisch, daher deklariert das Tool sie über
Tool::response_schema() — Anbieter, die Antwortschemas bereitstellen, erhalten sie
in der Tooldeklaration zusammen mit parameters.
Beziehung zu CodeAct
Der Pfad CodeActAgent + adk-codeact-monty führt ebenfalls Python über Monty aus,
jedoch mit ADK Tool-Dispatch aus Skripten heraus (call_tool(...)) und
Aussetzen/Fortsetzen über mehrere Agentenrunden hinweg. MontyPythonCodeTool schließt
beides bewusst aus — es ist ein eigenständiges Tool zur Codeausführung, dessen
Erweiterungsschnittstelle die Hostfunktionsregistrierung ist. Siehe Coding Agent für
CodeAct.
Beispiel
examples/monty_python_code_tool führt eine LlmAgent mit einem REPL-Modus-
MontyPythonCodeTool aus, der mit einer Lese-/Schreibbereitstellung, einer Umgebungsvariablen
und einer registrierten Hostfunktion konfiguriert ist — und demonstriert so die
Persistenz von Variablen über mehrere Runden hinweg sowie Aufrufe von Hostfunktionen aus vom Modell geschriebenem Python.