Runner

Die Laufzeitumgebung aus adk-runner, die die Agent-Ausführung orchestriert.

Übersicht

Runner verwaltet den vollständigen Lebenszyklus der Agent-Ausführung:

  • Sitzungsverwaltung (Sitzungen erstellen/abrufen)
  • Speicherinjektion (relevante Erinnerungen suchen und injizieren)
  • Artefaktverwaltung (bereichsbezogener Artefaktzugriff)
  • Ereignis-Streaming (Ereignisse verarbeiten und weiterleiten)
  • Agent-Übertragungen (Übergaben zwischen mehreren Agenten verwalten)
Rendering architecture…

Installation

[dependencies]
adk-runner = "2.1.0"

RunnerConfig

Konfigurieren Sie den Runner mit den erforderlichen Diensten:

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

Verwenden Sie den typestate builder, um einen Runner zu erstellen. Der Builder erzwingt erforderliche Felder zur Compile-Zeit und versieht alle optionalen Felder mit Standardwerten. Dadurch wird Ihr Code beim Hinzufügen neuer Felder in zukünftigen Versionen nicht beeinträchtigt:

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

Der Builder erfordert drei Felder: app_name, agent und session_service. Alles andere ist optional und verfügt über sinnvolle Standardwerte. Die Methode build() ist erst verfügbar, sobald alle drei erforderlichen Felder gesetzt sind – ein fehlendes Feld führt zu einem Fehler zur Compile-Zeit und nicht erst zur Laufzeit.

Konfigurationsfelder

FeldTypErforderlichBeschreibung
app_nameStringJaAnwendungskennung
agentArc<dyn Agent>JaAuszuführender Stammagent
session_serviceArc<dyn SessionService>JaBackend für Sitzungsspeicher
artifact_serviceOption<Arc<dyn ArtifactService>>NeinArtefaktspeicher
memory_serviceOption<Arc<dyn Memory>>NeinLangzeitgedächtnis
plugin_managerOption<Arc<PluginManager>>NeinHooks für den Plugin-Lebenszyklus
compaction_configOption<EventsCompactionConfig>NeinEinstellungen zur Kontextkomprimierung
run_configOption<RunConfig>NeinAusführungsoptionen
context_cache_configOption<ContextCacheConfig>NeinLebenszyklus des Kontext-Caches auf Runner-Ebene (experimentell — siehe unten)
cache_capableOption<Arc<dyn CacheCapable>>NeinReferenz auf ein cachefähiges Modell (experimentell — siehe unten)
request_contextOption<RequestContext>NeinKontext der Authentifizierungs-Middleware
cancellation_tokenOption<CancellationToken>NeinKooperative Abbruchbehandlung

Prompt-Caching

Caching ist eine Angelegenheit auf Anbieterebene und erfordert keine Runner-Konfiguration. Jede Anbieterintegration behandelt dies dort, wo die Anfrage zusammengestellt wird:

AnbieterMechanismusStandard
Anthropic / Bedrockcache_control Breakpointsaktiviert (AnthropicConfig::prompt_caching, deaktivieren mit with_prompt_caching(false))
OpenAIserverseitiges Prompt-Caching, PromptCacheRetention für die Aufbewahrungautomatisch
GeminiImplizites Caching auf 2.5/3.x – ein gemeinsames Präfix bringt ohne Codeänderung einen Rabattautomatisch

Cache-Treffer sind ohne zusätzliche Verdrahtung beobachtbar: Die Gemini-Integration erfasst cachedContentTokenCount bei jeder Antwort.

context_cache_config und cache_capable sind experimentell und sollten nicht gesetzt werden. Sie steuern cachedContents API von Gemini aus dem Runner. Dieser API erfordert, dass der Cache system_instruction, tools und tool_config ersetzt — das Senden eines Caches zusammen mit einem dieser Felder wird mit INVALID_ARGUMENT abgelehnt. Der Runner wählt einen Cache aus, bevor der Agent seine Tools auflöst, und kann diese Anfrage daher nicht zusammenstellen. Außerdem erzeugt die Aktivierung dieser Felder derzeit keine Cache-Treffer. Garantiertes Caching für Gemini (statt Best-Effort-Caching) gehört in die Modellintegration, ebenso wie die entsprechende Umsetzung bei den anderen Anbietern.

Agenten ausführen

Einen Agenten mit Benutzereingabe ausführen:

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

Komfortmethode für Zeichenfolgen

Für einfache Aufrufstellen akzeptiert run_str() einfache &str-Argumente und übernimmt die Umwandlung in den Newtype-Typ intern:

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

Wenn die Zeichenfolge die Validierung nicht besteht (leer ist, Nullbytes enthält oder das Längenlimit überschreitet), gibt run_str() einen Fehler zurück, bevor die Agentenschleife gestartet wird. Die vorhandene Methode run() mit typisierten UserId/SessionId bleibt unverändert.

Unterbrechung und Laufisolierung

