الإضافات
توفر حزمة adk-plugin نظامًا لخطافات دورة الحياة للوكلاء. تعترض الإضافات استدعاءات الأدوات، واستدعاءات النماذج، وأحداث التنفيذ دون تعديل شفرة الوكيل — وهي مفيدة للتسجيل، والضوابط الوقائية، والتخزين المؤقت، وتتبع التكلفة، والبرمجيات الوسيطة المخصصة.
نظرة عامة
يُبنى نظام الإضافات حول السمة EnhancedPlugin. لا تحتاج إلى تنفيذ سوى الخطافات التي تحتاج إليها:
- before_run / after_run — تغليف استدعاءات الوكيل بالكامل
- before_tool / after_tool — اعتراض تنفيذ الأدوات (تعديل الوسائط، أو التخطي المباشر، أو فحص النتائج)
- before_model / after_model — اعتراض استدعاءات LLM (تعديل الطلبات، وتخزين الاستجابات مؤقتًا)
- on_event — مراقبة كل حدث يصدره الوكيل
تُشغَّل الإضافات في مسار مرتب حسب الأولوية، مما يتيح إنشاء حزم برمجيات وسيطة قابلة للتركيب.
التثبيت
[dependencies]
adk-plugin = "2.1.0"
# Or via umbrella crate (included in standard tier)
adk-rust = { version = "2.1.0", features = ["standard"] }
البدء السريع
use adk_plugin::{EnhancedPlugin, PluginContext, ToolCallInfo, ToolResultInfo};
use adk_core::{Content, Result};
use async_trait::async_trait;
use serde_json::Value;
struct LoggingPlugin;
#[async_trait]
impl EnhancedPlugin for LoggingPlugin {
fn name(&self) -> &str { "logging" }
fn priority(&self) -> i32 { 0 }
async fn before_tool(
&self,
ctx: &PluginContext,
tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
tracing::info!(
tool = tool_call.name,
args = %tool_call.args,
"tool call started"
);
Ok(None) // Continue to actual tool execution
}
async fn after_tool(
&self,
ctx: &PluginContext,
tool_result: &mut ToolResultInfo,
) -> Result<()> {
tracing::info!(
tool = tool_result.name,
duration_ms = tool_result.duration_ms,
"tool call completed"
);
Ok(())
}
}
سمة EnhancedPlugin
#[async_trait]
pub trait EnhancedPlugin: Send + Sync {
/// Unique plugin identifier
fn name(&self) -> &str;
/// Execution order (lower = runs first) [default: 0]
fn priority(&self) -> i32 { 0 }
/// Called before agent execution starts
async fn before_run(&self, ctx: &PluginContext) -> Result<()> { Ok(()) }
/// Called after agent execution completes
async fn after_run(&self, ctx: &PluginContext) -> Result<()> { Ok(()) }
/// Called before each tool execution.
/// Return Some(value) to short-circuit (skip tool, return this value).
/// Return None to continue with normal execution.
async fn before_tool(
&self,
ctx: &PluginContext,
tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> { Ok(None) }
/// Called after each tool execution.
/// Can modify the result before it's returned to the LLM.
async fn after_tool(
&self,
ctx: &PluginContext,
tool_result: &mut ToolResultInfo,
) -> Result<()> { Ok(()) }
/// Called before each model (LLM) call.
/// Can modify the request or short-circuit with a cached response.
async fn before_model(
&self,
ctx: &PluginContext,
request: &mut ModelCallInfo,
) -> Result<Option<Content>> { Ok(None) }
/// Called after each model call.
/// Can modify the response before it's processed.
async fn after_model(
&self,
ctx: &PluginContext,
response: &mut ModelResultInfo,
) -> Result<()> { Ok(()) }
/// Called for every event emitted during execution.
async fn on_event(
&self,
ctx: &PluginContext,
event: &Event,
) -> Result<()> { Ok(()) }
}
اعتراض استدعاءات الأدوات
تعديل الوسائط
عدّل وسائط الأداة قبل التنفيذ:
async fn before_tool(
&self,
ctx: &PluginContext,
tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
// Inject default values
if tool_call.name == "search" {
if tool_call.args.get("limit").is_none() {
tool_call.args["limit"] = serde_json::json!(10);
}
}
Ok(None) // Continue to tool execution
}
التخطي المباشر (تخطي تنفيذ الأداة)
أعد قيمة مباشرةً دون استدعاء الأداة:
async fn before_tool(
&self,
ctx: &PluginContext,
tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
// Check cache
let cache_key = format!("{}:{}", tool_call.name, tool_call.args);
if let Some(cached) = self.cache.get(&cache_key).await {
return Ok(Some(cached)); // Short-circuit: return cached result
}
Ok(None) // Cache miss: proceed with tool execution
}
تعديل النتيجة
عدّل نتائج الأداة بعد التنفيذ:
async fn after_tool(
&self,
ctx: &PluginContext,
tool_result: &mut ToolResultInfo,
) -> Result<()> {
// Redact sensitive data from results
if let Some(obj) = tool_result.result.as_object_mut() {
if obj.contains_key("ssn") {
obj.insert("ssn".into(), serde_json::json!("***-**-****"));
}
}
Ok(())
}
اعتراض استدعاءات النماذج
تعديل الطلب
عدّل طلبات LLM قبل إرسالها:
async fn before_model(
&self,
ctx: &PluginContext,
request: &mut ModelCallInfo,
) -> Result<Option<Content>> {
// Add system context to every request
if let Some(ref mut instruction) = request.system_instruction {
instruction.push_str("\nAlways respond in JSON format.");
}
Ok(None)
}
تخزين الاستجابة مؤقتًا
async fn before_model(
&self,
ctx: &PluginContext,
request: &mut ModelCallInfo,
) -> Result<Option<Content>> {
let key = self.hash_request(request);
if let Some(cached) = self.cache.get(&key).await {
return Ok(Some(cached)); // Return cached response
}
Ok(None)
}
async fn after_model(
&self,
ctx: &PluginContext,
response: &mut ModelResultInfo,
) -> Result<()> {
// Cache the response for future calls
let key = self.hash_request(&response.original_request);
self.cache.set(&key, response.content.clone()).await;
Ok(())
}
المسار القائم على الأولوية
تُنفَّذ الإضافات وفق ترتيب الأولوية (تُشغَّل الأرقام الأقل أولًا):
struct AuthPlugin;
impl EnhancedPlugin for AuthPlugin {
fn name(&self) -> &str { "auth" }
fn priority(&self) -> i32 { -10 } // Runs first
// ...
}
struct LogPlugin;
impl EnhancedPlugin for LogPlugin {
fn name(&self) -> &str { "log" }
fn priority(&self) -> i32 { 0 } // Runs second
// ...
}
struct CachePlugin;
impl EnhancedPlugin for CachePlugin {
fn name(&self) -> &str { "cache" }
fn priority(&self) -> i32 { 10 } // Runs last
// ...
}
بالنسبة إلى خطافات before_*، تُشغَّل الإضافات بدءًا من الأقل أولوية. أما خطافات after_* فتُشغَّل بترتيب عكسي (بدءًا من الأعلى أولوية)، مما ينشئ نمط برمجيات وسيطة متداخلة.
PluginContext — الحالة المشتركة
يوفر PluginContext حالة مشتركة يمكن الوصول إليها عبر جميع الإضافات أثناء الاستدعاء:
use adk_plugin::PluginContext;
async fn before_tool(
&self,
ctx: &PluginContext,
tool_call: &mut ToolCallInfo,
) -> Result<Option<Value>> {
// Read shared state
let call_count: u64 = ctx.get("tool_call_count").unwrap_or(0);
// Write shared state
ctx.set("tool_call_count", call_count + 1);
// Access invocation metadata
let user_id = ctx.user_id();
let session_id = ctx.session_id();
let agent_name = ctx.agent_name();
Ok(None)
}
تسجيل الإضافات مع وكيل
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;
let agent = LlmAgentBuilder::new("my_agent")
.model(model)
.instruction("You are a helpful assistant.")
.tool(Arc::new(my_tool))
.plugin(Arc::new(LoggingPlugin))
.plugin(Arc::new(CachePlugin::new(cache_store)))
.plugin(Arc::new(CostTrackingPlugin::new()))
.build()?;
مثال: إضافة تتبّع التكلفة
use adk_plugin::{EnhancedPlugin, PluginContext, ModelResultInfo};
use adk_core::Result;
use std::sync::atomic::{AtomicU64, Ordering};
struct CostPlugin {
total_tokens: AtomicU64,
}
#[async_trait]
impl EnhancedPlugin for CostPlugin {
fn name(&self) -> &str { "cost_tracker" }
fn priority(&self) -> i32 { 100 }
async fn after_model(
&self,
ctx: &PluginContext,
response: &mut ModelResultInfo,
) -> Result<()> {
if let Some(usage) = &response.usage {
let tokens = usage.prompt_tokens + usage.completion_tokens;
self.total_tokens.fetch_add(tokens as u64, Ordering::Relaxed);
tracing::info!(
total_tokens = self.total_tokens.load(Ordering::Relaxed),
"token usage updated"
);
}
Ok(())
}
}
ذات صلة
- إعادة المحاولة والتأمل — إضافة مضمّنة للتعافي من فشل الأدوات
- الضوابط — إضافات للتحقق من صحة الإدخال والإخراج
- القياس عن بُعد — تكامل قابلية الملاحظة
- عمليات الاستدعاء — نظام خطّافات أبسط للحالات الشائعة
السابق: ← عُقد الإجراءات | التالي: الجلسات →