Runner

El tiempo de ejecución de adk-runner que orquesta la ejecución del agente.

Resumen

El Runner gestiona el ciclo de vida completo de la ejecución del agente:

  • Gestión de sesiones (crear/recuperar sesiones)
  • Inyección de memoria (buscar e inyectar memorias relevantes)
  • Manejo de artefactos (acceso acotado a artefactos)
  • Transmisión de eventos (procesar y reenviar eventos)
  • Transferencias de agente (manejar traspasos entre múltiples agentes)
Rendering architecture…

Instalación

[dependencies]
adk-runner = "2.0.0"

RunnerConfig

Configura el runner con los servicios requeridos:

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

Usa el constructor typestate para construir un Runner. El constructor impone los campos requeridos en tiempo de compilación y asigna valores predeterminados a todos los campos opcionales, por lo que añadir nuevos campos en futuras versiones no romperá tu código:

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

El constructor requiere tres campos: app_name, agent y session_service. Todo lo demás es opcional y tiene valores predeterminados razonables. El método build() solo está disponible una vez que los tres campos requeridos están establecidos: faltar uno es un error de compilación, no un error en tiempo de ejecución.

Campos de configuración

CampoTipoRequeridoDescripción
app_nameStringIdentificador de la aplicación
agentArc<dyn Agent>Agente raíz a ejecutar
session_serviceArc<dyn SessionService>Backend de almacenamiento de sesión
artifact_serviceOption<Arc<dyn ArtifactService>>NoAlmacenamiento de artefactos
memory_serviceOption<Arc<dyn Memory>>NoMemoria a largo plazo
plugin_managerOption<Arc<PluginManager>>NoHooks del ciclo de vida del plugin
compaction_configOption<EventsCompactionConfig>NoConfiguración de compactación de contexto
run_configOption<RunConfig>NoOpciones de ejecución
context_cache_configOption<ContextCacheConfig>NoCiclo de vida de la caché de contexto a nivel del runner (experimental — ver abajo)
cache_capableOption<Arc<dyn CacheCapable>>NoReferencia de modelo compatible con caché (experimental — ver abajo)
request_contextOption<RequestContext>NoContexto del middleware de autenticación
cancellation_tokenOption<CancellationToken>NoCancelación cooperativa

Caché de prompt

La caché es una responsabilidad a nivel de proveedor y no requiere configuración del Runner. Cada integración de proveedor la gestiona donde se ensambla la solicitud:

ProveedorMecanismoPredeterminado
Anthropic / Bedrockcache_control puntos de interrupciónactivado (AnthropicConfig::prompt_caching, excluirse con with_prompt_caching(false))
OpenAIcaché de prompts del lado del servidor, PromptCacheRetention para retenciónautomático
Geminicaché implícita en 2.5/3.x — un prefijo compartido obtiene un descuento sin cambios de códigoautomático

Los aciertos de la caché son observables sin necesidad de cableado adicional: la integración de Gemini registra cachedContentTokenCount en cada respuesta.

context_cache_config y cache_capable son experimentales y deben dejarse sin establecer. Impulsan el cachedContents API explícito de Gemini desde el Runner. Ese API requiere que la caché reemplace system_instruction, tools y tool_config — enviar una caché junto con cualquiera de ellas se rechaza con INVALID_ARGUMENT. El Runner selecciona una caché antes de que el agente resuelva sus herramientas, por lo que no puede ensamblar esa solicitud, y habilitar estos campos actualmente no produce aciertos de caché. El almacenamiento en caché garantizado (en lugar de mejor esfuerzo) para Gemini pertenece a la integración del modelo, junto con la forma en que lo hacen los demás proveedores.

Ejecutar agentes

Ejecutar un agente con entrada del usuario:

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étodo de conveniencia para cadenas

Para casos de uso simples, run_str() acepta argumentos de &str simples y maneja internamente la conversión de newtype:

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

