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 callbackQuando executadoCasos de uso
before_agentAntes de o agente começar o processamentoValidação de entrada, registro em log, encerramento antecipado
after_agentDepois que o agente concluiModificação da resposta, registro em log, limpeza
before_modelAntes da chamada de LLMModificação da solicitação, armazenamento em cache, limitação de taxa
after_modelApós a resposta de LLMFiltragem da resposta, registro, armazenamento em cache
before_toolAntes da execução da ferramentaVerificações de permissão, validação de parâmetros
after_toolApós a execução da ferramentaModificação do resultado, registro, inspeção de ToolOutcome
after_tool_fullApós a execução da ferramenta (V2 avançado)Inspecione/modifique diretamente os argumentos e a resposta da ferramenta
on_tool_errorApó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 retornoEfeito
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 retornoEfeito
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 retornoEfeito
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 None para 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 None para 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

  1. Mantenha os callbacks leves: evite computação pesada nos callbacks
  2. Trate os erros adequadamente: retorne mensagens de erro significativas
  3. Use o registro de logs com moderação: registrar logs em excesso pode afetar o desempenho
  4. Use o cache com critério: considere estratégias de invalidação de cache
  5. Teste os callbacks de forma independente: faça testes unitários da lógica dos callbacks separadamente

Anterior: ← Gerenciamento de estado | Próximo: Artefatos →

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