Runner

Le runtime d’exécution de adk-runner qui orchestre l’exécution de l’agent.

Vue d’ensemble

Le Runner gère le cycle de vie complet de l’exécution de l’agent :

  • Gestion des sessions (créer/récupérer des sessions)
  • Injection de mémoire (rechercher et injecter les mémoires pertinentes)
  • Gestion des artefacts (accès aux artefacts à portée limitée)
  • Flux d’événements (traiter et transmettre les événements)
  • Transferts d’agent (gérer les passations entre plusieurs agents)
Rendering architecture…

Installation

[dependencies]
adk-runner = "2.0.0"

RunnerConfig

Configurez le runner avec les services requis :

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

Utilisez le générateur typestate pour construire un Runner. Le générateur impose les champs requis à la compilation et attribue des valeurs par défaut à tous les champs optionnels, de sorte que l’ajout de nouveaux champs dans les futures versions ne casse pas votre code :

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

Le générateur exige trois champs : app_name, agent et session_service. Tout le reste est facultatif et dispose de valeurs par défaut raisonnables. La méthode build() n’est disponible qu’une fois les trois champs requis définis — en manquer un provoque une erreur à la compilation, et non à l’exécution.

Champs de configuration

ChampTypeObligatoireDescription
app_nameStringOuiIdentifiant de l'application
agentArc<dyn Agent>OuiAgent racine à exécuter
session_serviceArc<dyn SessionService>OuiBackend de stockage de session
artifact_serviceOption<Arc<dyn ArtifactService>>NonStockage des artefacts
memory_serviceOption<Arc<dyn Memory>>NonMémoire à long terme
plugin_managerOption<Arc<PluginManager>>NonHooks du cycle de vie des plugins
compaction_configOption<EventsCompactionConfig>NonParamètres de compaction du contexte
run_configOption<RunConfig>NonOptions d’exécution
context_cache_configOption<ContextCacheConfig>NonCycle de vie du cache de contexte au niveau du runner (expérimental — voir ci-dessous)
cache_capableOption<Arc<dyn CacheCapable>>NonRéférence de modèle compatible avec le cache (expérimental — voir ci-dessous)
request_contextOption<RequestContext>NonContexte du middleware d’authentification
cancellation_tokenOption<CancellationToken>NonAnnulation coopérative

Mise en cache des prompts

La mise en cache est une préoccupation au niveau du fournisseur et ne nécessite aucune configuration de Runner. Chaque intégration de fournisseur la gère à l’endroit où la requête est assemblée :

FournisseurMécanismePar défaut
Anthropic / Bedrockpoints d'arrêt cache_controlactivé (AnthropicConfig::prompt_caching, désactivable avec with_prompt_caching(false))
OpenAImise en cache des prompts côté serveur, PromptCacheRetention pour la rétentionautomatique
Geminimise en cache implicite sur 2.5/3.x — un préfixe partagé bénéficie d’une remise sans changement de codeautomatique

Les hits de cache sont observables sans câblage supplémentaire : l’intégration Gemini enregistre cachedContentTokenCount sur chaque réponse.

context_cache_config et cache_capable sont expérimentaux et doivent rester non définis. Ils pilotent le cachedContents API explicite de Gemini depuis le Runner. Ce API exige que le cache remplace system_instruction, tools et tool_config — envoyer un cache avec l’un d’eux est rejeté avec INVALID_ARGUMENT. Le Runner sélectionne un cache avant que l’agent ne résolve ses outils, il ne peut donc pas assembler cette requête, et l’activation de ces champs ne produit actuellement pas de hits de cache. Un cache garanti (plutôt qu’au mieux possible) pour Gemini relève de l’intégration du modèle, de la même manière que le font les autres fournisseurs.

Exécution des agents

Exécutez un agent avec l’entrée utilisateur :

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

Méthode pratique pour les chaînes

Pour les appels simples, run_str() accepte des arguments &str bruts et gère la conversion du nouveautype en interne :

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

