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)
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)?;
RunnerConfigBuilder (Recomendado)
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
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
app_name | String | Sí | Identificador de la aplicación |
agent | Arc<dyn Agent> | Sí | Agente raíz a ejecutar |
session_service | Arc<dyn SessionService> | Sí | Backend de almacenamiento de sesión |
artifact_service | Option<Arc<dyn ArtifactService>> | No | Almacenamiento de artefactos |
memory_service | Option<Arc<dyn Memory>> | No | Memoria a largo plazo |
plugin_manager | Option<Arc<PluginManager>> | No | Hooks del ciclo de vida del plugin |
compaction_config | Option<EventsCompactionConfig> | No | Configuración de compactación de contexto |
run_config | Option<RunConfig> | No | Opciones de ejecución |
context_cache_config | Option<ContextCacheConfig> | No | Ciclo de vida de la caché de contexto a nivel del runner (experimental — ver abajo) |
cache_capable | Option<Arc<dyn CacheCapable>> | No | Referencia de modelo compatible con caché (experimental — ver abajo) |
request_context | Option<RequestContext> | No | Contexto del middleware de autenticación |
cancellation_token | Option<CancellationToken> | No | Cancelació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:
| Proveedor | Mecanismo | Predeterminado |
|---|---|---|
| Anthropic / Bedrock | cache_control puntos de interrupción | activado (AnthropicConfig::prompt_caching, excluirse con with_prompt_caching(false)) |
| OpenAI | caché de prompts del lado del servidor, PromptCacheRetention para retención | automático |
| Gemini | caché implícita en 2.5/3.x — un prefijo compartido obtiene un descuento sin cambios de código | automá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_configycache_capableson experimentales y deben dejarse sin establecer. Impulsan elcachedContentsAPI explícito de Gemini desde el Runner. Ese API requiere que la caché reemplacesystem_instruction,toolsytool_config— enviar una caché junto con cualquiera de ellas se rechaza conINVALID_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étodo | Alcance |
|---|---|
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:
| Estrategia | Comportamiento |
|---|---|
Sequential (predeterminada) | Ejecuta las herramientas una a la vez en el orden devuelto por LLM |
Parallel | Ejecuta todas las herramientas concurrentemente; el llamador es responsable de la seguridad |
Auto | Ejecuta 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:
- Detectar la solicitud de transferencia en el evento
- Encontrar el agente de destino en sub_agents
- Actualizar el estado de la sesión con el nuevo agente activo
- 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 →