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)
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)?;
RunnerConfigBuilder (Recomendado)
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
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
app_name | String | Sim | Identificador da aplicação |
agent | Arc<dyn Agent> | Sim | Agente raiz a executar |
session_service | Arc<dyn SessionService> | Sim | Backend de armazenamento de sessão |
artifact_service | Option<Arc<dyn ArtifactService>> | Não | Armazenamento de artefatos |
memory_service | Option<Arc<dyn Memory>> | Não | Memória de longo prazo |
plugin_manager | Option<Arc<PluginManager>> | Não | Hooks de ciclo de vida de plugins |
compaction_config | Option<EventsCompactionConfig> | Não | Configurações de compactação de contexto |
run_config | Option<RunConfig> | Não | Opções de execução |
context_cache_config | Option<ContextCacheConfig> | Não | Ciclo de vida do cache de contexto no nível do runner (experimental — veja abaixo) |
cache_capable | Option<Arc<dyn CacheCapable>> | Não | Referência de modelo com suporte a cache (experimental — veja abaixo) |
request_context | Option<RequestContext> | Não | Contexto do middleware de autenticação |
cancellation_token | Option<CancellationToken> | Não | Cancelamento 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:
| Provedor | Mecanismo | Padrão |
|---|---|---|
| Anthropic / Bedrock | cache_control breakpoints | ativado (AnthropicConfig::prompt_caching, desative com with_prompt_caching(false)) |
| OpenAI | cache de prompt no lado do servidor, PromptCacheRetention para retenção | automático |
| Gemini | cache implícito no 2.5/3.x — um prefixo compartilhado recebe desconto sem alteração de código | automá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_configecache_capablesão experimentais e devem ser deixados sem definição. Eles acionam ocachedContentsAPI explícito do Gemini a partir do Runner. Esse API exige que o cache substituasystem_instruction,toolsetool_config— enviar um cache junto com qualquer um deles é rejeitado comINVALID_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étodo | Escopo |
|---|---|
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égia | Comportamento |
|---|---|
Sequential (padrão) | Executa as ferramentas uma de cada vez na ordem retornada por LLM |
Parallel | Executa todas as ferramentas concorrentemente; o chamador é responsável pela segurança |
Auto | Execute 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á:
- Detectar a solicitação de transferência no evento
- Encontrar o agente alvo em sub_agents
- Atualizar o estado da sessão com o novo agente ativo
- 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 →