Callbacks
Callbacks in adk-rust bieten Hooks, um das Agentenverhalten an wichtigen Ausführungspunkten zu beobachten, anzupassen und zu steuern. Sie ermöglichen Protokollierung, Schutzmechanismen, Caching, Antwortmodifikation und mehr.
Übersicht
adk-rust unterstützt sechs Callback-Typen, die verschiedene Phasen der Agentenausführung abfangen:
| Callback-Typ | Wann ausgeführt | Anwendungsfälle |
|---|---|---|
before_agent | Bevor der Agent mit der Verarbeitung beginnt | Eingabevalidierung, Protokollierung, vorzeitige Beendigung |
after_agent | Nachdem der Agent abgeschlossen hat | Antwortmodifikation, Protokollierung, Bereinigung |
before_model | Vor dem LLM-Aufruf | Anforderungsmodifikation, Caching, Ratenbegrenzung |
after_model | Nach der LLM-Antwort | Antwortfilterung, Protokollierung, Caching |
before_tool | Vor der Tool-Ausführung | Berechtigungsprüfungen, Parametervalidierung |
after_tool | Nach der Tool-Ausführung | Ergebnismodifikation, Protokollierung |
Callback-Typen
Agenten-Callbacks
Agenten-Callbacks umfassen den gesamten Agenten-Ausführungszyklus.
use adk_rust::prelude::*;
use std::sync::Arc;
// Typensignatur für BeforeAgentCallback
type BeforeAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// Typensignatur für AfterAgentCallback
type AfterAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Modell-Callbacks
Modell-Callbacks fangen LLM-Anfragen und -Antworten ab.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeModelResult - steuert, was nach dem Callback geschieht
pub enum BeforeModelResult {
Continue(LlmRequest), // Mit (möglicherweise modifizierter) Anfrage fortfahren
Skip(LlmResponse), // Modellaufruf überspringen, stattdessen diese Antwort verwenden
}
// BeforeModelCallback - kann Anfrage modifizieren oder Modellaufruf überspringen
type BeforeModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmRequest)
-> Pin<Box<dyn Future<Output = Result<BeforeModelResult>> + Send>>
+ Send + Sync
>;
// AfterModelCallback - kann die Antwort modifizieren
type AfterModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmResponse)
-> Pin<Box<dyn Future<Output = Result<Option<LlmResponse>>> + Send>>
+ Send + Sync
>;
Tool-Callbacks
Tool-Callbacks fangen die Tool-Ausführung ab.
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeToolCallback - kann Tool überspringen, indem Some(Content) zurückgegeben wird
type BeforeToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallback - kann das Tool-Ergebnis modifizieren
type AfterToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
Semantik der Rückgabewerte
Callbacks verwenden unterschiedliche Rückgabewerte, um den Ausführungsfluss zu steuern:
Agent/Tool-Callbacks
| Rückgabewert | Effekt |
|---|---|
Ok(None) | Setzt die normale Ausführung fort |
Ok(Some(content)) | Überschreibt/überspringt mit bereitgestelltem Inhalt |
Err(e) | Bricht die Ausführung mit einem Fehler ab |
Model-Callbacks
BeforeModelCallback verwendet BeforeModelResult:
| Rückgabewert | Effekt |
|---|---|
Ok(BeforeModelResult::Continue(request)) | Setzt mit der (möglicherweise geänderten) Anfrage fort |
Ok(BeforeModelResult::Skip(response)) | Überspringt den Model-Aufruf, verwendet stattdessen diese Antwort |
Err(e) | Bricht die Ausführung mit einem Fehler ab |
AfterModelCallback verwendet Option<LlmResponse>:
| Rückgabewert | Effekt |
|---|---|
Ok(None) | Behält die ursprüngliche Antwort bei |
Ok(Some(response)) | Ersetzt durch die modifizierte Antwort |
Err(e) | Bricht die Ausführung mit einem Fehler ab |
Zusammenfassung
- Vor Agent/Tool-Callbacks: Geben Sie
Nonezurück, um fortzufahren,Some(content), um zu überspringen - Vor Model-Callback: Geben Sie
Continue(request)zurück, um fortzufahren,Skip(response), um das Model zu umgehen - Nach Callbacks: Geben Sie
Nonezurück, um das Original beizubehalten,Some(...), um es zu ersetzen
Hinzufügen von Callbacks zu Agents
Callbacks werden Agents mit dem LlmAgentBuilder hinzugefügt:
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.")
// Callback vor Agent hinzufügen
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent starting: {}", ctx.agent_name());
Ok(None) // Ausführung fortsetzen
})
}))
// Callback nach Agent hinzufügen
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent completed: {}", ctx.agent_name());
Ok(None) // Ursprüngliches Ergebnis beibehalten
})
}))
.build()?;
Ok(())
}
CallbackContext-Schnittstelle
Der CallbackContext-Trait ermöglicht den Zugriff auf den Ausführungskontext:
use adk_rust::prelude::*;
#[async_trait]
pub trait CallbackContext: ReadonlyContext {
/// Zugriff auf Artefakt-Speicher (falls konfiguriert)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
// CallbackContext erweitert ReadonlyContext
#[async_trait]
pub trait ReadonlyContext: Send + Sync {
/// Aktuelle Invocation ID
fn invocation_id(&self) -> &str;
/// Name des aktuellen Agents
fn agent_name(&self) -> &str;
/// Benutzer-ID aus der Session
fn user_id(&self) -> &str;
/// Anwendungsname
fn app_name(&self) -> &str;
/// Session ID
fn session_id(&self) -> &str;
/// Aktueller Branch (für Multi-Agent)
fn branch(&self) -> &str;
/// Der Eingabeinhalt des Benutzers
fn user_content(&self) -> &Content;
}
Häufige Muster
Logging-Callback
Protokolliert alle Agent-Interaktionen:
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()?;
Eingabe-Guardrails
Blockiert unangemessenen Inhalt vor der Verarbeitung:
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 {
// Überprüft die Benutzereingabe auf blockierten Inhalt
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") {
// Frühzeitig mit einer Ablehnungsnachricht zurückkehren
return Ok(Some(Content {
role: "model".to_string(),
parts: vec![Part::Text {
text: "I cannot process that request.".to_string(),
}],
}));
}
}
}
Ok(None) // Normale Ausführung fortsetzen
})
}))
.build()?;
Antwort-Caching (Vor dem Modell)
Cacht LLM-Antworten, um API-Aufrufe zu reduzieren:
use adk_rust::prelude::*;
use std::sync::Arc;
use std::collections::HashMap;
use std::sync::Mutex;
// Einfacher 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 {
// Erstellt einen Cache-Schlüssel aus dem Anfrageinhalt
let key = format!("{:?}", request.contents);
// Cache überprüfen
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)) // Weiter zum Modell
})
}))
.build()?;
Injizieren von multimodalen Inhalten (Vor dem Modell)
Injiziert Bilder oder andere binäre Inhalte in LLM-Anfragen für multimodale Analysen:
use adk_rust::prelude::*;
use adk_rust::artifact::{ArtifactService, LoadRequest};
use std::sync::Arc;
// Artifact-Service mit vorab geladenem Bild
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 {
// Bild aus dem Artifact-Speicher laden
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 {
// Bild in die Nachricht des Benutzers injizieren
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()?;
Dieses Muster ist für multimodale KI unerlässlich, da Werkzeugantworten JSON-Text sind – das Modell kann von Werkzeugen zurückgegebene Bilder nicht „sehen“. Durch das direkte Injizieren des Bildes in die Anfrage empfängt das Modell tatsächliche Bilddaten.
Antwortmodifikation (Nach dem Modell)
Modifiziert oder filtert Modellantworten:
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 {
// Den Antwortinhalt modifizieren
if let Some(ref mut content) = response.content {
for part in &mut content.parts {
if let Part::Text { text } = part {
// Haftungsausschluss zu allen Antworten hinzufügen
*text = format!("{}\n\n[AI-generated response]", text);
}
}
}
Ok(Some(response))
})
}))
.build()?;
Werkzeug-Berechtigungsprüfung (Vor dem Werkzeug)
Validiert die Ausführungsberechtigungen des Werkzeugs:
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 {
// Überprüfen, ob der Benutzer Berechtigungen für Werkzeuge hat
let user_id = ctx.user_id();
// Beispiel: Bestimmte Benutzer von der Nutzung von Werkzeugen blockieren
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) // Werkzeugausführung erlauben
})
}))
.build()?;
Werkzeugergebnis-Logging (Nach dem Werkzeug)
Protokolliert alle Werkzeugausführungen:
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) // Ursprüngliches Ergebnis beibehalten
})
}))
.build()?;
Mehrere Callbacks
Sie können mehrere Callbacks desselben Typs hinzufügen. Sie werden in der angegebenen Reihenfolge ausgeführt:
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()?;
Wenn ein Callback Some(content) zurückgibt, werden nachfolgende Callbacks desselben Typs übersprungen.
Fehlerbehandlung
Callbacks können Fehler zurückgeben, um die Ausführung abzubrechen:
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()?;
Bewährte Praktiken
- Halten Sie Callbacks schlank: Vermeiden Sie aufwendige Berechnungen in Callbacks
- Behandeln Sie Fehler elegant: Geben Sie aussagekräftige Fehlermeldungen zurück
- Verwenden Sie Logging sparsam: Zu viel Logging kann die Leistung beeinträchtigen
- Cachen Sie klug: Berücksichtigen Sie Strategien zur Cache-Invalidierung
- Testen Sie Callbacks unabhängig: Unit-Tests der Callback-Logik separat durchführen
Verwandt
Vorheriges: ← Zustandsverwaltung | Nächstes: Artefakte →