Runner

O runtime de execução de adk-runner que orquestra a execução do agente.

Visão geral

O Runner gerencia o ciclo de vida completo da execução do agente:

  • Gerenciamento de sessão (criar/recuperar sessões)
  • Injeção de memória (buscar e injetar memórias relevantes)
  • Tratamento de artefatos (acesso a artefatos com escopo)
  • Streaming de eventos (processar e encaminhar eventos)
  • Transferências de agentes (lidar com handoffs entre múltiplos agentes)
Rendering architecture…

Instalação

[dependencies]
adk-runner = "2.0.0"

RunnerConfig

Configure o runner com os serviços necessários:

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

Use o builder typestate para construir um Runner. O builder impõe os campos obrigatórios em tempo de compilação e define valores padrão para todos os campos opcionais, então adicionar novos campos em versões futuras não quebrará seu 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()?;

O builder exige três campos: app_name, agent e session_service. Todo o resto é opcional e tem padrões razoáveis. O método build() só está disponível quando todos os três campos obrigatórios estiverem definidos — faltar um deles é um erro de compilação, não de execução.

Campos de configuração

CampoTipoObrigatórioDescrição
app_nameStringSimIdentificador da aplicação
agentArc<dyn Agent>SimAgente raiz a executar
session_serviceArc<dyn SessionService>SimBackend de armazenamento de sessão
artifact_serviceOption<Arc<dyn ArtifactService>>NãoArmazenamento de artefatos
memory_serviceOption<Arc<dyn Memory>>NãoMemória de longo prazo
plugin_managerOption<Arc<PluginManager>>NãoHooks de ciclo de vida de plugins
compaction_configOption<EventsCompactionConfig>NãoConfigurações de compactação de contexto
run_configOption<RunConfig>NãoOpções de execução
context_cache_configOption<ContextCacheConfig>NãoCiclo de vida do cache de contexto no nível do runner (experimental — veja abaixo)
cache_capableOption<Arc<dyn CacheCapable>>NãoReferência de modelo com suporte a cache (experimental — veja abaixo)
request_contextOption<RequestContext>NãoContexto do middleware de autenticação
cancellation_tokenOption<CancellationToken>NãoCancelamento cooperativo

Cache de prompt

O cache é uma preocupação do nível do provedor e não يحتاج configuração do Runner. Cada integração de provedor lida com isso no ponto em que a request é montada:

ProvedorMecanismoPadrão
Anthropic / Bedrockcache_control breakpointsativado (AnthropicConfig::prompt_caching, desative com with_prompt_caching(false))
OpenAIcache de prompt no lado do servidor, PromptCacheRetention para retençãoautomático
Geminicache implícito no 2.5/3.x — um prefixo compartilhado recebe desconto sem alteração de códigoautomático

Os cache hits são observáveis sem nenhuma fiação extra: a integração do Gemini registra cachedContentTokenCount em cada resposta.

context_cache_config e cache_capable são experimentais e devem ser deixados sem definição. Eles acionam o cachedContents API explícito do Gemini a partir do Runner. Esse API exige que o cache substitua system_instruction, tools e tool_config — enviar um cache junto com qualquer um deles é rejeitado com INVALID_ARGUMENT. O Runner seleciona um cache antes que o agente resolva suas ferramentas, então ele não pode montar essa solicitação, e habilitar esses campos atualmente não produz cache hits. O cache garantido (em vez de best-effort) para Gemini pertence à integração do modelo, junto com a forma como os outros provedores fazem isso.

Executando Agents

Execute um agent com entrada do usuário:

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 Conveniência para String

Para chamadas simples, run_str() aceita argumentos de &str simples e trata a conversão de newtype internamente:

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

Se a string falhar na validação (vazia, contém bytes nulos ou excede o limite de comprimento), run_str() retorna um erro antes de iniciar o loop do agent. O método run() existente com UserId/SessionId tipados permanece inalterado.

Interrupção e Isolamento de Run

Um run é registrado assim que run() retorna seu stream, e é desregistrado quando esse stream é descartado — inclusive quando é descartado sem nunca ter sido polled.

MétodoEscopo
interrupt(session_id)Cancela toda execução em andamento para esse ID de sessão, em todos os apps e usuários
interrupt_identity(app_name, user_id, session_id)Cancela execuções para uma única identidade exata
active_runs()A identidade de cada execução em andamento; uma identidade repetida significa execuções concorrentes
active_session_ids()IDs de sessão deduplicados das execuções em andamento
// Cancel one tenant's run without touching another that shares the session ID
let cancelled = runner.interrupt_identity("my-app", "user-1", "session-1");

Um ID de sessão é único apenas dentro de um app e de um usuário, então interrupt(session_id) é a forma ampla e interrupt_identity a precisa. Prefira interrupt_identity quando uma única Runner atende a mais de um app ou usuário.

As execuções são rastreadas por um ID de execução exclusivo, em vez de por ID de sessão, então duas execuções para a mesma identidade são rastreadas separadamente e cada uma se deregistra apenas a si mesma.

A Persistência é Vinculada à Identidade

Cada evento que o Runner persiste — turnos do usuário, respostas do modelo, eventos de transferência, eventos de plugin e eventos de compactação — é gravado por meio de SessionService::append_event_for_identity com o (app_name, user_id, session_id) completo. Um SessionService cuja chave natural é composta pode, portanto, vincular cada evento ao seu tenant, e pode rejeitar ou ignorar inteiramente o caminho de ID de sessão bruto append_event.

Fluxo de Execução

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

O contexto fornecido aos agentes durante a execução:

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

Opções de execução:

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

ToolExecutionStrategy

Controla como múltiplas chamadas de ferramenta de uma única resposta de LLM são despachadas:

EstratégiaComportamento
Sequential (padrão)Executa as ferramentas uma de cada vez na ordem retornada por LLM
ParallelExecuta todas as ferramentas concorrentemente; o chamador é responsável pela segurança
AutoExecute o subconjunto seguro somente leitura simultaneamente, depois todas as chamadas restantes sequencialmente

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

No modo Auto, o loop de despacho consulta tanto is_read_only() quanto is_concurrency_safe(). Chamadas cujas ferramentas selecionadas retornam true para ambos os métodos são executadas primeiro em paralelo; todas as demais chamadas são então executadas sequencialmente. Parallel contorna essas verificações de metadados como uma substituição explícita do chamador. Os resultados são sempre remontados na ordem original retornada por LLM, independentemente da estratégia. Ferramentas com falha produzem uma resposta de erro JSON sem abortar o lote.

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

Transferências de Agente

O Runner lida automaticamente com transferências entre múltiplos 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()
    });
}

O Runner irá:

  1. Detectar a solicitação de transferência no evento
  2. Encontrar o agente alvo em sub_agents
  3. Atualizar o estado da sessão com o novo agente ativo
  4. Continuar a execução com o novo agente

Compactação de Contexto

Para sessões de longa duração, ative a compactação automática de contexto para manter a janela de contexto de LLM limitada:

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

Quando a compactação é acionada, eventos mais antigos são substituídos por um evento de resumo. conversation_history() usa automaticamente o resumo em vez dos eventos originais.

Veja Compactação de Contexto para a documentação completa.

Integração com o Launcher

O 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 do Runner

Para cenários avançados, use o Runner diretamente:

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: ← Tipos Principais | Próximo: Launcher →

Runner - Documentação ADK-Rust | ADK-Rust