ランナー
adk-runner の実行ランタイムであり、エージェントの実行をオーケストレーションします。
概要
Runner は、エージェント実行のライフサイクル全体を管理します。
- セッション管理(セッションの作成と取得)
- メモリの注入(関連するメモリの検索と注入)
- アーティファクト処理(スコープ付きアーティファクトアクセス)
- イベントストリーミング(イベントの処理と転送)
- エージェントの転送(マルチエージェントのハンドオフ処理)
インストール
[dependencies]
adk-runner = "2.1.0"
RunnerConfig
必要なサービスを指定してランナーを設定します。
use adk_runner::{Runner, RunnerConfig};
use adk_session::InMemorySessionService;
use adk_artifact::InMemoryArtifactService;
use std::sync::Arc;
let config = RunnerConfig {
app_name: "my_app".to_string(),
agent: Arc::new(my_agent),
session_service: Arc::new(InMemorySessionService::new()),
artifact_service: Some(Arc::new(InMemoryArtifactService::new())),
memory_service: None,
plugin_manager: None,
run_config: None,
compaction_config: None,
context_cache_config: None,
cache_capable: None,
request_context: None,
cancellation_token: None,
};
let runner = Runner::new(config)?;
RunnerConfigBuilder(推奨)
typestateビルダーを使用して Runner を構築します。ビルダーはコンパイル時に必須フィールドを適用し、すべてのオプションフィールドにデフォルト値を設定するため、将来のリリースで新しいフィールドが追加されてもコードが壊れることはありません。
use adk_runner::Runner;
let runner = Runner::builder()
.app_name("my_app")
.agent(Arc::new(my_agent))
.session_service(Arc::new(InMemorySessionService::new()))
// Optional fields — only set what you need
.artifact_service(Arc::new(InMemoryArtifactService::new()))
.build()?;
ビルダーには、app_name、agent、session_service の3つのフィールドが必要です。それ以外はすべてオプションで、適切なデフォルト値が設定されます。build() メソッドは、3つの必須フィールドがすべて設定された後でのみ使用できます。いずれか1つでも不足している場合は、実行時エラーではなくコンパイル時エラーになります。
設定フィールド
| フィールド | 型 | 必須 | 説明 |
|---|---|---|---|
app_name | String | はい | アプリケーション識別子 |
agent | Arc<dyn Agent> | はい | 実行するルートエージェント |
session_service | Arc<dyn SessionService> | はい | セッションストレージバックエンド |
artifact_service | Option<Arc<dyn ArtifactService>> | いいえ | アーティファクトストレージ |
memory_service | Option<Arc<dyn Memory>> | いいえ | 長期メモリ |
plugin_manager | Option<Arc<PluginManager>> | いいえ | プラグインライフサイクルフック |
compaction_config | Option<EventsCompactionConfig> | いいえ | コンテキスト圧縮設定 |
run_config | Option<RunConfig> | いいえ | 実行オプション |
context_cache_config | Option<ContextCacheConfig> | いいえ | ランナーレベルのコンテキストキャッシュのライフサイクル(実験的 — 下記参照) |
cache_capable | Option<Arc<dyn CacheCapable>> | いいえ | キャッシュ対応モデルの参照(実験的 — 下記参照) |
request_context | Option<RequestContext> | いいえ | 認証ミドルウェアコンテキスト |
cancellation_token | Option<CancellationToken> | いいえ | 協調的キャンセル |
プロンプトキャッシュ
キャッシュはプロバイダーレベルの関心事であり、Runner の設定は必要ありません。 各 プロバイダー統合は、リクエストが組み立てられる箇所でキャッシュを処理します。
| プロバイダー | 仕組み | デフォルト |
|---|---|---|
| Anthropic / Bedrock | cache_control ブレークポイント | 有効(AnthropicConfig::prompt_caching、with_prompt_caching(false)でオプトアウト) |
| OpenAI | サーバー側のプロンプトキャッシュ、保持期間は PromptCacheRetention | 自動 |
| Gemini | 2.5/3.x での暗黙的なキャッシュ — 共有プレフィックスによって、コードを変更せずに割引が適用されます | 自動 |
キャッシュヒットは、追加の配線なしで観測できます。Gemini 統合は各レスポンスに cachedContentTokenCount を記録します。
context_cache_configとcache_capableは実験的なものであり、未設定のままにしてください。 これらは Runner から Gemini の 明示的なcachedContentsAPI を駆動します。この API では、キャッシュがsystem_instruction、tools、tool_configを 置き換える 必要があります。これらのいずれかと同時にキャッシュを送信すると、INVALID_ARGUMENTで拒否されます。Runner はエージェントがツールを解決する前にキャッシュを選択するため、そのリクエストを組み立てることができず、これらのフィールドを有効にしても現在はキャッシュヒットが発生しません。Gemini に対する、ベストエフォートではなく保証されたキャッシュは、他のプロバイダーが行っている方法と同様に、モデル統合に実装されるものです。
エージェントの実行
ユーザー入力でエージェントを実行します。
use adk_core::{Content, SessionId, UserId};
use futures::StreamExt;
let user_content = Content::new("user").with_text("Hello!");
let mut stream = runner.run(
UserId::new("user-123")?,
SessionId::new("session-456")?,
user_content,
).await?;
while let Some(event) = stream.next().await {
match event {
Ok(e) => {
if let Some(content) = e.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
}
Err(e) => eprintln!("Error: {}", e),
}
}
文字列の簡易メソッド
単純な呼び出し箇所では、run_str() はプレーンな &str 引数を受け取り、内部で newtype 変換を処理します。
let mut stream = runner.run_str(
"user-123",
"session-456",
Content::new("user").with_text("Hello!"),
).await?;
文字列が検証に失敗した場合(空である、null バイトを含む、または長さの上限を超えている場合)、run_str() はエージェントループを開始する前にエラーを返します。型付きの UserId/SessionId を使用する既存の run() メソッドは変更されません。
割り込みと実行の分離
実行は、run() がストリームを返すとすぐに登録され、そのストリームが破棄されると登録解除されます。ストリームが一度もポーリングされずに破棄された場合も同様です。
| メソッド | スコープ |
|---|---|
interrupt(session_id) | そのセッション ID に対する、アプリとユーザーをまたぐすべての進行中の実行をキャンセルします |
interrupt_identity(app_name, user_id, session_id) | 1 つの完全一致する ID に対する実行をキャンセルします |
active_runs() | 実行中のすべての実行の識別子。同じ識別子の繰り返しは並行実行を意味します |
active_session_ids() | 実行中の実行における重複を除いたセッション ID |
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");
セッション ID はアプリとユーザーの組み合わせ内でのみ一意であるため、interrupt(session_id) が広範な形式であり、interrupt_identity が正確な形式です。単一の Runner が複数のアプリまたはユーザーに対応する場合は、interrupt_identity を優先してください。
実行はセッション ID ではなく一意の実行 ID によって追跡されるため、同じ識別情報に対する 2 つの実行も個別に追跡され、それぞれが自身のみの登録を解除します。
永続化は識別情報にバインドされる
Runner が永続化するすべてのイベント(ユーザーターン、モデル応答、転送イベント、プラグインイベント、圧縮イベント)は、完全な (app_name, user_id, session_id) トリプルを使用して SessionService::append_event_for_identity 経由で書き込まれます。そのため、自然キーが複合キーである SessionService は、各イベントをそのテナントにバインドでき、raw-session-ID append_event パスを完全に拒否または無視できます。
実行フロー
┌─────────────────────────────────────────────────────────────┐
│ Runner.run() │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. Session Retrieval │
│ │
│ SessionService.get(app_name, user_id, session_id) │
│ → Creates new session if not exists │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 2. Agent Selection │
│ │
│ Check session state for active agent │
│ → Use root agent or transferred agent │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 3. Context Creation │
│ │
│ InvocationContext with: │
│ - Session (mutable) │
│ - Artifacts (scoped to session) │
│ - Memory (if configured) │
│ - Run config │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 4. Agent Execution │
│ │
│ agent.run(ctx) → EventStream │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 5. Event Processing │
│ │
│ For each event: │
│ - Update session state │
│ - Handle transfers │
│ - Forward to caller │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 6. Session Save │
│ │
│ SessionService.append_event(session, events) │
└─────────────────────────────────────────────────────────────┘
InvocationContext
実行中にエージェントへ提供されるコンテキスト:
pub trait InvocationContext: CallbackContext {
/// The agent being executed
fn agent(&self) -> Arc<dyn Agent>;
/// Memory service (if configured)
fn memory(&self) -> Option<Arc<dyn Memory>>;
/// Current session
fn session(&self) -> &dyn Session;
/// Execution configuration
fn run_config(&self) -> &RunConfig;
/// Signal end of invocation
fn end_invocation(&self);
/// Check if invocation has ended
fn ended(&self) -> bool;
}
RunConfig
実行オプション:
pub struct RunConfig {
/// Streaming mode for responses
pub streaming_mode: StreamingMode,
// ... other fields (tool_confirmation_decisions, cached_content, etc.)
}
ToolExecutionStrategy
単一の LLM 応答からの複数のツール呼び出しをどのようにディスパッチするかを制御します。
| 戦略 | 動作 |
|---|---|
Sequential(デフォルト) | LLMが返した順序でツールを1つずつ実行 |
Parallel | すべてのツールを同時に実行し、安全性は呼び出し元が担う |
Auto | 安全な読み取り専用サブセットを同時実行し、その後、残りのすべての呼び出しを順次実行する |
エージェントごとに LlmAgentBuilder で設定します。
use adk_core::ToolExecutionStrategy;
let agent = LlmAgentBuilder::new("fast_agent")
.model(model)
.tool_execution_strategy(ToolExecutionStrategy::Auto)
.tool(Arc::new(
search_tool
.with_read_only(true)
.with_concurrency_safe(true),
))
.tool(Arc::new(save_tool)) // runs after the concurrent safe subset
.build()?;
Auto モードでは、ディスパッチループが is_read_only() と is_concurrency_safe() の両方を照会します。選択されたツールが両方のメソッドで true を返す呼び出しが最初に並行実行され、その後、残りの呼び出しが順次実行されます。Parallel は明示的な呼び出し元によるオーバーライドとして、これらのメタデータチェックをバイパスします。戦略にかかわらず、結果は常に元の LLM が返した順序で再構成されます。失敗したツールは JSON エラーレスポンスを生成しますが、バッチ全体を中断することはありません。
pub enum StreamingMode {
/// No streaming, return complete response
None,
/// Server-Sent Events (default)
SSE,
/// Bidirectional streaming (realtime)
Bidi,
}
エージェントの転送
Runner は、複数エージェント間の転送を自動的に処理します。
// In an agent's tool or callback
if should_transfer {
// Set transfer in event actions
ctx.set_actions(EventActions {
transfer_to_agent: Some("specialist_agent".to_string()),
..Default::default()
});
}
Runner は次の処理を行います。
- イベント内の転送リクエストを検出する
- sub_agents 内で対象エージェントを検索する
- 新しいアクティブエージェントでセッション状態を更新する
- 新しいエージェントで実行を継続する
コンテキストの圧縮
長時間実行されるセッションでは、LLM のコンテキストウィンドウを一定範囲内に保つため、自動コンテキスト圧縮を有効にします。
use adk_runner::{Runner, RunnerConfig, EventsCompactionConfig};
use adk_agent::LlmEventSummarizer;
use std::sync::Arc;
let summarizer = LlmEventSummarizer::new(model.clone());
let config = RunnerConfig {
// ... other fields ...
compaction_config: Some(EventsCompactionConfig {
compaction_interval: 3, // Compact every 3 invocations
overlap_size: 1, // Keep 1 event overlap for continuity
summarizer: Arc::new(summarizer),
}),
// ...
};
圧縮が開始されると、以前のイベントは要約イベントに置き換えられます。conversation_history() は、元のイベントの代わりにこの要約を自動的に使用します。
詳細については、コンテキストの圧縮 を参照してください。
Launcher との統合
Launcher は内部で Runner を使用します。
// Launcher creates Runner with default services
Launcher::new(agent)
.app_name("my_app")
.run()
.await?;
// Equivalent to using the builder:
let runner = Runner::builder()
.app_name("my_app")
.agent(agent)
.session_service(Arc::new(InMemorySessionService::new()))
.build()?;
カスタム Runner の使用
高度なシナリオでは、Runner を直接使用します。
use adk_runner::Runner;
// Production configuration using the builder
let runner = Runner::builder()
.app_name("production_app")
.agent(my_agent)
.session_service(Arc::new(SqliteSessionService::new(db_pool)))
.artifact_service(Arc::new(S3ArtifactService::new(s3_client)))
.memory_service(Arc::new(QdrantMemoryService::new(qdrant_client)))
.build()?;
// Use in HTTP handler with run_str() for convenience
async fn chat_handler(runner: &Runner, request: ChatRequest) -> Response {
let stream = runner.run_str(
&request.user_id,
&request.session_id,
request.content,
).await?;
// Stream events to client
Response::sse(stream)
}
前へ: ← コア型 | 次へ: Launcher →