Si la chaîne échoue à la validation (vide, contient des octets nuls, ou dépasse la limite de longueur), run_str() renvoie une erreur avant de démarrer la boucle de l’agent. La méthode run() existante avec des UserId/SessionId typés reste inchangée.

Interruption et isolation des exécutions

Une exécution est enregistrée dès que run() renvoie son flux, et désenregistrée lorsque ce flux est supprimé — y compris lorsqu’il est supprimé sans jamais avoir été interrogé.

MéthodePortée
interrupt(session_id)Annule toute exécution en cours pour cet ID de session, dans toutes les applications et pour tous les utilisateurs
interrupt_identity(app_name, user_id, session_id)Annule les exécutions pour une identité exacte
active_runs()L’identité de chaque exécution en cours ; une identité répétée signifie des exécutions concurrentes
active_session_ids()IDs de session dédupliqués des exécutions en cours
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");

Un identifiant de session n’est unique qu’à l’intérieur d’une application et d’un utilisateur, donc interrupt(session_id) est la forme large et interrupt_identity la forme précise. Préférez interrupt_identity lorsqu’une seule Runner sert plus d’une application ou d’un utilisateur.

Les exécutions sont suivies par un identifiant d’exécution unique plutôt que par un identifiant de session, donc deux exécutions pour la même identité sont suivies séparément et chacune ne se désenregistre qu’elle-même.

La persistance est liée à l’identité

Chaque événement que le Runner persiste — tours utilisateur, réponses du modèle, événements de transfert, événements de plugin et événements de compaction — est écrit via SessionService::append_event_for_identity avec le triplet complet (app_name, user_id, session_id). Une SessionService dont la clé naturelle est composite peut donc lier chaque événement à son locataire, et peut rejeter ou ignorer entièrement le chemin append_event de l’identifiant de session brut.

Flux d’exécution

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

Le contexte fourni aux agents pendant l’exécution :

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

Options d’exécution :

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

ToolExecutionStrategy

Contrôle la manière dont plusieurs appels d’outil provenant d’une seule réponse LLM sont dispatchés :

StratégieComportement
Sequential (par défaut)Exécute les outils un par un dans l’ordre renvoyé par LLM
ParallelExécute tous les outils simultanément ; l’appelant est responsable de la sécurité
AutoExécutez le sous-ensemble sécurisé en lecture seule simultanément, puis toutes les autres appels séquentiellement

Définir par agent via 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()?;

En mode Auto, la boucle de dispatch interroge à la fois is_read_only() et is_concurrency_safe(). Les appels dont les outils sélectionnés renvoient true pour les deux méthodes s’exécutent d’abord en parallèle ; tous les appels restants s’exécutent ensuite séquentiellement. Parallel contourne ces vérifications de métadonnées en tant que surcharge explicite du caller. Les résultats sont toujours réassemblés dans l’ordre d’origine renvoyé par LLM, quelle que soit la stratégie. Les outils en échec produisent une réponse d’erreur JSON sans interrompre le lot.

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

Transferts d’agent

Le Runner gère automatiquement les transferts multi-agent :

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

Le Runner va :

  1. Détecter la demande de transfert dans l’événement
  2. Trouver l’agent cible dans sub_agents
  3. Mettre à jour l’état de session avec le nouvel agent actif
  4. Continuer l’exécution avec le nouvel agent

Compaction du contexte

Pour les sessions de longue durée, activez la compaction automatique du contexte afin de maintenir la fenêtre de contexte LLM bornée :

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

Lorsque la compaction se déclenche, les événements plus anciens sont remplacés par un événement de résumé. conversation_history() utilise automatiquement le résumé à la place des événements d’origine.

Voir Compaction du contexte pour la documentation complète.

Intégration avec Launcher

Le Launcher utilise Runner en interne :

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

Utilisation personnalisée de Runner

Pour des scénarios avancés, utilisez Runner directement :

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

Précédent : ← Types de base | Suivant : Launcher →

Runner - Documentation ADK-Rust | ADK-Rust