Ein Lauf wird registriert, sobald run() seinen Stream zurückgibt, und deregistriert, wenn dieser Stream verworfen wird — auch wenn er verworfen wird, ohne jemals abgefragt worden zu sein.

MethodeGeltungsbereich
interrupt(session_id)Bricht jeden laufenden Durchlauf für diese Sitzungs-ID über Apps und Benutzer hinweg ab
interrupt_identity(app_name, user_id, session_id)Bricht Durchläufe für eine exakt übereinstimmende Identität ab
active_runs()Die Identität jedes laufenden Durchlaufs; eine wiederholte Identität bedeutet parallele Durchläufe
active_session_ids()Deduplizierte Sitzungs-IDs laufender Durchläufe
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");

Eine Sitzungs-ID ist nur innerhalb einer App und eines Benutzers eindeutig, daher ist interrupt(session_id) die allgemeine Form und interrupt_identity die präzise Form. Bevorzugen Sie interrupt_identity, wenn ein einzelnes Runner mehr als eine App oder einen Benutzer bedient.

Ausführungen werden anhand einer eindeutigen Ausführungs-ID und nicht anhand der Sitzungs-ID verfolgt. Daher werden zwei Ausführungen für dieselbe Identität separat verfolgt, und jede meldet nur sich selbst ab.

Persistenz ist an die Identität gebunden

Jedes Ereignis, das der Runner persistent speichert — Benutzeraktionen, Modellantworten, Übertragungsereignisse, Plug-in-Ereignisse und Komprimierungsereignisse — wird über SessionService::append_event_for_identity mit dem vollständigen (app_name, user_id, session_id)-Tripel geschrieben. Ein SessionService, dessen natürlicher Schlüssel zusammengesetzt ist, kann daher jedes Ereignis an seinen Mandanten binden und den Pfad append_event mit der rohen Sitzungs-ID vollständig ablehnen oder ignorieren.

Ausführungsablauf

┌─────────────────────────────────────────────────────────────┐
│                     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

Der Kontext, der Agenten während der Ausführung bereitgestellt wird:

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

Ausführungsoptionen:

pub struct RunConfig {
    /// Streaming mode for responses
    pub streaming_mode: StreamingMode,
    // ... other fields (tool_confirmation_decisions, cached_content, etc.)
}

ToolExecutionStrategy

Steuert, wie mehrere Tool-Aufrufe aus einer einzelnen LLM-Antwort verteilt werden:

StrategieVerhalten
Sequential (Standard)Tools nacheinander in der von LLM zurückgegebenen Reihenfolge ausführen
ParallelAlle Tools gleichzeitig ausführen; der Aufrufer ist für die Sicherheit verantwortlich
AutoDie sichere schreibgeschützte Teilmenge gleichzeitig ausführen, anschließend alle verbleibenden Aufrufe sequenziell

Pro Agent festgelegt über 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()?;

Im Modus Auto fragt die Dispatch-Schleife sowohl is_read_only() als auch is_concurrency_safe() ab. Aufrufe, deren ausgewählte Tools für beide Methoden true zurückgeben, werden zuerst gleichzeitig ausgeführt; alle verbleibenden Aufrufe werden anschließend sequenziell ausgeführt. Parallel umgeht diese Metadatenprüfungen als explizite Überschreibung durch den Aufrufer. Die Ergebnisse werden unabhängig von der Strategie immer in der ursprünglichen, von LLM zurückgegebenen Reihenfolge wieder zusammengesetzt. Fehlgeschlagene Tools erzeugen eine JSON-Fehlerantwort, ohne den Batch abzubrechen.

pub enum StreamingMode {
    /// No streaming, return complete response
    None,
    /// Server-Sent Events (default)
    SSE,
    /// Bidirectional streaming (realtime)
    Bidi,
}

Agent-Übergaben

Der Runner verarbeitet Übergaben zwischen mehreren Agents automatisch:

// 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()
    });
}

Der Runner wird:

  1. Die Übergabeanforderung im Ereignis erkennen
  2. Den Ziel-Agenten in sub_agents finden
  3. Den Sitzungsstatus mit dem neuen aktiven Agenten aktualisieren
  4. Die Ausführung mit dem neuen Agenten fortsetzen

Kontextkomprimierung

Aktivieren Sie für Sitzungen mit langer Laufzeit die automatische Kontextkomprimierung, um das Kontextfenster von LLM begrenzt zu halten:

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),
    }),
    // ...
};

Wenn die Komprimierung ausgelöst wird, werden ältere Ereignisse durch ein Zusammenfassungsereignis ersetzt. conversation_history() verwendet automatisch die Zusammenfassung anstelle der ursprünglichen Ereignisse.

Siehe Kontextkomprimierung für die vollständige Dokumentation.

Integration mit Launcher

Der Launcher verwendet intern 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()?;

Benutzerdefinierte Runner-Verwendung

Für fortgeschrittene Szenarien verwenden Sie Runner direkt:

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

Zurück: ← Core Types | Weiter: Launcher →

Runner - ADK-Rust Dokumentation | ADK-Rust