Runner

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

Visão geral

O Runner gerencia todo o ciclo de vida da execução de agentes:

  • Gerenciamento de sessões (criar/recuperar sessões)
  • Injeção de memória (pesquisar e injetar memórias relevantes)
  • Gerenciamento de artefatos (acesso a artefatos com escopo)
  • Transmissão de eventos (processar e encaminhar eventos)
  • Transferências entre agentes (gerenciar handoffs entre vários agentes)
Rendering architecture…

Instalação

[dependencies]
adk-runner = "2.1.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 construtor typestate para criar um Runner. O construtor impõe os campos obrigatórios em tempo de compilação e define valores padrão para todos os campos opcionais; assim, adicionar novos campos em versões futuras não interromperá 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 construtor exige três campos: app_name, agent e session_service. Todo o restante é opcional e possui valores padrão apropriados. O método build() só fica disponível quando os três campos obrigatórios são definidos — deixar um deles de fora gera um erro em tempo de compilação, não em tempo de execução.

Campos de configuração

CampoTipoObrigatórioDescrição
app_nameStringSimIdentificador da aplicação
agentArc<dyn Agent>SimAgente raiz a ser executado
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ãoGanchos do ciclo de vida do plugin
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 executor (experimental — veja abaixo)
cache_capableOption<Arc<dyn CacheCapable>>NãoReferência de modelo compatível com cache (experimental — veja abaixo)
request_contextOption<RequestContext>NãoContexto do middleware de autenticação
cancellation_tokenOption<CancellationToken>NãoCancelamento cooperativo

Cache de prompts

O armazenamento em cache é uma preocupação do nível do provedor e não requer configuração do Runner. Cada integração com o provedor lida com isso quando a solicitação é montada:

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

As ocorrências de cache são observáveis sem qualquer configuração adicional: a integração com Gemini registra cachedContentTokenCount em cada resposta.

context_cache_config e cache_capable são experimentais e devem permanecer não definidos. Eles controlam 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 de o agente resolver suas ferramentas, portanto não consegue montar essa solicitação, e habilitar esses campos atualmente não produz ocorrências de cache. O cache garantido (em vez de baseado no melhor esforço) para o Gemini deve ficar na integração do modelo, assim como os outros provedores fazem.

Executando agentes

Execute um agente com uma 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 strings

Para pontos de chamada simples, run_str() aceita argumentos &str simples e realiza a conversão do novo tipo 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 (estiver vazia, contiver bytes nulos ou exceder o limite de tamanho), run_str() retornará um erro antes de iniciar o loop do agente. O método run() existente com UserId/SessionId tipados permanece inalterado.

Interrupção e isolamento das execuções

Uma execução é registrada assim que run() retorna seu fluxo, e removida do registro quando esse fluxo é descartado — inclusive quando é descartado sem nunca ter sido consultado.

MétodoEscopo
interrupt(session_id)Cancela todas as execuções em andamento para esse ID de sessão, entre aplicativos e usuários
interrupt_identity(app_name, user_id, session_id)Cancela as execuções de uma identidade exata
active_runs()A identidade de cada execução em andamento; uma identidade repetida significa execuções simultâneas
active_session_ids()IDs de sessão sem duplicatas 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 é exclusivo apenas dentro de um app e usuário, portanto, interrupt(session_id) é a forma abrangente e interrupt_identity é a precisa. Prefira interrupt_identity quando um único Runner atender a mais de um app ou usuário.

As execuções são rastreadas por um ID de execução exclusivo, e não pelo ID da sessão, portanto, duas execuções para a mesma identidade são rastreadas separadamente e cada uma remove o registro apenas de 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 plug-ins e eventos de compactação — é gravado por meio de SessionService::append_event_for_identity com a (app_name, user_id, session_id) completa. Um SessionService cuja chave natural é composta pode, portanto, vincular cada evento ao seu tenant e rejeitar ou ignorar completamente o caminho de append_event do ID de sessão bruto.

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 várias chamadas de ferramentas de uma única resposta de LLM são despachadas:

EstratégiaComportamento
Sequential (padrão)Execute as ferramentas uma de cada vez, na ordem retornada por LLM
ParallelExecute todas as ferramentas simultaneamente; o chamador é responsável pela segurança
AutoExecute o subconjunto seguro somente para leitura simultaneamente e, em seguida, 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(). As chamadas cujas ferramentas selecionadas retornam true para ambos os métodos são executadas primeiro de forma concorrente; todas as chamadas restantes são executadas sequencialmente. Parallel ignora 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 interromper o lote.

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

Transferências entre agentes

O Runner gerencia automaticamente as transferências entre vários 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 de destino 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, habilite a compactação automática de contexto para manter limitada a janela 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),
    }),
    // ...
};

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

Consulte Compactação de contexto para obter 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 →