ランナー

adk-runner の実行ランタイムで、エージェント実行を調整します。

概要

Runner は、エージェント実行の完全なライフサイクルを管理します。

  • セッション管理(セッションの作成/取得)
  • メモリ注入(関連するメモリの検索と注入)
  • アーティファクト処理(スコープ付きアーティファクトアクセス)
  • イベントストリーミング(イベントの処理と転送)
  • エージェント転送(複数エージェント間のハンドオフ処理)
Rendering architecture…

インストール

[dependencies]
adk-runner = "2.0.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)?;

typestate builder を使用して Runner を構築します。builder はコンパイル時に必須フィールドを強制し、すべての任意フィールドにはデフォルトを設定するため、将来のリリースで新しいフィールドが追加されてもコードは壊れません。

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()?;

builder には app_nameagentsession_service の 3 つのフィールドが必要です。それ以外はすべて任意で、適切なデフォルトがあります。build() メソッドは、3 つの必須フィールドがすべて設定された後にのみ使用できます。不足している場合は実行時エラーではなく、コンパイル時エラーになります。

設定フィールド

フィールド必須説明
app_nameStringはいアプリケーション識別子
agentArc<dyn Agent>はい実行するルートエージェント
session_serviceArc<dyn SessionService>はいセッションストレージバックエンド
artifact_serviceOption<Arc<dyn ArtifactService>>いいえアーティファクトストレージ
memory_serviceOption<Arc<dyn Memory>>いいえ長期メモリ
plugin_managerOption<Arc<PluginManager>>いいえプラグインのライフサイクルフック
compaction_configOption<EventsCompactionConfig>いいえコンテキスト圧縮設定
run_configOption<RunConfig>いいえ実行オプション
context_cache_configOption<ContextCacheConfig>いいえランナーレベルのコンテキストキャッシュのライフサイクル(実験的 — 以下を参照)
cache_capableOption<Arc<dyn CacheCapable>>いいえキャッシュ対応モデル参照(実験的 — 以下を参照)
request_contextOption<RequestContext>いいえ認証ミドルウェアのコンテキスト
cancellation_tokenOption<CancellationToken>いいえ協調的キャンセル

プロンプトキャッシュ

キャッシュはプロバイダーレベルの関心事であり、Runner の設定は不要です。 各プロバイダー統合は、リクエストが組み立てられる場所でそれを処理します:

プロバイダーメカニズムデフォルト
Anthropic / Bedrockcache_control ブレークポイント有効 (AnthropicConfig::prompt_cachingwith_prompt_caching(false) で無効化可能)
OpenAIサーバー側プロンプトキャッシュ、保持のための PromptCacheRetention自動
Gemini2.5/3.x での暗黙的キャッシュ — 共有プレフィックスにより、コード変更なしで割引が適用される自動

追加の配線なしでキャッシュヒットを観測できます。Gemini の統合は、各レスポンスごとに cachedContentTokenCount を記録します。

context_cache_configcache_capable は実験的であり、 未設定のままにするべきです。 これらは Runner から Gemini の 明示的な cachedContents API を駆動します。その API では、キャッシュが system_instructiontoolstool_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 引数を受け取り、新しい型への変換を内部で処理します:

let mut stream = runner.run_str(
    "user-123",
    "session-456",
    Content::new("user").with_text("Hello!"),
).await?;

文字列の検証に失敗した場合(空、null バイトを含む、または長さ制限を超える)、run_str() はエージェントループを開始する前にエラーを返します。型付きの UserId/SessionId を使う既存の run() メソッドは変更ありません。

中断と Run の分離

Run は run() がストリームを返した時点で登録され、そのストリームが破棄されたときに登録解除されます — 一度もポーリングされないまま破棄された場合も含みます。

メソッド範囲
interrupt(session_id)その session ID に対する進行中のすべての実行を、アプリとユーザーをまたいでキャンセルします
interrupt_identity(app_name, user_id, session_id)1つの完全一致する識別情報に対する実行をキャンセルします
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 はより厳密な形式です。単一の interrupt_identity が複数のアプリまたはユーザーにまたがる場合は、Runner を優先してください。

Run はセッションIDではなく一意の run ID で追跡されるため、同じ ID に対する 2 つの run は別々に追跡され、それぞれが自分自身のみを登録解除します。

永続化は ID に紐づく

Runner が永続化するすべてのイベント — ユーザーのターン、モデル応答、転送イベント、 プラグインイベント、圧縮イベント — は、完全な SessionService::append_event_for_identity(app_name, user_id, session_id) の triple を使って書き込まれます。自然キーが 複合である 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 (default)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 は以下を行います:

  1. イベント内の転送要求を検出する
  2. sub_agents内で対象エージェントを見つける
  3. 新しいアクティブエージェントでセッション状態を更新する
  4. 新しいエージェントで実行を継続する

コンテキスト圧縮

長時間実行されるセッションでは、自動コンテキスト圧縮を有効にして、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()は自動的に元のイベントの代わりに要約を使用します。

完全なドキュメントについては、Context Compactionを参照してください。

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)
}

: ← Core Types | : Launcher →