CodeActAgent (CodeAct)
CodeActAgent は LlmAgent と対になるもので、ツール呼び出しを一度に 1 つずつ生成するのではなく、コードを記述して実行することで動作します。各ターンでモデルは 1 つのスクリプトを生成します。ツールはスクリプトが組み合わせて呼び出せる関数として公開され、スクリプトはタグ付きの値を返すことで結果を伝えます。
これは CodeAct パターンです。call tool A → observe → call tool B の代わりに、モデルが 1 つのスクリプト内で b(a(x)) を記述するため、複数ステップの作業を 1 ターンで実行できます。これは adk-agent の codeact 機能によって有効になります。
使用するタイミング
- 1 ターンで複数のツールを連鎖または組み合わせるタスク(データ加工、バッチ処理、接着ロジック)。
- コード生成向けに追加学習されたモデル。
- 実際のインタープリター(例:Python)をアクションの基盤として利用できるワークフロー。
ネイティブなツール呼び出しには、LlmAgent を推奨します。サンドボックス化されたファイル/シェルのコーディングハーネスについては、コーディングエージェント を参照してください。
ループの仕組み
各ターンで次の処理を行います。
- モデルが 1 つのフェンス付きコードブロック(スクリプト)を出力します。
- スクリプトが [
CodeRuntime] 上で実行されます。ツール呼び出しはホストに通知され、ホストがツールを実行した後、結果を伴ってスクリプトを再開します。 - スクリプトがタグ付きの
ScriptOutputを返します。observation— モデルに送り返され、ループが続行されます。error— メッセージとして送り返され、ループが続行されます。final_result— 呼び出し元に返され、ループが終了します。transfer_to_agent— 制御を別のエージェントに渡し、ループが終了します。
このフレームワークは 言語に依存しません。CodeRuntime トレイトがステップごとのインタープリター境界となり、自由形式のプロンプトを介して自身の言語/環境をモデルに報告します。本番環境向けのアダプターは、Rust ネイティブの Python インタープリターである Monty をラップするものを想定しています。
永続性:一時停止と再開
CodeActAgent は呼び出し間でステートレスです。永続的な状態は、LlmAgent とまったく同様に セッション に保持されます。実行が 一時停止 する状況は 2 つあります。
- まだ判断が下されていない、確認によってゲートされたツール(HITL)
- 結果が帯域外で到着する、長時間実行されるツール
一時停止時には、実行中のインタープリター継続情報がシリアライズされて CodeActCheckpoint となり、セッション状態に書き込まれます。次の run() がそれを読み戻して再開します。確認の判断は RunConfig::tool_confirmation_decisions を介して届き、長時間実行されるツールの結果は、次のメッセージ内で FunctionResponse として届きます。インラインのツール呼び出しは、先行書き込み(SAVE-BEFORE)チェックポイントと SAVE-AFTER チェックポイントで囲まれます。SAVE-AFTER チェックポイントが永続化されると、復旧時には保存された結果から再開され、ツールが再実行されることはありません。ツールの副作用が発生した後、その SAVE-AFTER チェックポイントが反映される前の狭い時間枠でクラッシュすると、復旧時にツールが再実行されます。そのため、べき等でないツールはこれに備える必要があります(LlmAgent と同じ、少なくとも 1 回の実行境界です)。
これには、一時停止した呼び出しのスナップショットを取得できるランタイムが必要です。それができないランタイムでは、長時間実行されるツールをインラインで実行し、確認による一時停止を拒否します。
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 と runtime は必須です。それ以外はすべてデフォルト値が設定されます。
LlmAgent との同等性
ビルダーは LlmAgentBuilder を反映しています。
- モデル:
generate_content_configおよびtemperature/top_p/top_k/max_output_tokensの短縮表記。 - 指示:
instruction/instruction_provider、global_instruction/global_instruction_provider、{state.key}テンプレートインジェクション、さらにスキル(skills機能)。 - 履歴:
include_contents。 - ツール: 静的な
tools と呼び出しごとのtoolsets;tool_timeout、default_retry_budget/tool_retry_budget、circuit_breaker_threshold、およびon_tool_errorフォールバック。 - 認可:
ToolConfirmationPolicy(require_tool_confirmation/require_tool_confirmation_for_all)。 - 転送:
sub_agents およびdisallow_transfer_to_parent/disallow_transfer_to_peers。 - 出力:
output_key、output_schema/output_typeと、 修正・再試行ループ(output_max_retries)。 - コールバック:
before_callback/after_callback、before_model_callback/after_model_callback、およびbefore_tool_callback/after_tool_callback/after_tool_callback_full。 ツール実行後のコールバックでは、CallbackContext::tool_outcome()を介して 構造化された実行メタデータを検査できます。 - 機能ゲート: 入出力ガードレール(
guardrails)およびEnhancedPluginパイプライン(enhanced-plugins)。
各ツール呼び出しには、インタープリターの呼び出し ID を保持する新しい ToolContext が割り当てられ、
アーティファクト、メモリ、共有状態、ユーザースコープ、シークレットを実行中の呼び出しに委譲します。そのため、ツールは CodeActAgent または LlmAgent の下でも同じように動作します。
意図的な相違点
- コード実行のサンドボックス化は
CodeRuntimeの責任であり、 後付けの機能ではありません。 - ツールのディスパッチは 設計上、逐次的 です(単一の継続が 1 回の呼び出し境界でスナップショットされるため)ので、並列の
tool_execution_strategyはありません。 skip_summarizationビルダーオプションはありません。モデルはfinal_resultを介して自らループを終了します。ただし、アクションにskip_summarizationを設定するツールは実行を終了させます。
例
実行可能で依存関係のないエンドツーエンドデモ(自己完結型の CodeRuntime と
決定論的なモデル)は、次の場所にあります。
examples/codeact_agent:
cargo run --manifest-path examples/codeact_agent/Cargo.toml
CodeRuntimeの実装
CodeRuntimeはスクリプトを解析してステップ実行し、一度に1つの外部呼び出しを公開します。
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は構造体バリアントの集合です —Call { call, stdout }、Complete { value, stdout }、およびRaised { message, stdout }。これらはRunStep::call/RunStep::complete/RunStep::raisedヘルパーで構築し、.with_stdout(..)で取得した出力を付加します。RunStep::Callは 保留中の呼び出しを正確に1つ公開します。値またはエラーで再開するか、 継続をdump()して中断します。ランタイムが付加するstdoutは モデルに返され、チェックポイントに永続化されるため、中断と再開をまたいで保持されます。PendingCallは、インタープリターが生成した形式で引数を報告します —positional_args()とkeyword_args()を別々に報告します。位置引数を自分で 名前に対応付けてはいけません。ドライバーがadk_agent::codeact::bind_call_argsを介して ツールのパラメーターに一元的にバインドするため、ランタイムは呼び出し境界でツール スキーマを必要とせず、render_toolsはツールスライスだけの純粋な関数にできます。- スクリプトエラーとホストエラー。 モデルが異なるコードを書くことで修正できるもの —
構文/解析エラー、捕捉されない例外、リソース制限によるキャンセル — は
RunStep::Raisedです(モデルにそのまま返される不透明な文字列)。RuntimeErrorは 真のホスト障害(スナップショットのシリアライズ/デシリアライズ、インタープリター内部エラー)のために 予約されており、実行を中止します。 - HITLおよび長時間実行の延期を可能にするには、
RuntimeCapabilities::supports_suspensionをtrueにする必要があります。promptは言語/環境をモデルに説明します。
中断と再開をサポートする完全かつ最小限の実装については、examples/codeact_agent/src/runtime.rsを参照してください。
Monty による Python
本番環境で使用するアダプターは
adk-codeact-monty、
Pydantic Montyを基盤とするCodeRuntimeです。モデルは
Python を記述することで動作でき、コンテナやサブプロセスなしでプロセス内実行され、
一時停止した実行をバイト列にスナップショットできます。これは suspend/resume に
必要な機能そのものです。Monty インタープリターは adk-code の embedded-python
カーネルを通じて利用され、monty クレートを一箇所で固定します。rustc 1.95 以降が必要です。
[dependencies]
adk-agent = { version = "2.1.0", features = ["codeact"] }
adk-codeact-monty = "2.1.0"
または、アンブレラ・クレート経由でも利用できます(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(),
);
OS へのアクセス
スクリプトが試みるオペレーティングシステムの操作(ファイルシステムの読み取り/書き込み、
os.getenv/os.environ、および date.today()/datetime.now())は、ホストが制御する
ポリシーに基づいてその場で処理されます。これらはツールではなく、エージェントループを
一時停止することもありません。デフォルトではランタイムは完全にサンドボックス化されます
(ファイルシステムへのアクセスなし、環境は空、ホスト時計は有効)。ビルダーを使用して特定のアクセスを許可します。
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(),
);
ネットワークおよびサブプロセスへのアクセスには Monty の OS 呼び出しインターフェースがなく、 ポリシーに関係なく利用できません。許可されたアクセスはシステムプロンプトでモデルに説明されるため、 読み取りまたは書き込みが可能なパスと、存在する環境変数をモデルが把握できます。
Monty が実装するのは pathlib.Path のサブセットのみです。そのため、パスをマウントすると、
プロンプトにはサポートされるメソッドが正確に一覧表示されます(その他のメソッドは AttributeError を発生させます)。
- 読み取り/クエリ(任意のマウント):
exists(),is_file(),is_dir(),is_symlink(),read_text(),read_bytes(),stat(),iterdir(),resolve(),absolute(),open("r"). - 書き込み(読み書き可能なマウントのみ):
write_text(),write_bytes(),append_text(),append_bytes(),mkdir(),unlink(),rmdir(),rename(),open("w")/open("a"). - 純粋なパス操作(I/O なし):
/演算子、およびjoinpath(),is_absolute(),with_name(),with_stem(),with_suffix(),as_posix(),.name,.parent,.stem,.suffix,.suffixes,.partsプロパティ。
ツールは単一の組み込み関数 call_tool("name", {"arg": value, ...}) を通じて呼び出されます。これはツールを呼び出す唯一の方法であり、単独の呼び出し可能オブジェクトとしてスコープ内に置かれることはありません。ツール名は文字列リテラルであり、すべての引数は 1 つの辞書内の文字列キー付きエントリです。そのため、実際の名前はシリアライズされた継続の内部に保持されます(ホスト側の名前テーブルなしで一時停止/再開後も保持されます)。また、ツールおよび各引数には任意の名前を付けることができます("fetch-cart" のような有効な Python 識別子でなくても、Python のキーワードであっても、さらには "call_tool" であっても構いません)。ドライバーは辞書のエントリを名前どおりに正確にバインドし、位置による推論は行いません。各ツールは、パラメーターと説明を含む call_tool("name", {...}) 使用行としてプロンプトに表示されます。この 1 つの形式以外のもの — 単独の fetch_cart(...)、キーワード引数、辞書でない引数、または文字列でないキー — は、暗黙的にディスパッチされるのではなく、修正を促すエラーとして拒否されます。そのため、モデルが学習すべき呼び出し形式は正確に 1 つだけです。
実行可能な
examples/codeact_monty_agent
は、実際の Python に対して完全にオフラインで CodeActAgent を実行します。