Retrollamadas
Las retrollamadas en adk-rust proporcionan ganchos para observar, personalizar y controlar el comportamiento del agente en puntos clave de ejecución. Permiten el registro, los mecanismos de protección, el almacenamiento en caché, la modificación de respuestas y más.
Resumen
adk-rust admite seis tipos de retrollamadas que interceptan diferentes etapas de la ejecución del agente:
| Tipo de Retrollamada | Cuándo se Ejecuta | Casos de Uso |
|---|---|---|
before_agent | Antes de que el agente comience a procesar | Validación de entrada, registro, terminación temprana |
after_agent | Después de que el agente finalice | Modificación de respuesta, registro, limpieza |
before_model | Antes de la llamada a LLM | Modificación de solicitud, almacenamiento en caché, limitación de tasa |
after_model | Después de la respuesta de LLM | Filtrado de respuesta, registro, almacenamiento en caché |
before_tool | Antes de la ejecución de la herramienta | Comprobaciones de permisos, validación de parámetros |
after_tool | Después de la ejecución de la herramienta | Modificación de resultado, registro |
Tipos de Retrollamadas
Retrollamadas del Agente
Las retrollamadas del agente envuelven el ciclo completo de ejecución del agente.
use adk_rust::prelude::*;
use std::sync::Arc;
// Firma del tipo BeforeAgentCallback
type BeforeAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// Firma del tipo AfterAgentCallback
type AfterAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Retrollamadas del Modelo
Las retrollamadas del modelo interceptan las solicitudes y respuestas de LLM.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeModelResult - controla lo que sucede después de la retrollamada
pub enum BeforeModelResult {
Continue(LlmRequest), // Continuar con la solicitud (posiblemente modificada)
Skip(LlmResponse), // Omitir la llamada al modelo, usar esta respuesta en su lugar
}
// BeforeModelCallback - puede modificar la solicitud u omitir la llamada al modelo
type BeforeModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmRequest)
-> Pin<Box<dyn Future<Output = Result<BeforeModelResult>> + Send>>
+ Send + Sync
>;
// AfterModelCallback - puede modificar la respuesta
type AfterModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmResponse)
-> Pin<Box<dyn Future<Output = Result<Option<LlmResponse>>> + Send>>
+ Send + Sync
>;
Retrollamadas de la Herramienta
Las retrollamadas de la herramienta interceptan la ejecución de la herramienta.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeToolCallback - puede omitir la herramienta devolviendo Some(Content)
type BeforeToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallback - puede modificar el resultado de la herramienta
type AfterToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Semántica de los Valores de Retorno
Las retrollamadas (callbacks) utilizan diferentes valores de retorno para controlar el flujo de ejecución:
Retrollamadas de Agent/Tool
| Valor de Retorno | Efecto |
|---|---|
Ok(None) | Continuar ejecución normal |
Ok(Some(content)) | Sobrescribir/omitir con el contenido proporcionado |
Err(e) | Abortar ejecución con error |
Retrollamadas de Model
BeforeModelCallback utiliza BeforeModelResult:
| Valor de Retorno | Efecto |
|---|---|
Ok(BeforeModelResult::Continue(request)) | Continuar con la solicitud (posiblemente modificada) |
Ok(BeforeModelResult::Skip(response)) | Omitir la llamada al modelo, usar esta respuesta en su lugar |
Err(e) | Abortar ejecución con error |
AfterModelCallback utiliza Option<LlmResponse>:
| Valor de Retorno | Efecto |
|---|---|
Ok(None) | Mantener la respuesta original |
Ok(Some(response)) | Reemplazar con la respuesta modificada |
Err(e) | Abortar ejecución con error |
Resumen
- Retrollamadas antes de agent/tool: Retornar
Nonepara continuar,Some(content)para omitir - Retrollamada antes del modelo: Retornar
Continue(request)para proceder,Skip(response)para omitir el modelo - Retrollamadas después: Retornar
Nonepara mantener el original,Some(...)para reemplazar
Añadiendo Retrollamadas a los Agents
Las retrollamadas se añaden a los agents usando el 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-2.5-flash")?);
let agent = LlmAgentBuilder::new("my_agent")
.model(model)
.instruction("You are a helpful assistant.")
// Añadir retrollamada before_agent
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent starting: {}", ctx.agent_name());
Ok(None) // Continuar ejecución
})
}))
// Añadir retrollamada after_agent
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent completed: {}", ctx.agent_name());
Ok(None) // Mantener resultado original
})
}))
.build()?;
Ok(())
}
Interfaz CallbackContext
El trait CallbackContext proporciona acceso al contexto de ejecución:
use adk_rust::prelude::*;
#[async_trait]
pub trait CallbackContext: ReadonlyContext {
/// Accede al almacenamiento de artefactos (si está configurado)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
// CallbackContext extiende ReadonlyContext
#[async_trait]
pub trait ReadonlyContext: Send + Sync {
/// ID de invocación actual
fn invocation_id(&self) -> &str;
/// Nombre del agent actual
fn agent_name(&self) -> &str;
/// ID de usuario de la sesión
fn user_id(&self) -> &str;
/// Nombre de la aplicación
fn app_name(&self) -> &str;
/// ID de sesión
fn session_id(&self) -> &str;
/// Rama actual (para multi-agent)
fn branch(&self) -> &str;
/// El contenido de entrada del usuario
fn user_content(&self) -> &Content;
}
Patrones Comunes
Callback de Registro
Registra todas las interacciones del agent:
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!("[REGISTRO] Agent '{}' iniciando", ctx.agent_name());
println!("[REGISTRO] Sesión: {}", ctx.session_id());
println!("[REGISTRO] Usuario: {}", ctx.user_id());
Ok(None)
})
}))
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[REGISTRO] Agent '{}' completado", ctx.agent_name());
Ok(None)
})
}))
.build()?;
Controles de Entrada
Bloquear contenido inapropiado antes del procesamiento:
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 {
// Verificar entrada del usuario en busca de contenido bloqueado
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") {
// Retornar anticipadamente con mensaje de rechazo
return Ok(Some(Content {
role: "model".to_string(),
parts: vec![Part::Text {
text: "No puedo procesar esa solicitud.".to_string(),
}],
}));
}
}
}
Ok(None) // Continuar ejecución normal
})
}))
.build()?;
Caché de Respuestas (Antes del Modelo)
Almacenar en caché las respuestas del LLM para reducir las llamadas a la API:
use adk_rust::prelude::*;
use std::sync::Arc;
use std::collections::HashMap;
use std::sync::Mutex;
// Caché simple en memoria
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 {
// Crear clave de caché a partir de los contenidos de la solicitud
let key = format!("{:?}", request.contents);
// Verificar caché
if let Some(cached) = cache.lock().unwrap().get(&key) {
println!("[CACHÉ] Acierto para la solicitud");
return Ok(BeforeModelResult::Skip(cached.clone()));
}
println!("[CACHÉ] Fallo, llamando al modelo");
Ok(BeforeModelResult::Continue(request)) // Continuar al modelo
})
}))
.build()?;
Inyección de Contenido Multimodal (Antes del Modelo)
Inyectar imágenes u otro contenido binario en las solicitudes del LLM para análisis multimodal:
use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, LoadRequest};
use std::sync::Arc;
// Servicio de artefactos con imagen precargada
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 {
// Cargar imagen del almacenamiento de artefactos
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 {
// Inyectar imagen en el mensaje del usuario
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()?;
Este patrón es esencial para la IA multimodal porque las respuestas de las tools son texto JSON; el modelo no puede "ver" las imágenes devueltas por las tools. Al inyectar la imagen directamente en la solicitud, el modelo recibe datos de imagen reales.
Modificación de Respuesta (Después del Modelo)
Modificar o filtrar respuestas del 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 {
// Modificar el contenido de la respuesta
if let Some(ref mut content) = response.content {
for part in &mut content.parts {
if let Part::Text { text } = part {
// Añadir descargo de responsabilidad a todas las respuestas
*text = format!("{}\n\n[Respuesta generada por IA]", text);
}
}
}
Ok(Some(response))
})
}))
.build()?;
Verificación de Permisos de Tool (Antes de la Tool)
Validar permisos de ejecución de tools:
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 {
// Verificar si el usuario tiene permiso para las tools
let user_id = ctx.user_id();
// Ejemplo: bloquear a ciertos usuarios para que no usen las tools
if user_id == "restricted_user" {
return Ok(Some(Content {
role: "function".to_string(),
parts: vec![Part::Text {
text: "Acceso a la tool denegado para este usuario.".to_string(),
}],
}));
}
Ok(None) // Permitir la ejecución de la tool
})
}))
.build()?;
Registro de Resultados de Tool (Después de la Tool)
Registrar todas las ejecuciones de tools:
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!("[REGISTRO DE TOOL] Tool ejecutada para el agent: {}", ctx.agent_name());
println!("[REGISTRO DE TOOL] Sesión: {}", ctx.session_id());
Ok(None) // Mantener el resultado original
})
}))
.build()?;
Múltiples Callbacks
Puedes añadir múltiples callbacks del mismo tipo. Se ejecutan en orden:
use adk_rust::prelude::*;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("multi_callback_agent")
.model(model)
// Primer callback "before" - registro
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[1] Logging callback");
Ok(None)
})
}))
// Segundo callback "before" - validación
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("[2] Validation callback");
Ok(None)
})
}))
.build()?;
Cuando un callback devuelve Some(content), los callbacks subsiguientes del mismo tipo se omiten.
Manejo de Errores
Los callbacks pueden devolver errores para abortar la ejecución:
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 {
// Validar algo crítico
if ctx.user_id().is_empty() {
return Err(AdkError::Agent("User ID is required".to_string()));
}
Ok(None)
})
}))
.build()?;
Mejores Prácticas
- Mantén los callbacks ligeros: Evita computaciones pesadas en los callbacks
- Maneja los errores con elegancia: Devuelve mensajes de error significativos
- Usa el registro con moderación: Demasiado registro puede afectar el rendimiento
- Usa la caché sabiamente: Considera estrategias de invalidación de caché
- Prueba los callbacks de forma independiente: Realiza pruebas unitarias de la lógica del callback por separado
Relacionado
Anterior: ← State Management | Siguiente: Artifacts →