Ejecución de código Python (Monty)
ADK-Rust ejecuta Python escrito por el modelo en proceso mediante el intérprete Pydantic Monty: sin contenedor, sin subproceso y con un inicio en microsegundos. La capacidad se distribuye en dos capas:
adk-code(funcionalidadembedded-python):MontyExecutorBuildery los dos productos ejecutores,MontyOneShotExecutoryMontyReplExecutor, ambos implementandoCodeExecutor.adk-tool(funcionalidadcode-embedded-python):MontyPythonCodeTool(monty_python_code), la herramienta orientada al agente sobre esos ejecutores.
Esto complementa a PythonCodeTool con respaldo de contenedor (python_code),
que ejecuta CPython completo en Docker. Úsalo cuando los scripts necesiten el
ecosistema real de Python (paquetes de pip, extensiones de C y la biblioteca
estándar completa). Monty implementa un subconjunto de Python a cambio de
velocidad en proceso, estado del intérprete serializable y una garantía de
ausencia de red y subprocesos que se mantiene por construcción.
[dependencies]
adk-tool = { version = "2.1.0", features = ["code-embedded-python"] }
O mediante el crate general:
[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "code-embedded-python"] }
Ejecución única frente a REPL
Un generador produce ambos productos; el modo está codificado en el tipo, no en una bandera:
| Modo | Compilación | Estado | Concurrencia |
|---|---|---|---|
| Una sola ejecución | build_one_shot() | Intérprete nuevo en cada llamada | Compatible con la concurrencia |
| REPL | build_repl() | Las variables, funciones e importaciones persisten entre llamadas | Las llamadas se serializan por sesión |
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()?;
El ejecutor de REPL almacena el intérprete serializado entre llamadas. Monty
preserva la sesión mediante excepciones a nivel de Python, por lo que un fragmento
fallido no destruye el estado acumulado. Los métodos de ciclo de vida de
CodeExecutor gestionan la sesión: start() la inicializa, stop() la descarta, restart() la
restablece, y execute() la inicializa de forma diferida antes de start().
Modelo de seguridad
El aislamiento combina una política explícita con la aplicación mediante omisión:
- Sistema de archivos. Solo se puede acceder a los directorios concedidos mediante
allow_path, cada uno en modo de solo lectura o lectura y escritura, a través depathlib.Pathcontra la ruta de montaje virtual. La tabla de montajes de Monty impone el límite (canonicalización + detección de escapes mediante enlaces simbólicos). Cualquier otra ruta genera unOSErrorque se puede capturar (las comprobaciones de existencia devuelvenFalse). - Entorno.
os.getenv/os.environsolo leen el mapa explícito concedido durante la construcción; el entorno del proceso anfitrión nunca queda expuesto. - Reloj.
date.today()/datetime.now()funcionan únicamente cuando se concedió.system_clock(); de lo contrario, generanOSError. - Red y subprocesos. Monty no ofrece ninguna superficie para ninguno de los dos, por lo que son imposibles independientemente de la configuración.
- Tiempos de espera.
SandboxPolicy::timeoutse asigna aResourceLimits::max_durationde Monty (preempción real dentro de la VM, por llamada). Un límite de memoria (256 MiB de forma predeterminada) limita el montón; en modo REPL limita el montón acumulado de la sesión.
Política de concesiones frente a solicitudes. Las concesiones del constructor son el acceso máximo que puede tener cualquier script. SandboxPolicy por solicitud solo puede restringirse dentro de esos límites; una solicitud que exceda las concesiones se rechaza de forma segura ante fallos, con ExecutionError::UnsupportedPolicy indicando el exceso, antes de que se ejecute cualquier código.
Una concesión cubre todo su subárbol de directorios: solicitar un punto de montaje concedido o cualquier subdirectorio de este tiene éxito, y el punto de montaje efectivo es la ruta solicitada respaldada por el subdirectorio correspondiente del host. Usa granted_policy() para solicitar exactamente lo que ofrece el ejecutor.
La política efectiva de una sesión REPL no debe variar entre llamadas; una llamada cuya política difiera de la política establecida de la sesión se rechaza, con instrucciones para restart().
Funciones del host
Las funciones de Rust registradas (síncronas o asíncronas) se convierten en funciones de Python invocables, visibles para los scripts por su nombre sin prefijo:
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()?;
Para la forma completa del trait, implementa HostFunction (name, description, signature opcional para la solicitud de LLM, y call asíncrono con argumentos posicionales y de palabra convertidos mediante JSON). La validación del registro tiene lugar en build_*(): los nombres deben ser identificadores de Python válidos, únicos y no deben entrar en conflicto con las funciones integradas de Python.
Dentro de un script, las funciones del host se llaman de forma síncrona, nunca con await. Un Err devuelto se convierte en una excepción de Python que se puede capturar y que contiene el mensaje; una llamada a un nombre no registrado genera una excepción correctiva que enumera los nombres registrados. La ejecución de las funciones del host tiene su propio límite de tiempo de reloj de pared (host_function_timeout, 30 s de forma predeterminada), por lo que una función bloqueada no puede bloquear execute().
Nota: las funciones del host se ejecutan como código del host. Constituyen el propio límite de confianza del usuario, no el de Monty; el entorno aislado del intérprete no contiene sus efectos secundarios.
Ejecutores autodescriptivos
Ambos ejecutores implementan CodeExecutor::prompt_snippet(), mostrando sus capacidades
compiladas: semántica del modo, raíces del sistema de archivos con niveles de
acceso, nombres de variables de entorno (los valores nunca se muestran),
disponibilidad del reloj, la garantía de no usar red ni subprocesos, el contrato
de salida y un bloque de stub de Python para las funciones de host registradas.
MontyPythonCodeTool añade el fragmento a su descripción orientada a LLM, por lo que el
prompt y el comportamiento dentro del intérprete se derivan de la misma
configuración y no pueden desviarse.
MontyPythonCodeTool
La herramienta orientada al agente (monty_python_code, ámbito code:execute) refleja
JavaScriptCodeTool: JSON de error como información, claves de salida en camelCase y
un recurso alternativo "rejected" estructurado cuando la funcionalidad está
deshabilitada.
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() crea una herramienta completamente aislada para una sola ejecución;
MontyPythonCodeTool::repl(), una herramienta REPL completamente aislada.
Ámbito de la sesión
En el modo REPL, las sesiones del intérprete se identifican mediante la
identidad completa de la sesión ADK: nombre de la aplicación, ID de usuario
e ID de sesión, por lo que el estado nunca se filtra entre usuarios, incluso
cuando las cadenas de ID de sesión se repiten entre usuarios. Todas las
sesiones comparten las mismas concesiones y el mismo registro de funciones de
host; únicamente el estado del intérprete es específico de cada sesión.
El mapa de sesiones está limitado por un tope LRU (max_sessions, 100 de forma
predeterminada; 0 se trata como 1); la siguiente llamada de una sesión
expulsada inicia de forma transparente un intérprete nuevo.
Argumentos de la herramienta
| Argumento | Tipo | Descripción |
|---|---|---|
code | cadena (obligatorio) | Código fuente de Python que se ejecutará |
input | cualquiera | Valor opcional de JSON vinculado a la variable input |
timeout_secs | entero | Presupuesto de tiempo del intérprete (30 de forma predeterminada, limitado entre 1 y 300) |
reset | booleano | Solo en modo REPL: descarta la sesión persistente antes de ejecutar |
Envoltorio de salida
{ "status": "success", "stdout": "", "stderr": "", "output": {"n": 42},
"stdoutTruncated": false, "stderrTruncated": false, "durationMs": 3 }
No hay exitCode: la ejecución se realiza en el mismo proceso, no se inicia ningún proceso;
status es la señal de éxito o fracaso. stdoutTruncated / stderrTruncated
indican cuándo la salida capturada se truncó al alcanzar el límite de bytes de la política del entorno aislado (1 MB
cada uno de forma predeterminada).
El valor de la expresión final del script se devuelve como output; la
salida de print() se captura como stdout. Estados de fallo: "failed" (excepción de Python:
el seguimiento de pila está en stderr, incluidas las excepciones provocadas por las funciones del
host), "timeout" (se superó el presupuesto de tiempo), "rejected" (argumentos incorrectos o
función deshabilitada). Nunca hay un ToolError.
El envoltorio es fijo en ambos modos, por lo que la herramienta lo declara mediante
Tool::response_schema(); los proveedores que exponen esquemas de respuesta lo reciben
en la declaración de la herramienta junto con parameters.
Relación con CodeAct
La ruta de CodeActAgent + adk-codeact-monty también ejecuta Python mediante Monty,
pero con despacho de ADK Tool desde el interior de los scripts (call_tool(...)) y
suspensión/reanudación entre turnos del agente. MontyPythonCodeTool excluye deliberadamente
ambos: es una herramienta de ejecución de código autónoma cuya interfaz de extensibilidad es
el registro de funciones del host. Consulta Agente de programación para
CodeAct.
Ejemplo
examples/monty_python_code_tool ejecuta un LlmAgent con un MontyPythonCodeTool en modo REPL
configurado con un montaje de lectura y escritura, una variable de entorno y una función del host
registrada, lo que demuestra la persistencia de variables entre varios turnos y las llamadas a
funciones del host desde Python escrito por el modelo.