Si la cadena falla en la validación (vacía, contiene bytes nulos o excede el límite de longitud), run_str() devuelve un error antes de iniciar el bucle del agente. El método run() existente con UserId/SessionId tipados permanece sin cambios.

Interrupción y aislamiento de ejecuciones

Una ejecución se registra en cuanto run() devuelve su stream, y se da de baja cuando ese stream se elimina — incluso cuando se elimina sin haber sido nunca sondeado.

MétodoAlcance
interrupt(session_id)Cancela cada ejecución en curso para ese ID de sesión, en todas las aplicaciones y usuarios
interrupt_identity(app_name, user_id, session_id)Cancela las ejecuciones para una identidad exacta
active_runs()La identidad de cada ejecución en curso; una identidad repetida significa ejecuciones concurrentes
active_session_ids()IDs de sesión deduplicados de ejecuciones en curso
// 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 ID de sesión solo es único dentro de una app y un usuario, así que interrupt(session_id) es la forma amplia y interrupt_identity la precisa. Prefiere interrupt_identity cuando una sola Runner sirve para más de una app o usuario.

Las ejecuciones se rastrean por un ID de ejecución único en lugar de por ID de sesión, así que dos ejecuciones para la misma identidad se rastrean por separado y cada una se anula solo a sí misma.

Persistence Is Identity-Bound

Cada evento que persiste el Runner — turnos de usuario, respuestas del modelo, eventos de transferencia, eventos de plugin y eventos de compactación — se escribe a través de SessionService::append_event_for_identity con el triple completo (app_name, user_id, session_id). Un SessionService cuya clave natural es compuesta puede, por lo tanto, vincular cada evento a su inquilino y puede rechazar o ignorar por completo la ruta de ID de sesión sin procesar append_event.

Execution Flow

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

El contexto proporcionado a los agentes durante la ejecución:

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

Opciones de ejecución:

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

ToolExecutionStrategy

Controla cómo se despachan múltiples llamadas a herramientas desde una sola respuesta de LLM:

EstrategiaComportamiento
Sequential (predeterminada)Ejecuta las herramientas una a la vez en el orden devuelto por LLM
ParallelEjecuta todas las herramientas concurrentemente; el llamador es responsable de la seguridad
AutoEjecuta concurrentemente el subconjunto seguro de solo lectura, luego todas las llamadas restantes de forma secuencial

Configurar por agente mediante 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 modo Auto, el bucle de despacho consulta tanto is_read_only() como is_concurrency_safe(). Las llamadas cuyos herramientas seleccionadas devuelven true para ambos métodos se ejecutan en paralelo primero; todas las llamadas restantes se ejecutan luego secuencialmente. Parallel omite estas comprobaciones de metadatos como una anulación explícita del llamador. Los resultados siempre se reensamblan en el orden original devuelto por LLM, independientemente de la estrategia. Las herramientas fallidas producen una respuesta de error JSON sin abortar el lote.

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

Transferencias de agentes

Runner maneja automáticamente las transferencias entre múltiples agentes:

// 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 hará lo siguiente:

  1. Detectar la solicitud de transferencia en el evento
  2. Encontrar el agente de destino en sub_agents
  3. Actualizar el estado de la sesión con el nuevo agente activo
  4. Continuar la ejecución con el nuevo agente

Compactación de contexto

Para sesiones de larga duración, habilita la compactación automática del contexto para mantener acotada la ventana de contexto de 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),
    }),
    // ...
};

Cuando se activa la compactación, los eventos más antiguos se reemplazan por un evento de resumen. conversation_history() usa automáticamente el resumen en lugar de los eventos originales.

Consulta Compactación de contexto para la documentación completa.

Integración con Launcher

Launcher usa Runner internamente:

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

Uso personalizado de Runner

Para escenarios avanzados, usa Runner directamente:

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

Anterior: ← Core Types | Siguiente: Launcher →