استدعاءات

توفر الاستدعاءات في ADK-Rust نقاط ربط لمراقبة سلوك الـ Agent وتخصيصه والتحكم فيه عند نقاط التنفيذ الرئيسية. إنها تمكّن من التسجيل، وحواجز الأمان، والتخزين المؤقت، وتعديل الاستجابة، والمزيد.

نظرة عامة

يدعم ADK-Rust ستة أنواع من الاستدعاءات التي تعترض مراحل مختلفة من تنفيذ الـ Agent:

نوع الاستدعاءمتى يتم التنفيذحالات الاستخدام
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 - يمكنه تخطي الـ Tool عن طريق إرجاع Some(Content)
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
>;

دلالات قيمة الإرجاع

تستخدم Callbacks قيم إرجاع مختلفة للتحكم في تدفق التنفيذ:

Callbacks لـ Agent/Tool

قيمة الإرجاعالتأثير
Ok(None)متابعة التنفيذ الطبيعي
Ok(Some(content))تجاوز/تخطي بالمحتوى المقدم
Err(e)إجهاض التنفيذ مع خطأ

Callbacks للنماذج (Model Callbacks)

يستخدم BeforeModelCallback BeforeModelResult:

قيمة الإرجاعالتأثير
Ok(BeforeModelResult::Continue(request))المتابعة مع الطلب (المعدّل المحتمل)
Ok(BeforeModelResult::Skip(response))تخطي استدعاء النموذج، واستخدام هذا الاستجابة بدلاً من ذلك
Err(e)إجهاض التنفيذ مع خطأ

يستخدم AfterModelCallback Option<LlmResponse>:

قيمة الإرجاعالتأثير
Ok(None)الاحتفاظ بالاستجابة الأصلية
Ok(Some(response))الاستبدال بالاستجابة المعدّلة
Err(e)إجهاض التنفيذ مع خطأ

ملخص

  • قبل Callbacks لـ Agent/Tool: أرجع None للمتابعة، Some(content) للتخطي
  • قبل Callback للنموذج (Before model callback): أرجع Continue(request) للمتابعة، Skip(response) لتجاوز النموذج
  • بعد Callbacks (After callbacks): أرجع None للاحتفاظ بالأصل، Some(...) للاستبدال

إضافة Callbacks إلى Agents

تتم إضافة Callbacks إلى Agents باستخدام 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.")
        // 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(())
}

واجهة CallbackContext

توفر السمة CallbackContext الوصول إلى سياق التنفيذ:

use adk_rust::prelude::*;

#[async_trait]
pub trait CallbackContext: ReadonlyContext {
    /// Access artifact storage (if configured)
    fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}

// 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;
}

أنماط شائعة

رد الاتصال للتسجيل (Logging Callback)

سجل جميع تفاعلات 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()?;

حواجز حماية المدخلات (Input Guardrails)

حظر المحتوى غير اللائق قبل المعالجة:

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()?;

التخزين المؤقت للاستجابة (قبل Model)

تخزين استجابات 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()?;

حقن المحتوى متعدد الوسائط (قبل Model)

حقن الصور أو المحتوى الثنائي الآخر في طلبات 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()?;

هذا النمط ضروري للذكاء الاصطناعي متعدد الوسائط لأن استجابات Tool هي نصوص JSON - لا يستطيع Model "رؤية" الصور التي يتم إرجاعها بواسطة Tool. عن طريق حقن الصورة مباشرة في الطلب، يتلقى Model بيانات الصورة الفعلية.

تعديل الاستجابة (بعد Model)

تعديل أو تصفية استجابات Model:

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 (قبل Tool)

التحقق من صحة أذونات تنفيذ 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 (بعد Tool)

سجل جميع عمليات تنفيذ 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()?;

Callbacks متعددة

يمكنك إضافة callbacks متعددة من نفس النوع. يتم تنفيذها بالترتيب:

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)، يتم تخطي callbacks اللاحقة من نفس النوع.

معالجة الأخطاء

يمكن لـcallbacks إرجاع أخطاء لإلغاء التنفيذ:

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()?;

أفضل الممارسات

  1. اجعل callbacks خفيفة الوزن: تجنب العمليات الحسابية الثقيلة في callbacks
  2. تعامل مع الأخطاء بلطف: أعد رسائل خطأ ذات معنى
  3. استخدم التسجيل باعتدال: كثرة التسجيل قد تؤثر على الأداء
  4. التخزين المؤقت بحكمة: ضع في اعتبارك استراتيجيات إبطال التخزين المؤقت
  5. اختبر callbacks بشكل مستقل: اختبر منطق callback كوحدة منفصلة

ذات صلة


السابق: ← إدارة الحالة | التالي: المخرجات →