Rappels
Les Callbacks (rappels) dans adk-rust fournissent des points d'ancrage pour observer, personnaliser et contrôler le comportement des agents à des points d'exécution clés. Ils permettent la journalisation, les garde-fous, la mise en cache, la modification des réponses, et bien plus encore.
Aperçu
adk-rust prend en charge six types de Callbacks qui interceptent différentes étapes de l'exécution d'un agent :
| Type de Callback | Quand Exécuté | Cas d'utilisation |
|---|---|---|
before_agent | Avant que l'Agent ne commence le traitement | Validation de l'entrée, journalisation, terminaison anticipée |
after_agent | Après que l'Agent ait terminé | Modification de la réponse, journalisation, nettoyage |
before_model | Avant l'appel LLM | Modification de la requête, mise en cache, limitation de débit |
after_model | Après la réponse LLM | Filtrage de la réponse, journalisation, mise en cache |
before_tool | Avant l'exécution du Tool | Vérifications des permissions, validation des paramètres |
after_tool | Après l'exécution du Tool | Modification du résultat, journalisation |
Types de Callbacks
Callbacks d'Agent
Les Callbacks d'Agent enveloppent l'intégralité du cycle d'exécution de l'Agent.
use adk_rust::prelude::*;
use std::sync::Arc;
// Signature du type BeforeAgentCallback
type BeforeAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// Signature du type AfterAgentCallback
type AfterAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Callbacks de Modèle
Les Callbacks de Modèle interceptent les requêtes et les réponses LLM.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeModelResult - contrôle ce qui se passe après le callback
pub enum BeforeModelResult {
Continue(LlmRequest), // Continuer avec la requête (éventuellement modifiée)
Skip(LlmResponse), // Ignorer l'appel au modèle, utiliser cette réponse à la place
}
// BeforeModelCallback - peut modifier la requête ou ignorer l'appel au modèle
type BeforeModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmRequest)
-> Pin<Box<dyn Future<Output = Result<BeforeModelResult>> + Send>>
+ Send + Sync
>;
// AfterModelCallback - peut modifier la réponse
type AfterModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmResponse)
-> Pin<Box<dyn Future<Output = Result<Option<LlmResponse>>> + Send>>
+ Send + Sync
>;
Callbacks de Tool
Les Callbacks de Tool interceptent l'exécution du Tool.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeToolCallback - peut ignorer le Tool en renvoyant Some(Content)
type BeforeToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallback - peut modifier le résultat du Tool
type AfterToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Sémantique des valeurs de retour
Les callbacks utilisent différentes valeurs de retour pour contrôler le flux d'exécution :
Callbacks d'Agent/Outil
| Valeur de retour | Effet |
|---|---|
Ok(None) | Poursuit l'exécution normale |
Ok(Some(content)) | Remplace/saute avec le contenu fourni |
Err(e) | Annule l'exécution avec une erreur |
Callbacks de Modèle
BeforeModelCallback utilise BeforeModelResult :
| Valeur de retour | Effet |
|---|---|
Ok(BeforeModelResult::Continue(request)) | Poursuit avec la requête (éventuellement modifiée) |
Ok(BeforeModelResult::Skip(response)) | Ignore l'appel au modèle, utilise cette réponse à la place |
Err(e) | Annule l'exécution avec une erreur |
AfterModelCallback utilise Option<LlmResponse> :
| Valeur de retour | Effet |
|---|---|
Ok(None) | Conserve la réponse originale |
Ok(Some(response)) | Remplace par la réponse modifiée |
Err(e) | Annule l'exécution avec une erreur |
Résumé
- Avant les callbacks d'agent/outil : Retourne
Nonepour continuer,Some(content)pour ignorer - Callback avant le modèle : Retourne
Continue(request)pour procéder,Skip(response)pour contourner le modèle - Après les callbacks : Retourne
Nonepour conserver l'original,Some(...)pour remplacer
Ajouter des Callbacks aux Agents
Les callbacks sont ajoutés aux agents à l'aide du 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.")
// Ajoute un callback before_agent
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent starting: {}", ctx.agent_name());
Ok(None) // Poursuit l'exécution
})
}))
// Ajoute un callback after_agent
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent completed: {}", ctx.agent_name());
Ok(None) // Conserve le résultat original
})
}))
.build()?;
Ok(())
}
Interface CallbackContext
Le trait CallbackContext fournit l'accès au contexte d'exécution :
use adk_rust::prelude::*;
#[async_trait]
pub trait CallbackContext: ReadonlyContext {
/// Accède au stockage d'artefacts (si configuré)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
// CallbackContext étend ReadonlyContext
#[async_trait]
pub trait ReadonlyContext: Send + Sync {
/// ID d'invocation actuel
fn invocation_id(&self) -> &str;
/// Nom de l'agent actuel
fn agent_name(&self) -> &str;
/// ID utilisateur de la session
fn user_id(&self) -> &str;
/// Nom de l'application
fn app_name(&self) -> &str;
/// ID de session
fn session_id(&self) -> &str;
/// Branche actuelle (pour multi-agent)
fn branch(&self) -> &str;
/// Contenu d'entrée de l'utilisateur
fn user_content(&self) -> &Content;
}
Modèles courants
Rappel de journalisation
Journaliser toutes les interactions de l'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!("[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()?;
Garde-fous d'entrée
Bloquer le contenu inapproprié avant le traitement :
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()?;
Mise en cache des réponses (avant le modèle)
Mettre en cache les réponses du LLM pour réduire les appels 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()?;
Injection de contenu multimodal (avant le modèle)
Injecter des images ou d'autres contenus binaires dans les requêtes LLM pour une analyse multimodale :
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()?;
Ce modèle est essentiel pour l'IA multimodale car les réponses des outils sont du texte JSON - le modèle ne peut pas "voir" les images renvoyées par les outils. En injectant l'image directement dans la requête, le modèle reçoit des données d'image réelles.
Modification de la réponse (après le modèle)
Modifier ou filtrer les réponses du modèle :
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()?;
Vérification des permissions de l'outil (avant l'outil)
Valider les permissions d'exécution de l'outil :
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 {
// 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()?;
Journalisation du résultat de l'outil (après l'outil)
Journaliser toutes les exécutions d'outils :
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());
Ok(None) // Keep original result
})
}))
.build()?;
Rappels Multiples
Vous pouvez ajouter plusieurs rappels du même type. Ils s'exécutent dans l'ordre :
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()?;
Lorsqu'un rappel retourne Some(content), les rappels subséquents du même type sont ignorés.
Gestion des Erreurs
Les rappels peuvent retourner des erreurs pour annuler l'exécution :
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".to_string()));
}
Ok(None)
})
}))
.build()?;
Bonnes Pratiques
- Maintenez les rappels légers : Évitez les calculs lourds dans les rappels
- Gérez les erreurs de manière élégante : Retournez des messages d'erreur significatifs
- Utilisez la journalisation avec parcimonie : Une trop grande quantité de journalisation peut affecter les performances
- Utilisez le cache avec sagesse : Envisagez des stratégies d'invalidation du cache
- Testez les rappels indépendamment : Testez la logique des rappels séparément
Liens Connexes
Précédent : ← Gestion de l'état | Suivant : Artefacts →