Callbacks
Os callbacks em ADK-Rust fornecem pontos de extensão para observar, personalizar e controlar o comportamento do agente em momentos importantes da execução. Eles permitem registro em log, proteções, armazenamento em cache, modificação de respostas e muito mais.
Visão geral
ADK-Rust é compatível com oito tipos de callbacks que interceptam diferentes etapas da execução do agente:
| Tipo de callback | Quando executado | Casos de uso |
|---|---|---|
before_agent | Antes de o agente começar o processamento | Validação de entrada, registro em log, encerramento antecipado |
after_agent | Depois que o agente conclui | Modificação da resposta, registro em log, limpeza |
before_model | Antes da chamada de LLM | Modificação da solicitação, armazenamento em cache, limitação de taxa |
after_model | Após a resposta de LLM | Filtragem da resposta, registro, armazenamento em cache |
before_tool | Antes da execução da ferramenta | Verificações de permissão, validação de parâmetros |
after_tool | Após a execução da ferramenta | Modificação do resultado, registro, inspeção de ToolOutcome |
after_tool_full | Após a execução da ferramenta (V2 avançado) | Inspecione/modifique diretamente os argumentos e a resposta da ferramenta |
on_tool_error | Após a falha da ferramenta (tentativas esgotadas) | Resultados de fallback, recuperação de erros |
Tipos de callbacks
Callbacks de agentes
Os callbacks de agentes envolvem todo o ciclo de execução do agente.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeAgentCallback type signature
type BeforeAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterAgentCallback type signature
type AfterAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Callbacks de modelos
Os callbacks de modelos interceptam solicitações e respostas de LLM.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeModelResult - controls what happens after the callback
pub enum BeforeModelResult {
Continue(LlmRequest), // Continue with (possibly modified) request
Skip(LlmResponse), // Skip model call, use this response instead
}
// BeforeModelCallback - can modify request or skip model call
type BeforeModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmRequest)
-> Pin<Box<dyn Future<Output = Result<BeforeModelResult>> + Send>>
+ Send + Sync
>;
// AfterModelCallback - can modify the response
type AfterModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmResponse)
-> Pin<Box<dyn Future<Output = Result<Option<LlmResponse>>> + Send>>
+ Send + Sync
>;
Callbacks de ferramentas
Os callbacks de ferramentas interceptam a execução de ferramentas.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeToolCallback - can skip tool by returning Some(Content)
type BeforeToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallback - can modify tool result
type AfterToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallbackFull (V2) - receives tool, args, and response
type AfterToolCallbackFull = Box<
dyn Fn(
Arc<dyn CallbackContext>,
Arc<dyn Tool>, // the tool that was executed
serde_json::Value, // args passed to the tool
serde_json::Value, // tool response (success or error JSON)
) -> Pin<Box<dyn Future<Output = Result<Option<serde_json::Value>>> + Send>>
+ Send + Sync
>;
AfterToolCallbackFull é o callback avançado pós-ferramenta da V2, alinhado ao modelo ADK do Python/Go. Diferentemente de AfterToolCallback (que recebe apenas CallbackContext), ele recebe a referência da ferramenta, os argumentos com os quais ela foi chamada e a resposta que produziu. Retorne Ok(None) para manter a resposta original ou Ok(Some(value)) para substituir a resposta da função enviada ao LLM.
Semântica dos valores de retorno
Os callbacks usam diferentes valores de retorno para controlar o fluxo de execução:
Callbacks de agentes/ferramentas
| Valor de retorno | Efeito |
|---|---|
Ok(None) | Continuar execução normal |
Ok(Some(content)) | Substituir/ignorar com o conteúdo fornecido |
Err(e) | Abortar a execução com erro |
Callbacks do modelo
BeforeModelCallback usa BeforeModelResult:
| Valor de retorno | Efeito |
|---|---|
Ok(BeforeModelResult::Continue(request)) | Continuar com a solicitação (possivelmente modificada) |
Ok(BeforeModelResult::Skip(response)) | Ignorar a chamada ao modelo e usar esta resposta |
Err(e) | Abortar a execução com erro |
AfterModelCallback usa Option<LlmResponse>:
| Valor de retorno | Efeito |
|---|---|
Ok(None) | Mantém a resposta original |
Ok(Some(response)) | Substitui pela resposta modificada |
Err(e) | Abortar a execução com erro |
Resumo
- Antes dos callbacks do agente/ferramenta: Retorne
Nonepara continuar,Some(content)para ignorar - Antes do callback do modelo: Retorne
Continue(request)para prosseguir,Skip(response)para ignorar o modelo - Após os callbacks: Retorne
Nonepara manter o original,Some(...)para substituir
Adicionando callbacks a agentes
Os callbacks são adicionados aos agentes usando LlmAgentBuilder:
use adk_rust::prelude::*;
use std::sync::Arc;
#[tokio::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let api_key = std::env::var("GOOGLE_API_KEY")?;
let model = Arc::new(GeminiModel::new(&api_key, "gemini-3.7-flash")?);
let agent = LlmAgentBuilder::new("my_agent")
.model(model)
.instruction("You are a helpful assistant.")
// Add before_agent callback
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent starting: {}", ctx.agent_name());
Ok(None) // Continue execution
})
}))
// Add after_agent callback
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent completed: {}", ctx.agent_name());
Ok(None) // Keep original result
})
}))
.build()?;
Ok(())
}
Interface CallbackContext
O trait CallbackContext fornece acesso ao contexto de execução:
use adk_rust::prelude::*;
#[async_trait]
pub trait CallbackContext: ReadonlyContext {
/// Access artifact storage (if configured)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
/// Structured metadata about the most recent tool execution.
/// Available in after-tool callbacks. Returns `None` outside tool context.
fn tool_outcome(&self) -> Option<ToolOutcome> { None }
/// Name of the tool being executed.
/// Available in before-tool and after-tool callbacks via `ToolCallbackContext`.
fn tool_name(&self) -> Option<&str> { None }
/// Input arguments for the tool being executed.
/// Available in before-tool and after-tool callbacks via `ToolCallbackContext`.
fn tool_input(&self) -> Option<&serde_json::Value> { None }
}
// CallbackContext extends ReadonlyContext
#[async_trait]
pub trait ReadonlyContext: Send + Sync {
/// Current invocation ID
fn invocation_id(&self) -> &str;
/// Name of the current agent
fn agent_name(&self) -> &str;
/// User ID from session
fn user_id(&self) -> &str;
/// Application name
fn app_name(&self) -> &str;
/// Session ID
fn session_id(&self) -> &str;
/// Current branch (for multi-agent)
fn branch(&self) -> &str;
/// The user's input content
fn user_content(&self) -> &Content;
}
ToolOutcome
ToolOutcome contém metadados estruturados sobre uma execução de ferramenta concluída. Ele está disponível por meio de ctx.tool_outcome() nos callbacks após a ferramenta:
pub struct ToolOutcome {
pub tool_name: String,
pub tool_args: serde_json::Value,
pub success: bool,
pub duration: std::time::Duration,
pub error_message: Option<String>,
pub attempt: u32, // 0-based retry attempt number
}
O campo success é derivado do caminho Rust Result, não da inspeção do conteúdo de JSON. Uma ferramenta que retorna Ok(json!({"error": "..."})) terá success: true.
Padrões comuns
Callback de registro
Registre todas as interações do agente:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("logged_agent")
.model(model)
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[LOG] Agent '{}' starting", ctx.agent_name());
println!("[LOG] Session: {}", ctx.session_id());
println!("[LOG] User: {}", ctx.user_id());
Ok(None)
})
}))
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[LOG] Agent '{}' completed", ctx.agent_name());
Ok(None)
})
}))
.build()?;
Barreiras de entrada
Bloqueie conteúdo inadequado antes do processamento:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("guarded_agent")
.model(model)
.before_callback(Box::new(|ctx| {
Box::pin(async move {
// Check user input for blocked content
let user_content = ctx.user_content();
for part in &user_content.parts {
if let Part::Text { text } = part {
if text.to_lowercase().contains("blocked_word") {
// Return early with rejection message
return Ok(Some(Content {
role: "model".to_string(),
parts: vec![Part::Text {
text: "I cannot process that request.".to_string(),
}],
}));
}
}
}
Ok(None) // Continue normal execution
})
}))
.build()?;
Armazenamento em cache de respostas (antes do modelo)
Armazene em cache as respostas de LLM para reduzir as chamadas de API:
use adk_rust::prelude::*;
use std::sync::Arc;
use std::collections::HashMap;
use std::sync::Mutex;
// Simple in-memory cache
let cache: Arc<Mutex<HashMap<String, LlmResponse>>> = Arc::new(Mutex::new(HashMap::new()));
let cache_clone = cache.clone();
let agent = LlmAgentBuilder::new("cached_agent")
.model(model)
.before_model_callback(Box::new(move |ctx, request| {
let cache = cache_clone.clone();
Box::pin(async move {
// Create cache key from request contents
let key = format!("{:?}", request.contents);
// Check cache
if let Some(cached) = cache.lock().unwrap().get(&key) {
println!("[CACHE] Hit for request");
return Ok(BeforeModelResult::Skip(cached.clone()));
}
println!("[CACHE] Miss, calling model");
Ok(BeforeModelResult::Continue(request)) // Continue to model
})
}))
.build()?;
Injeção de conteúdo multimodal (antes do modelo)
Injete imagens ou outro conteúdo binário nas solicitações de LLM para análise multimodal:
use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, LoadRequest};
use std::sync::Arc;
// Artifact service with pre-loaded image
let artifact_service: Arc<dyn ArtifactService> = /* ... */;
let callback_service = artifact_service.clone();
let agent = LlmAgentBuilder::new("image_analyst")
.model(model)
.instruction("Describe the image provided by the user.")
.before_model_callback(Box::new(move |_ctx, mut request| {
let service = callback_service.clone();
Box::pin(async move {
// Load image from artifact storage
if let Ok(response) = service.load(LoadRequest {
app_name: "my_app".to_string(),
user_id: "user".to_string(),
session_id: "session".to_string(),
file_name: "user:photo.png".to_string(),
version: None,
}).await {
// Inject image into the user's message
if let Some(last_content) = request.contents.last_mut() {
if last_content.role == "user" {
last_content.parts.push(response.part);
}
}
}
Ok(BeforeModelResult::Continue(request))
})
}))
.build()?;
Esse padrão é essencial para IA multimodal porque as respostas das ferramentas são texto JSON - o modelo não consegue "ver" as imagens retornadas pelas ferramentas. Ao injetar a imagem diretamente na solicitação, o modelo recebe os dados reais da imagem.
Modificação de respostas (após o modelo)
Modifique ou filtre as respostas do modelo:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("filtered_agent")
.model(model)
.after_model_callback(Box::new(|ctx, mut response| {
Box::pin(async move {
// Modify the response content
if let Some(ref mut content) = response.content {
for part in &mut content.parts {
if let Part::Text { text } = part {
// Add disclaimer to all responses
*text = format!("{}\n\n[AI-generated response]", text);
}
}
}
Ok(Some(response))
})
}))
.build()?;
Verificação de permissões da ferramenta (antes da ferramenta)
Valide as permissões de execução da ferramenta:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("permission_agent")
.model(model)
.tool(Arc::new(GoogleSearchTool::new()))
.before_tool_callback(Box::new(|ctx| {
Box::pin(async move {
// Access tool name and input via ToolCallbackContext
if let Some(name) = ctx.tool_name() {
println!("About to execute tool: {}", name);
}
if let Some(input) = ctx.tool_input() {
println!("Tool input: {}", input);
}
// Check if user has permission for tools
let user_id = ctx.user_id();
// Example: block certain users from using tools
if user_id == "restricted_user" {
return Ok(Some(Content {
role: "function".to_string(),
parts: vec![Part::Text {
text: "Tool access denied for this user.".to_string(),
}],
}));
}
Ok(None) // Allow tool execution
})
}))
.build()?;
Registro do resultado da ferramenta (após a ferramenta)
Registre todas as execuções de ferramentas:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("tool_logged_agent")
.model(model)
.tool(Arc::new(GoogleSearchTool::new()))
.after_tool_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[TOOL LOG] Tool executed for agent: {}", ctx.agent_name());
println!("[TOOL LOG] Session: {}", ctx.session_id());
// Inspect structured outcome metadata
if let Some(outcome) = ctx.tool_outcome() {
println!(
"[TOOL LOG] {} {} in {:?}",
outcome.tool_name,
if outcome.success { "OK" } else { "FAIL" },
outcome.duration,
);
}
Ok(None) // Keep original result
})
}))
.build()?;
Fallback de erro da ferramenta (em caso de erro da ferramenta)
Forneça um resultado alternativo quando uma ferramenta falhar:
use adk_rust::prelude::*;
use serde_json::json;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("fallback_agent")
.model(model)
.tool(Arc::new(my_tool))
.on_tool_error(Box::new(|_ctx, tool, _args, error| {
Box::pin(async move {
// Return a fallback result instead of the error
if tool.name() == "weather_api" {
Ok(Some(json!({ "error": "Weather service unavailable", "fallback": true })))
} else {
Ok(None) // No fallback — propagate original error to LLM
}
})
}))
.build()?;
O callback on_tool_error é invocado após o esgotamento das tentativas (se um orçamento de novas tentativas estiver configurado). Retorne Some(value) para substituir por um fallback ou None para permitir que o erro original chegue ao LLM.
Inspeção avançada após a ferramenta (V2)
Inspecione e, opcionalmente, modifique os resultados da ferramenta com o contexto completo:
use adk_rust::prelude::*;
use serde_json::json;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("audited_agent")
.model(model)
.tool(Arc::new(my_tool))
.after_tool_callback_full(Box::new(|_ctx, tool, args, response| {
Box::pin(async move {
// Log the full tool execution details
println!(
"[AUDIT] Tool '{}' called with {} returned {}",
tool.name(), args, response
);
// Optionally modify the response
if tool.name() == "sensitive_api" {
// Redact sensitive fields before the LLM sees them
let mut redacted = response.clone();
if let Some(obj) = redacted.as_object_mut() {
obj.remove("secret_token");
}
Ok(Some(redacted))
} else {
Ok(None) // Keep original response
}
})
}))
.build()?;
after_tool_callback_full é executado após a cadeia legada after_tool_callback. Ambos podem coexistir no mesmo agente.
Vários callbacks
Você pode adicionar vários callbacks do mesmo tipo. Eles são executados na ordem:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("multi_callback_agent")
.model(model)
// First before callback - logging
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[1] Logging callback");
Ok(None)
})
}))
// Second before callback - validation
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[2] Validation callback");
Ok(None)
})
}))
.build()?;
Quando um callback retorna Some(content), os callbacks subsequentes do mesmo tipo são ignorados.
Tratamento de erros
Os callbacks podem retornar erros para abortar a execução:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("error_handling_agent")
.model(model)
.before_callback(Box::new(|ctx| {
Box::pin(async move {
// Validate something critical
if ctx.user_id().is_empty() {
return Err(AdkError::agent("User ID is required"));
}
Ok(None)
})
}))
.build()?;
Práticas recomendadas
- Mantenha os callbacks leves: evite computação pesada nos callbacks
- Trate os erros adequadamente: retorne mensagens de erro significativas
- Use o registro de logs com moderação: registrar logs em excesso pode afetar o desempenho
- Use o cache com critério: considere estratégias de invalidação de cache
- Teste os callbacks de forma independente: faça testes unitários da lógica dos callbacks separadamente
Relacionado
- LlmAgent - Configuração do agente
- Ferramentas - Sistema de ferramentas
- Eventos - Estrutura de eventos
Anterior: ← Gerenciamento de estado | Próximo: Artefatos →