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 :

  1. Le modÚle émet un bloc de code délimité (un script).
  2. 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.
  3. Le script renvoie un ScriptOutput marqué :
    • 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_config ainsi que les raccourcis temperature/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 et toolsets par invocation ; tool_timeout, default_retry_budget/tool_retry_budget, circuit_breaker_threshold, ainsi que les solutions de repli on_tool_error.
  • Autorisation : ToolConfirmationPolicy (require_tool_confirmation/require_tool_confirmation_for_all).
  • Transfert : sub_agents et disallow_transfer_to_parent/ disallow_transfer_to_peers.
  • Sortie : output_key, output_schema/output_type avec une boucle de correction et de nouvelle tentative (output_max_retries).
  • Rappels : before_callback/after_callback, before_model_callback/after_model_callback, et before_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 via CallbackContext::tool_outcome().
  • ConditionnĂ© par fonctionnalitĂ© : garde-fous d’entrĂ©e/sortie (guardrails) et le pipeline EnhancedPlugin (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_strategy parallĂšle.
  • Il n’existe pas d’option de construction skip_summarization — le modĂšle termine lui-mĂȘme la boucle via final_result — bien qu’un outil qui dĂ©finisse skip_summarization sur 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 */ }
}
  • RunStep est un ensemble de variantes de structure — Call { call, stdout }, Complete { value, stdout } et Raised { message, stdout }. Construisez-les avec les assistants RunStep::call / RunStep::complete / RunStep::raised et associez la sortie capturĂ©e avec .with_stdout(..). RunStep::Call expose exactement un appel en attente ; reprenez-le avec une valeur ou une erreur, ou dump() sa continuation pour le suspendre. Le stdout attachĂ© 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 PendingCall transmet ses arguments tels que l’interprĂ©teur les a produits — positional_args() et keyword_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 via adk_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, et render_tools peut ĂȘ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). RuntimeError est 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_suspension doit ĂȘtre true pour permettre le HITL et le report des opĂ©rations de longue durĂ©e ; prompt dĂ©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 / et joinpath(), 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.

CodeActAgent (CodeAct) - Documentation ADK-Rust | ADK-Rust