Exécution de code Python (Monty)
ADK-Rust exĂ©cute du Python Ă©crit par le modĂšle dans le processus via lâinterprĂ©teur Pydantic Monty â sans conteneur, sans sous-processus, avec un dĂ©marrage en quelques microsecondes. Cette fonctionnalitĂ© est fournie en deux couches :
adk-code(fonctionnalitĂ©embedded-python) âMontyExecutorBuilderet les deux produits dâexĂ©cution,MontyOneShotExecutoretMontyReplExecutor, qui implĂ©mentent tous deuxCodeExecutor.adk-tool(fonctionnalitĂ©code-embedded-python) âMontyPythonCodeTool(monty_python_code), lâoutil destinĂ© Ă lâagent qui sâappuie sur ces exĂ©cuteurs.
Il sâagit dâun complĂ©ment Ă PythonCodeTool (python_code), basĂ© sur un conteneur,
qui exĂ©cute CPython complet dans Docker â utilisez-le lorsque les scripts ont
besoin du véritable écosystÚme Python (packages pip, extensions C, bibliothÚque
standard complÚte). Monty implémente un sous-ensemble de Python, en échange
dâune exĂ©cution rapide dans le processus, dâun Ă©tat dâinterprĂ©teur
sĂ©rialisable et dâune garantie dâabsence de rĂ©seau et de sous-processus,
assurée par construction.
[dependencies]
adk-tool = { version = "2.1.0", features = ["code-embedded-python"] }
Ou via le crate parapluie :
[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "code-embedded-python"] }
Exécution unique ou REPL
Un seul générateur produit les deux produits ; le mode est encodé dans le type, et non dans un indicateur :
| Mode | Compilation | Ătat | Concurrence |
|---|---|---|---|
| Exécution unique | build_one_shot() | Interpréteur vierge à chaque appel | Sûr en concurrence |
| REPL | build_repl() | Les variables, fonctions et imports persistent entre les appels | Les appels sont sérialisés par session |
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()?;
LâexĂ©cuteur REPL stocke lâinterprĂ©teur sĂ©rialisĂ© entre les appels. Monty
prĂ©serve la session lors des exceptions au niveau Python, de sorte quâun
extrait ayant Ă©chouĂ© ne dĂ©truit pas lâĂ©tat accumulĂ©. Les mĂ©thodes de cycle de
vie de CodeExecutor gĂšrent la session : start() lâinitialise, stop() la
supprime, restart() la rĂ©initialise, et execute() lâinitialise paresseusement
avant start().
ModÚle de sécurité
Lâisolation combine une politique explicite et une mise en Ćuvre par omission :
- SystÚme de fichiers. Seuls les répertoires accordés avec
allow_pathsont accessibles, chacun en lecture seule ou en lecture-Ă©criture, viapathlib.Pathsur le chemin de montage virtuel. La table de montage de Monty applique la limite (canonicalisation + dĂ©tection des Ă©chappements par liens symboliques). Tout autre chemin lĂšve uneOSErrorinterceptable (les vĂ©rifications dâexistence renvoientFalse). - Environnement.
os.getenv/os.environne lisent que la map explicite accordĂ©e lors de la construction â lâenvironnement du processus hĂŽte nâest jamais exposĂ©. - Horloge.
date.today()/datetime.now()ne fonctionnent que lorsque.system_clock()a Ă©tĂ© accordĂ© ; sinon, ils lĂšventOSError. - RĂ©seau et sous-processus. Monty ne fournit aucune surface pour lâun ou lâautre â ils sont impossibles quelle que soit la configuration.
- DĂ©lais dâexpiration.
SandboxPolicy::timeoutcorrespond ĂResourceLimits::max_durationde Monty (prĂ©emption rĂ©elle dans la VM, par appel). Une limite de mĂ©moire (256 MiB par dĂ©faut) borne le tas ; en mode REPL, elle borne le tas cumulĂ© de la session.
Autorisations par rapport Ă la politique de requĂȘte. Les autorisations du constructeur constituent l'accĂšs maximal dont peut disposer un script. La SandboxPolicy par requĂȘte ne peut que les restreindre â une requĂȘte dĂ©passant les autorisations est rejetĂ©e de maniĂšre sĂ©curisĂ©e avec ExecutionError::UnsupportedPolicy indiquant l'excĂ©dent, avant l'exĂ©cution de tout code.
Une autorisation couvre toute sa sous-arborescence de répertoires : demander un point de montage autorisé ou n'importe quel sous-répertoire de celui-ci réussit, et le point de montage effectif correspond au chemin demandé, adossé au sous-répertoire hÎte correspondant. Utilisez granted_policy() pour demander exactement ce que l'exécuteur propose.
La politique effective d'une session REPL ne doit pas varier d'un appel à l'autre ; un appel dont la politique diffÚre de celle établie pour la session est rejeté, avec des instructions invitant à restart().
Fonctions hĂŽte
Les fonctions Rust enregistrées (synchrones ou asynchrones) deviennent des fonctions Python appelables, visibles par les scripts sous leur nom simple :
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()?;
Pour utiliser la forme complĂšte du trait, implĂ©mentez HostFunction (name, description, signature facultatif pour l'invite LLM, et call asynchrone avec des arguments positionnels et nommĂ©s convertis par JSON). La validation du registre a lieu lors de build_*() : les noms doivent ĂȘtre des identifiants Python valides, uniques, et ne doivent pas entrer en collision avec les fonctions intĂ©grĂ©es de Python.
Dans un script, les fonctions hĂŽte sont appelĂ©es de maniĂšre synchrone â jamais avec await. Un Err renvoyĂ© devient une exception Python interceptable contenant le message ; l'appel d'un nom non enregistrĂ© lĂšve une exception corrective listant les noms enregistrĂ©s. L'exĂ©cution des fonctions hĂŽte dispose de sa propre limite de temps rĂ©el (host_function_timeout, 30 s par dĂ©faut), afin qu'une fonction bloquĂ©e ne puisse pas bloquer execute().
Remarque : les fonctions hĂŽte s'exĂ©cutent comme du code hĂŽte. Elles relĂšvent de la propre limite de confiance de l'utilisateur, et non de celle de Monty â le bac Ă sable de l'interprĂ©teur n'isole pas leurs effets de bord.
Exécuteurs auto-descriptifs
Les deux exécuteurs implémentent CodeExecutor::prompt_snippet(), en restituant leurs
capacités intégrées : la sémantique des modes, les racines du systÚme de fichiers avec leurs niveaux d'accÚs,
les noms des variables d'environnement (les valeurs ne sont jamais restituées), la disponibilité de l'horloge,
la garantie d'absence de réseau et de sous-processus, le contrat de sortie, ainsi qu'un bloc d'initialisation Python
pour les fonctions hÎtes enregistrées. MontyPythonCodeTool ajoute l'extrait à sa description destinée à LLM,
de sorte que l'invite et le comportement dans l'interprĂ©teur dĂ©rivent de la mĂȘme configuration et ne puissent pas diverger.
MontyPythonCodeTool
L'outil destiné à l'agent (monty_python_code, portée code:execute) reproduit
JavaScriptCodeTool : les erreurs comme informations JSON, les clés de sortie en camelCase et un
repli "rejected" structuré lorsque la fonctionnalité est désactivée.
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() construit un outil ponctuel entiÚrement sandboxé ;
MontyPythonCodeTool::repl() un outil REPL entiÚrement sandboxé.
Portée des sessions
En mode REPL, les sessions de l'interpréteur sont indexées par l'identité complÚte de session ADK
â nom de l'application, identifiant utilisateur et identifiant de session â afin que l'Ă©tat ne soit jamais partagĂ© entre utilisateurs, mĂȘme lorsque les chaĂźnes d'identifiant de session se rĂ©pĂštent d'un utilisateur Ă l'autre. Toutes les sessions partagent les mĂȘmes autorisations et le mĂȘme registre de fonctions hĂŽtes ; seul l'Ă©tat de l'interprĂ©teur est propre Ă chaque session.
La table des sessions est limitée par une capacité LRU (max_sessions, 100 par défaut ; 0 est traité comme 1) ; lors de l'appel suivant d'une session évincée, un nouvel interpréteur est créé de maniÚre transparente.
Arguments de l'outil
| Argument | Type | Description |
|---|---|---|
code | chaßne (obligatoire) | Code source Python à exécuter |
input | quelconque | Valeur JSON facultative liée à la variable input |
timeout_secs | entier | Budget de temps de lâinterprĂ©teur (30 par dĂ©faut, limitĂ© entre 1 et 300) |
reset | boolĂ©en | Mode REPL uniquement : supprimer la session persistante avant lâexĂ©cution |
Enveloppe de sortie
{ "status": "success", "stdout": "", "stderr": "", "output": {"n": 42},
"stdoutTruncated": false, "stderrTruncated": false, "durationMs": 3 }
Il n'y a pas de exitCode â l'exĂ©cution se fait dans le processus, aucun processus n'est créé ;
status est le signal de réussite ou d'échec. stdoutTruncated / stderrTruncated
indiquent que la sortie capturée a été tronquée à la limite en octets
imposée par la politique du bac à sable (1 Mo
par défaut).
La valeur de l'expression finale du script est renvoyée sous forme de output ; la sortie
print() est capturée sous forme de stdout. Statuts d'échec : "failed" (exception
Python â trace d'exĂ©cution dans stderr, y compris les exceptions levĂ©es par les fonctions
hÎtes), "timeout" (budget temporel dépassé), "rejected" (arguments incorrects ou fonctionnalité
désactivée). Jamais de ToolError.
L'enveloppe est fixe dans les deux modes ; l'outil la déclare donc via
Tool::response_schema() â les fournisseurs qui exposent des schĂ©mas de rĂ©ponse la reçoivent
dans la déclaration de l'outil aux cÎtés de parameters.
Relation avec CodeAct
Le parcours CodeActAgent + adk-codeact-monty exécute également du Python via Monty,
mais avec une distribution ADK Tool depuis l'intérieur des scripts (call_tool(...)) et
une suspension/reprise entre les tours de l'agent. MontyPythonCodeTool exclut délibérément
les deux â il s'agit d'un outil autonome d'exĂ©cution de code dont le point d'extension est
le registre de fonctions hĂŽtes. Voir Coding Agent pour
CodeAct.
Exemple
examples/monty_python_code_tool exécute un LlmAgent avec un
MontyPythonCodeTool en mode REPL configuré avec un montage en lecture-écriture, une variable
d'environnement et une fonction hĂŽte enregistrĂ©e â illustrant la persistance des variables
sur plusieurs tours et les appels de fonctions hÎtes depuis du Python écrit par le modÚle.