CodeActAgent (CodeAct)
CodeActAgent est l’équivalent de LlmAgent qui agit en écrivant et
en exécutant du code au lieu d’émettre un appel d’outil à la fois. À chaque tour, le modèle
produit un script unique ; les outils sont exposés sous forme de fonctions appelables que le script peut
composer ; et le script communique son résultat en renvoyant une valeur marquée.
Il s’agit du modèle CodeAct : plutôt que call tool A → observe → call tool B,
le modèle écrit b(a(x)) dans un seul script, de sorte que le travail en plusieurs étapes s’effectue en un seul
tour. Il est activé par la fonctionnalité codeact de adk-agent.
Quand l’utiliser
- Tâches qui enchaînent ou combinent plusieurs outils par tour (manipulation de données, opérations par lots, logique de raccordement).
- Modèles ayant fait l’objet d’un post-entraînement pour la génération de code.
- Flux de travail dans lesquels un véritable interpréteur (par ex. Python) est disponible comme substrat d’action.
Pour les appels d’outils natifs, préférez LlmAgent. Pour un environnement de
codage avec fichiers et shell en bac à sable, consultez l’Agent de codage.
Fonctionnement de la boucle
À chaque tour :
- Le modèle émet un bloc de code délimité (un script).
- Le script s’exécute sur un [
CodeRuntime] ; les appels d’outils remontent jusqu’à l’hôte, qui exécute l’outil et reprend le script avec le résultat. - Le script renvoie un
ScriptOutputmarqué :observation— renvoyé au modèle ; la boucle continue.error— renvoyé sous forme de message ; la boucle continue.final_result— renvoyé à l’appelant ; la boucle se termine.transfer_to_agent— transmet le contrôle à un autre agent ; la boucle se termine.
Le framework est indépendant du langage : le trait CodeRuntime constitue l’interface de l’interpréteur
pas à pas, et il indique lui-même son langage et son environnement au modèle via une invite
en texte libre. L’adaptateur de production prévu encapsule
Monty, un interpréteur Python natif en Rust.
Durabilité : suspendre et reprendre
CodeActAgent est sans état entre les invocations — l’état durable réside dans la
session, exactement comme pour LlmAgent. Deux situations suspendent
l’exécution :
- un outil soumis à confirmation, pour lequel aucune décision n’a encore été prise (HITL), et
- un outil de longue durée dont le résultat arrive hors bande.
Lors de la suspension, la continuation de l’interpréteur en cours est sérialisée dans un
CodeActCheckpoint et écrite dans l’état de la session ; le run() suivant la
relit et reprend l’exécution — la décision de confirmation arrive via
RunConfig::tool_confirmation_decisions, et le résultat d’une opération de longue durée arrive sous la forme d’un
FunctionResponse dans le message suivant. Les appels d’outils inline sont encadrés par des points de contrôle
write-ahead (SAVE-BEFORE) et SAVE-AFTER : une fois le point de contrôle SAVE-AFTER
persisté, la récupération reprend avec le résultat enregistré et n’exécute jamais
à nouveau l’outil. Un plantage dans la courte fenêtre suivant l’effet secondaire d’un outil, mais
avant l’enregistrement de son point de contrôle SAVE-AFTER, entraînera la
réexécution de l’outil lors de la récupération ; les outils qui ne sont pas idempotents
doivent donc s’en prémunir (la même limite « au moins une fois » que
LlmAgent).
Cela nécessite un environnement d’exécution capable de prendre un instantané d’un appel en pause. Un environnement d’exécution qui ne le peut pas exécute les outils de longue durée en ligne et refuse les pauses de confirmation.
Création d’un CodeActAgent
use adk_agent::codeact::CodeActAgent;
use std::sync::Arc;
// `model` implements `adk_core::Llm`; `runtime` implements `CodeRuntime`.
let agent = CodeActAgent::builder()
.name("analyst")
.model(model)
.runtime(runtime)
.instruction("Prefer concise, composable steps.")
.tool(Arc::new(load_csv_tool))
.output_key("report")
.build()?;
model et runtime sont requis ; tout le reste possède une valeur par défaut.
Parité avec LlmAgent
Le générateur reflète LlmAgentBuilder :
- Modèle :
generate_content_configainsi que les raccourcistemperature/top_p/top_k/max_output_tokens. - Instructions :
instruction/instruction_provider,global_instruction/global_instruction_provider, avec injection de modèle{state.key}; ainsi que les compétences (fonctionnalitéskills). - Historique :
include_contents. - Outils :
tools statiques ettoolsets par invocation ;tool_timeout,default_retry_budget/tool_retry_budget,circuit_breaker_threshold, ainsi que les solutions de replion_tool_error. - Autorisation :
ToolConfirmationPolicy(require_tool_confirmation/require_tool_confirmation_for_all). - Transfert :
sub_agents etdisallow_transfer_to_parent/disallow_transfer_to_peers. - Sortie :
output_key,output_schema/output_typeavec une boucle de correction et de nouvelle tentative (output_max_retries). - Rappels :
before_callback/after_callback,before_model_callback/after_model_callback, etbefore_tool_callback/after_tool_callback/after_tool_callback_full. Les rappels après l’appel d’un outil peuvent inspecter les métadonnées d’exécution structurées viaCallbackContext::tool_outcome(). - Conditionné par fonctionnalité : garde-fous d’entrée/sortie (
guardrails) et le pipelineEnhancedPlugin(enhanced-plugins).
Chaque appel d’outil reçoit un nouveau ToolContext qui transporte l’identifiant d’appel de l’interpréteur
et délègue les artefacts, la mémoire, l’état partagé, les portées utilisateur et les secrets à l’invocation active — ainsi, un outil se comporte de manière identique sous CodeActAgent ou LlmAgent.
Différences délibérées
- La mise en bac à sable de l’exécution de code relève de la responsabilité du
CodeRuntime, et ne constitue pas un ajout rapporté. - La distribution des outils est séquentielle par conception (une continuation unique est capturée à une seule limite d’appel), il n’y a donc pas de
tool_execution_strategyparallèle. - Il n’existe pas d’option de construction
skip_summarization— le modèle termine lui-même la boucle viafinal_result— bien qu’un outil qui définisseskip_summarizationsur ses actions termine tout de même l’exécution.
Exemple
Une démonstration de bout en bout exécutable et sans dépendances — un CodeRuntime autonome ainsi qu’un modèle déterministe — se trouve dans
examples/codeact_agent:
cargo run --manifest-path examples/codeact_agent/Cargo.toml
Implémentation d’un CodeRuntime
Un CodeRuntime analyse et exécute un script par étapes, en exposant un appel externe à la fois :
pub trait CodeRuntime: Send + Sync {
fn start(&self, script: &str, script_name: &str) -> Result<RunStep, RuntimeError>;
fn resume(&self, snapshot: &[u8], with: ResumeWith) -> Result<RunStep, RuntimeError>;
fn capabilities(&self) -> RuntimeCapabilities { /* default */ }
fn render_tools(&self, tools: &[Arc<dyn Tool>]) -> String { /* default */ }
}
RunStepest un ensemble de variantes de structure —Call { call, stdout },Complete { value, stdout }etRaised { message, stdout }. Construisez-les avec les assistantsRunStep::call/RunStep::complete/RunStep::raisedet associez la sortie capturée avec.with_stdout(..).RunStep::Callexpose exactement un appel en attente ; reprenez-le avec une valeur ou une erreur, oudump()sa continuation pour le suspendre. Lestdoutattaché par un environnement d’exécution est renvoyé au modèle et conservé dans les points de contrôle, de sorte qu’il survive à la suspension et à la reprise.- Un
PendingCalltransmet ses arguments tels que l’interpréteur les a produits —positional_args()etkeyword_args()séparément. Ne faites pas correspondre vous-même les arguments positionnels aux noms : le pilote les associe aux paramètres de l’outil de manière centralisée viaadk_agent::codeact::bind_call_args; un environnement d’exécution n’a donc pas besoin de schéma d’outil à la limite de l’appel, etrender_toolspeut être une fonction pure de la tranche d’outils. - Erreurs du script et erreurs de l’hôte. Tout ce que le modèle pourrait corriger en écrivant un
code différent — une erreur de syntaxe ou d’analyse, une exception non interceptée, une
annulation due à une limite de ressources — est une
RunStep::Raised(une chaîne opaque renvoyée au modèle textuellement).RuntimeErrorest réservé aux véritables défaillances de l’hôte (sérialisation ou désérialisation d’un instantané, erreurs internes de l’interpréteur) et interrompt l’exécution. RuntimeCapabilities::supports_suspensiondoit êtretruepour permettre le HITL et le report des opérations de longue durée ;promptdécrit le langage et l’environnement au modèle.
Voir examples/codeact_agent/src/runtime.rs pour une implémentation complète et minimale
prenant en charge la suspension et la reprise.
Python avec Monty
L’adaptateur de production prévu est
adk-codeact-monty,
un CodeRuntime reposant sur Pydantic Monty. Il
permet au modèle d’agir en écrivant du Python, s’exécute dans le même processus,
sans conteneur ni sous-processus, et capture une exécution en pause sous forme
d’octets — exactement ce dont la suspension/reprise a besoin. L’interpréteur Monty
est utilisé via le noyau embedded-python de adk-code,
qui regroupe les crates monty en un seul endroit ; rustc 1.95+ est requis.
[dependencies]
adk-agent = { version = "2.1.0", features = ["codeact"] }
adk-codeact-monty = "2.1.0"
Ou via la crate parapluie (réexportée sous le nom de adk_rust::codeact_monty) :
[dependencies]
adk-rust = { version = "2.1.0", features = ["minimal", "codeact-monty"] }
use adk_codeact_monty::MontyRuntime;
// Conservative default resource limits (per-advance time + memory caps) make
// `new()` safe for untrusted, LLM-generated code.
let runtime = Arc::new(MontyRuntime::new());
// Tighten or relax with the builder; `unlimited()` removes the caps for
// trusted scripts only.
let runtime = Arc::new(
MontyRuntime::builder()
.max_duration(std::time::Duration::from_secs(2))
.max_memory(64 * 1024 * 1024)
.build(),
);
Accès au système d’exploitation
Les effets sur le système d’exploitation qu’un script tente d’effectuer — lectures/écritures
du système de fichiers, os.getenv/os.environ et date.today()/datetime.now() — sont traités
sur place conformément à une politique contrôlée par l’hôte. Ce ne sont pas des
outils et ils ne mettent jamais en pause la boucle de l’agent. Par défaut, un runtime est
entièrement isolé (aucun accès au système de fichiers, environnement vide, horloge de
l’hôte activée). Accordez des accès spécifiques avec le builder :
use adk_codeact_monty::{MontyRuntime, PathAccess};
let runtime = Arc::new(
MontyRuntime::builder()
// Mount host directories at virtual paths; Monty enforces the boundary
// (canonicalization + symlink-escape detection) so a script can never
// escape a mount. Reads/writes outside every mount raise PermissionError.
.allow_path("/data", "/srv/agent/data", PathAccess::ReadOnly)
.allow_path("/out", "/srv/agent/out", PathAccess::ReadWrite)
// Expose an explicit environment map to os.getenv / os.environ. Empty by
// default — the host process environment is never exposed implicitly.
.environ_var("PROJECT", "acme")
// date.today() / datetime.now() read the host clock (enabled by default).
.system_clock(true)
.build(),
);
L’accès au réseau et aux sous-processus ne dispose d’aucune surface d’appel système Monty et reste indisponible quelle que soit la politique. Les accès accordés sont décrits au modèle dans le prompt système, afin qu’il sache quels chemins il peut lire ou écrire et quelles variables d’environnement existent.
Monty n’implémente qu’un sous-ensemble de pathlib.Path ; lorsque des chemins sont montés,
le prompt répertorie les méthodes exactement prises en charge (toute autre méthode
déclenche AttributeError) :
- Lecture/requête (tout montage) :
exists(),is_file(),is_dir(),is_symlink(),read_text(),read_bytes(),stat(),iterdir(),resolve(),absolute(),open("r"). - Écriture (montages en lecture-écriture uniquement) :
write_text(),write_bytes(),append_text(),append_bytes(),mkdir(),unlink(),rmdir(),rename(),open("w")/open("a"). - Opérations pures sur les chemins (sans E/S) : l’opérateur
/etjoinpath(),is_absolute(),with_name(),with_stem(),with_suffix(),as_posix(), ainsi que les propriétés.name,.parent,.stem,.suffix,.suffixes,.parts.
Les outils sont invoqués au moyen d’une unique fonction intégrée,
call_tool("name", {"arg": value, ...}) — le seul moyen d’appeler un outil ; ils ne sont
jamais accessibles comme fonctions appelables isolées. Le nom de l’outil est une chaîne littérale et chaque
argument est une entrée indexée par une chaîne dans un dictionnaire, de sorte que le véritable nom est
transporté dans la continuation sérialisée (et conservé lors de la suspension et de la reprise, sans table de noms
côté hôte), un outil ainsi que chaque argument pouvant porter n’importe quel nom (pas nécessairement un
identifiant Python valide comme "fetch-cart", un mot-clé Python, ni même "call_tool"), et le pilote associe
exactement les entrées du dictionnaire par leur nom — sans déduction positionnelle. Chaque outil apparaît dans
l’invite sous la forme d’une ligne d’utilisation call_tool("name", {...}) avec ses paramètres et sa
description. Toute autre forme que celle-ci — un fetch_cart(...) isolé, des
arguments nommés, un argument qui n’est pas un dictionnaire ou une clé qui n’est pas une chaîne — est refusée
avec une erreur corrective plutôt que distribuée silencieusement ; le modèle n’a donc qu’une seule forme d’appel
à apprendre.
L’élément exécutable
examples/codeact_monty_agent
exécute un CodeActAgent avec du Python réel, entièrement hors ligne.