コールバック
ADK-RustのCallbacksは、主要な実行ポイントでAgentの動作を監視、カスタマイズ、制御するためのフックを提供します。これらにより、ログ記録、ガードレール、キャッシュ、応答の変更などが可能になります。
概要
ADK-Rustは、Agent実行のさまざまな段階をインターセプトする6つのコールバックタイプをサポートしています。
| コールバックタイプ | 実行タイミング | ユースケース |
|---|---|---|
before_agent | Agentが処理を開始する前 | 入力検証、ログ記録、早期終了 |
after_agent | Agentが完了した後 | 応答の変更、ログ記録、クリーンアップ |
before_model | LLM呼び出し前 | リクエストの変更、キャッシュ、レート制限 |
after_model | LLM応答後 | 応答のフィルタリング、ログ記録、キャッシュ |
before_tool | Tool実行前 | 権限チェック、パラメータ検証 |
after_tool | Tool実行後 | 結果の変更、ログ記録 |
コールバックタイプ
Agentコールバック
Agentコールバックは、Agentの実行サイクル全体をラップします。
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeAgentCallback型シグネチャ
type BeforeAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterAgentCallback型シグネチャ
type AfterAgentCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
モデルコールバック
モデルコールバックは、LLMのリクエストとレスポンスをインターセプトします。
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeModelResult - コールバック後の動作を制御します
pub enum BeforeModelResult {
Continue(LlmRequest), // (変更された可能性のある)リクエストで続行
Skip(LlmResponse), // モデル呼び出しをスキップし、この応答を使用
}
// BeforeModelCallback - リクエストを変更するか、モデル呼び出しをスキップできます
type BeforeModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmRequest)
-> Pin<Box<dyn Future<Output = Result<BeforeModelResult>> + Send>>
+ Send + Sync
>;
// AfterModelCallback - 応答を変更できます
type AfterModelCallback = Box<
dyn Fn(Arc<dyn CallbackContext>, LlmResponse)
-> Pin<Box<dyn Future<Output = Result<Option<LlmResponse>>> + Send>>
+ Send + Sync
>;
Toolコールバック
Toolコールバックは、Toolの実行をインターセプトします。
use adk_rust::prelude::*;
use std::sync::Arc;
// BeforeToolCallback - Some(Content)を返すことでToolをスキップできます
type BeforeToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
// AfterToolCallback - Toolの結果を変更できます
type AfterToolCallback = Box<
dyn Fn(Arc<dyn CallbackContext>)
-> Pin<Box<dyn Future<Output = Result<Option<Content>>> + Send>>
+ Send + Sync
>;
戻り値のセマンティクス
コールバックは、異なる戻り値を使用して実行フローを制御します。
Agent/Toolコールバック
| 戻り値 | 効果 |
|---|---|
Ok(None) | 通常の実行を続行 |
Ok(Some(content)) | 提供されたコンテンツで上書き/スキップ |
Err(e) | エラーで実行を中止 |
Modelコールバック
BeforeModelCallback は BeforeModelResult を使用します。
| 戻り値 | 効果 |
|---|---|
Ok(BeforeModelResult::Continue(request)) | (変更された可能性のある) リクエストで続行 |
Ok(BeforeModelResult::Skip(response)) | モデル呼び出しをスキップし、代わりにこのレスポンスを使用 |
Err(e) | エラーで実行を中止 |
AfterModelCallback は Option<LlmResponse> を使用します。
| 戻り値 | 効果 |
|---|---|
Ok(None) | 元のレスポンスを保持 |
Ok(Some(response)) | 変更されたレスポンスに置換 |
Err(e) | エラーで実行を中止 |
まとめ
- Agent/Toolコールバックの前:
Noneを返して続行し、Some(content)を返してスキップします - Modelコールバックの前:
Continue(request)を返して続行し、Skip(response)を返してモデルをバイパスします - コールバックの後:
Noneを返して元の状態を保持し、Some(...)を返して置換します
Agentへのコールバックの追加
コールバックは、LlmAgentBuilder を使用してAgentに追加されます。
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.")
// before_agent コールバックを追加
.before_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent starting: {}", ctx.agent_name());
Ok(None) // 実行を続行
})
}))
// after_agent コールバックを追加
.after_callback(Box::new(|ctx| {
Box::pin(async move {
println!("Agent completed: {}", ctx.agent_name());
Ok(None) // 元の結果を保持
})
}))
.build()?;
Ok(())
}
CallbackContextインターフェース
CallbackContext トレイトは実行コンテキストへのアクセスを提供します。
use adk_rust::prelude::*;
#[async_trait]
pub trait CallbackContext: ReadonlyContext {
/// アーティファクトストレージにアクセス (設定されている場合)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
// CallbackContext は ReadonlyContext を拡張します
#[async_trait]
pub trait ReadonlyContext: Send + Sync {
/// 現在の呼び出しID
fn invocation_id(&self) -> &str;
/// 現在のAgent名
fn agent_name(&self) -> &str;
/// セッションからのユーザーID
fn user_id(&self) -> &str;
/// アプリケーション名
fn app_name(&self) -> &str;
/// セッションID
fn session_id(&self) -> &str;
/// 現在のブランチ (マルチAgent用)
fn branch(&self) -> &str;
/// ユーザーの入力コンテンツ
fn user_content(&self) -> &Content;
}
共通のパターン
ロギングコールバック
すべてのエージェントインタラクションをログに記録します。
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()?;
入力ガードレール
不適切なコンテンツを処理する前にブロックします。
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()?;
レスポンスキャッシュ (モデルの前)
LLM レスポンスをキャッシュして、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()?;
マルチモーダルコンテンツの注入 (モデルの前)
マルチモーダル分析のために、画像やその他のバイナリコンテンツをLLMリクエストに注入します。
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()?;
このパターンは、ツールのレスポンスが JSON テキストであるため、マルチモーダル AI にとって不可欠です。モデルはツールによって返された画像を「見る」ことができません。画像をリクエストに直接注入することで、モデルは実際の画像データを受け取ります。
レスポンスの変更 (モデルの後)
モデルのレスポンスを変更またはフィルタリングします。
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()?;
Tool 権限チェック (ツールの前)
ツール実行権限を検証します。
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()?;
Tool 結果のロギング (ツールの後)
すべてのツール実行をログに記録します。
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()?;
複数のCallback
同じタイプのCallbackを複数追加できます。これらは順序通りに実行されます。
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()?;
CallbackがSome(content)を返した場合、同じタイプの後続のCallbackはスキップされます。
エラーハンドリング
Callbackはエラーを返して実行を中止できます。
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()?;
ベストプラクティス
- Callbackを軽量に保つ: Callback内での重い計算を避ける
- エラーを適切に処理する: 意味のあるエラーメッセージを返す
- ロギングを控えめに使用する: 過度なロギングはパフォーマンスに影響を与える可能性があります
- キャッシュを賢く利用する: キャッシュ無効化戦略を検討する
- Callbackを独立してテストする: Callbackのロジックを個別に単体テストする
関連
前へ: ← State Management | 次へ: Artifacts →