Exécuteur

L’environnement d’exécution de adk-runner qui orchestre l’exécution des agents.

Vue d’ensemble

Runner gère l’intégralité du cycle de vie de l’exécution des agents :

  • Gestion des sessions (création/récupération des sessions)
  • Injection de mémoire (recherche et injection des souvenirs pertinents)
  • Gestion des artefacts (accès aux artefacts limité au périmètre défini)
  • Diffusion des événements (traitement et transmission des événements)
  • Transferts d’agents (gestion des transitions entre plusieurs agents)
Rendering architecture…

Installation

[dependencies]
adk-runner = "2.1.0"

RunnerConfig

Configurez l’exécuteur 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 au moment de la compilation et attribue des valeurs par défaut à tous les champs facultatifs. Ainsi, l’ajout de nouveaux champs dans de futures versions ne provoquera pas de rupture dans 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 requiert trois champs : app_name, agent et session_service. Tout le reste est facultatif et dispose de valeurs par défaut appropriées. La méthode build() n’est disponible qu’une fois les trois champs requis définis : l’absence de l’un d’eux provoque une erreur de compilation, et non une erreur d’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 des sessions
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 de l’exécuteur (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 relève du fournisseur et ne nécessite aucune configuration de Runner. Chaque intégration de fournisseur la gère au moment où la requête est assemblée :

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

Les accès au cache sont observables sans configuration supplémentaire : l’intégration Gemini enregistre cachedContentTokenCount dans chaque réponse.

context_cache_config et cache_capable sont expérimentaux et doivent rester non définis. Ils pilotent le cachedContents explicite de Gemini API depuis le Runner. Cette API exige que le cache remplace system_instruction, tools et tool_config — l’envoi d’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 aucun accès au cache. La mise en cache garantie (plutôt que dans la mesure du possible) pour Gemini relève de l’intégration du modèle, comme pour les autres fournisseurs.

Exécution des agents

Exécutez un agent avec une 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 points d’appel simples, run_str() accepte des arguments &str simples et gère en interne la conversion vers le newtype :

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

Si la chaîne échoue à la validation (elle est 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 avoir jamais été interrogé.

MéthodePortée
interrupt(session_id)Annule toutes les exécutions en cours pour cet ID de session, parmi les applications et 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 que les exécutions sont concurrentes
active_session_ids()Identifiants 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’au sein d’une application et d’un utilisateur ; interrupt(session_id) est donc la forme générale et interrupt_identity la forme précise. Préférez interrupt_identity lorsqu’un seul Runner est utilisé par plusieurs applications ou utilisateurs.

Les exécutions sont suivies par un identifiant d’exécution unique plutôt que par un identifiant de session. Ainsi, deux exécutions associées à la même identité sont suivies séparément et chacune se désenregistre uniquement elle-même.

La persistance est liée à l’identité

Chaque événement que le Runner rend persistant — tours utilisateur, réponses du modèle, événements de transfert, événements de plugin et événements de compactage — est écrit via SessionService::append_event_for_identity avec le triplet complet (app_name, user_id, session_id). Un SessionService dont la clé naturelle est composite peut donc associer chaque événement à son locataire et peut rejeter ou ignorer entièrement le chemin append_event utilisant 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’outils provenant d’une même réponse LLM sont distribué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écuter simultanément le sous-ensemble sûr en lecture seule, puis tous les appels restants séquentiellement

Définissez-le 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 sont d’abord exécutés simultanément ; tous les autres appels sont ensuite exécutés séquentiellement. Parallel contourne ces vérifications de métadonnées en tant que surcharge explicite de l’appelant. 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 entre agents

Le Runner gère automatiquement les transferts entre plusieurs agents :

// 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. Rechercher l’agent cible dans sub_agents
  3. Mettre à jour l’état de la session avec le nouvel agent actif
  4. Poursuivre l’exécution avec le nouvel agent

Compactage du contexte

Pour les sessions de longue durée, activez le compactage automatique du contexte afin de maintenir la fenêtre de contexte LLM dans des limites définies :

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 le compactage est déclenché, les événements plus anciens sont remplacés par un événement de synthèse. conversation_history() utilise automatiquement la synthèse au lieu des événements d’origine.

Consultez Compactage 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 du Runner

Pour les scénarios avancés, utilisez directement 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)
}

Précédent : ← Types principaux | Suivant : Launcher →