أنواع أساسية
الأنواع والسمات الأساسية من adk-core التي تشكل أساس ADK-Rust.
Content و Part
تتدفق كل رسالة في ADK عبر Content و Part. فهم هذه الأنواع ضروري للعمل مع agents و tools و callbacks.
Content
يمثل Content رسالة واحدة في المحادثة. يحتوي على role (من أرسلها) و parts واحد أو أكثر (المحتوى الفعلي).
الأدوار:
"user"- رسائل من المستخدم"model"- استجابات من نموذج الذكاء الاصطناعي"tool"- نتائج تنفيذtool
use adk_core::Content;
// Simple text message from user
let user_msg = Content::new("user")
.with_text("What's the weather like?");
// Model response
let model_msg = Content::new("model")
.with_text("The weather is sunny and 72°F.");
// Multiple text parts in one message
let detailed_msg = Content::new("user")
.with_text("Here's my question:")
.with_text("What is the capital of France?");
محتوى متعدد الوسائط:
يمكن أن يتضمن Content صورًا وصوتًا وملفات PDF وبيانات ثنائية أخرى بجانب النص:
// Image from bytes (e.g., read from file)
let image_bytes = std::fs::read("photo.jpg")?;
let content = Content::new("user")
.with_text("What's in this image?")
.with_inline_data("image/jpeg", image_bytes);
// Image from URL (model fetches it)
let content = Content::new("user")
.with_text("Describe this image")
.with_file_uri("image/png", "https://example.com/chart.png");
// PDF document
let pdf_bytes = std::fs::read("report.pdf")?;
let content = Content::new("user")
.with_text("Summarize this document")
.with_inline_data("application/pdf", pdf_bytes);
Part
Part هو enum يمثل أنواعًا مختلفة من المحتوى داخل الرسالة:
pub enum Part {
// Plain text
Text { text: String },
// Binary data embedded in the message
InlineData { mime_type: String, data: Vec<u8> },
// Reference to external file (URL or cloud storage)
FileData { mime_type: String, file_uri: String },
// Model requesting a tool call
FunctionCall { name: String, args: Value, id: Option<String> },
// Result of a tool execution
FunctionResponse { function_response: FunctionResponseData, id: Option<String> },
}
إنشاء Parts مباشرة:
use adk_core::Part;
// Text part
let text = Part::text_part("Hello, world!");
// Image from bytes
let image = Part::inline_data("image/png", png_bytes);
// Image from URL
let remote_image = Part::file_data("image/jpeg", "https://example.com/photo.jpg");
فحص Parts:
// Get text content (returns None for non-text parts)
if let Some(text) = part.text() {
println!("Text: {}", text);
}
// Get MIME type (for InlineData and FileData)
if let Some(mime) = part.mime_type() {
println!("MIME type: {}", mime);
}
// Get file URI (for FileData only)
if let Some(uri) = part.file_uri() {
println!("File URI: {}", uri);
}
// Check if part contains media (image, audio, video, etc.)
if part.is_media() {
println!("This part contains binary media");
}
التكرار عبر Parts:
for part in &content.parts {
match part {
Part::Text { text } => println!("Text: {}", text),
Part::InlineData { mime_type, data } => {
println!("Binary data: {} ({} bytes)", mime_type, data.len());
}
Part::FileData { mime_type, file_uri } => {
println!("File: {} at {}", mime_type, file_uri);
}
Part::FunctionCall { name, args, .. } => {
println!("Tool call: {}({})", name, args);
}
Part::FunctionResponse { function_response, .. } => {
println!("Tool result: {} -> {}",
function_response.name,
function_response.response
);
}
}
}
سمة العامل (Agent Trait)
سمة Agent هي التجريد الأساسي لجميع agents في ADK. كل نوع عامل — LlmAgent، و workflow agents، و custom agents — يطبق هذه السمة.
#[async_trait]
pub trait Agent: Send + Sync {
/// معرف فريد لهذا الـ agent
fn name(&self) -> &str;
/// وصف سهل القراءة لما يفعله هذا الـ agent
fn description(&self) -> &str;
/// الـ agents الفرعية (لـ workflow agents مثل SequentialAgent، ParallelAgent)
fn sub_agents(&self) -> &[Arc<dyn Agent>];
/// تنفيذ الـ agent وإرجاع تدفق من الأحداث
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream>;
}
النقاط الرئيسية:
name(): يستخدم للتسجيل، وعمليات النقل، وتحديد الهوية. يجب أن يكون فريدًا ضمن نظام متعدد الـ agents.description(): يظهر لـ LLMs عندما يستخدم الـ agent كـ tool أو لاتخاذ قرارات التوجيه.sub_agents(): يعيد الـ agents الفرعية. يكون فارغًا لـ leaf agents (مثل LlmAgent)، ويتم تعبئته لـ containers (مثل SequentialAgent، ParallelAgent).run(): هي طريقة التنفيذ الرئيسية. تستقبل السياق وتعيد تدفقًا من الأحداث.
لماذا EventStream؟
تعيد الـ Agents EventStream (تدفق من Result<Event>) بدلاً من استجابة واحدة للأسباب التالية:
- Streaming: يمكن بث الاستجابات رمزًا برمز (token-by-token) لتجربة مستخدم أفضل (UX).
- Tool calls: تحدث عدة استدعاءات tool واستجاباتها أثناء التنفيذ.
- State changes: يتم إصدار تحديثات الحالة كأحداث.
- Transfers: يتم الإشارة إلى عمليات نقل الـ Agent عبر الأحداث.
Tool Trait
توسّع الأدوات قدرات الوكلاء خارج المحادثة. فهي تتيح للوكلاء التفاعل مع واجهات برمجة التطبيقات (APIs) أو قواعد البيانات أو أنظمة الملفات أو إجراء عمليات حسابية.
#[async_trait]
pub trait Tool: Send + Sync {
/// Tool name (used in function calls from the model)
fn name(&self) -> &str;
/// Description shown to the LLM to help it decide when to use this tool
fn description(&self) -> &str;
/// JSON Schema defining the expected parameters
fn parameters_schema(&self) -> Option<Value> { None }
/// JSON Schema defining the response format
fn response_schema(&self) -> Option<Value> { None }
/// Whether this tool runs asynchronously (returns task ID immediately)
fn is_long_running(&self) -> bool { false }
/// Execute the tool with given arguments
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value>;
}
نقاط رئيسية:
name(): اسم الدالة الذي يستخدمه النموذج لاستدعاء هذه الأداة. اجعله قصيرًا ووصفيًا (مثلget_weather,search_database).description(): أمر بالغ الأهمية للنموذج لفهم متى يستخدم الأداة. كن محددًا بشأن ما تفعله ومتى تستخدمها.parameters_schema(): مخطط JSON الذي يخبر النموذج ما هي الوسائط التي يجب توفيرها. بدون هذا، يتكهن النموذج.execute(): يتلقى الوسائط المحللة كـserde_json::Value. أعد النتيجة كـ JSON.
تنفيذ أداة مخصصة:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::{json, Value};
use std::sync::Arc;
struct WeatherTool {
api_key: String,
}
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get current weather for a city. Use this when the user asks about weather conditions."
}
fn parameters_schema(&self) -> Option<Value> {
Some(json!({
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name (e.g., 'London', 'New York')"
},
"units": {
"type": "string",
"enum": ["celsius", "fahrenheit"],
"description": "Temperature units"
}
},
"required": ["city"]
}))
}
async fn execute(&self, _ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"].as_str().unwrap_or("Unknown");
let units = args["units"].as_str().unwrap_or("celsius");
// Call weather API here...
Ok(json!({
"city": city,
"temperature": 22,
"units": units,
"condition": "sunny"
}))
}
}
الأدوات طويلة المدى:
للعمليات التي تستغرق وقتًا طويلاً (معالجة الملفات، استدعاءات API الخارجية)، ضع علامة على الأداة بأنها طويلة المدى:
fn is_long_running(&self) -> bool { true }
تُرجع الأدوات طويلة المدى معرف مهمة على الفور. يُطلب من النموذج عدم استدعاء الأداة مرة أخرى أثناء انتظارها.
سمة Toolset
توفر Toolset أدوات ديناميكيًا بناءً على السياق. استخدم هذا عندما:
- تعتمد الأدوات على أذونات المستخدم
- يتم تحميل الأدوات من مصادر خارجية (MCP servers)
- تتغير إتاحة الأدوات أثناء التنفيذ
#[async_trait]
pub trait Toolset: Send + Sync {
/// معرف Toolset
fn name(&self) -> &str;
/// إرجاع الأدوات المتاحة للسياق الحالي
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>>;
}
مثال: Toolset يعتمد على الأذونات:
struct AdminToolset {
admin_tools: Vec<Arc<dyn Tool>>,
user_tools: Vec<Arc<dyn Tool>>,
}
#[async_trait]
impl Toolset for AdminToolset {
fn name(&self) -> &str { "admin_toolset" }
async fn tools(&self, ctx: Arc<dyn ReadonlyContext>) -> Result<Vec<Arc<dyn Tool>>> {
if ctx.user_id().starts_with("admin_") {
Ok(self.admin_tools.clone())
} else {
Ok(self.user_tools.clone())
}
}
}
سمات السياق
توفر السياقات المعلومات والخدمات للـagents والـtools أثناء التنفيذ. هناك تسلسل هرمي لسمات السياق، يضيف كل منها المزيد من الإمكانات.
ReadonlyContext
المعلومات الأساسية المتاحة في كل مكان:
pub trait ReadonlyContext: Send + Sync {
/// معرف فريد لهذا الاستدعاء
fn invocation_id(&self) -> &str;
/// اسم الـagent قيد التنفيذ حاليًا
fn agent_name(&self) -> &str;
/// معرف المستخدم
fn user_id(&self) -> &str;
/// اسم التطبيق
fn app_name(&self) -> &str;
/// معرف الجلسة
fn session_id(&self) -> &str;
/// إدخال المستخدم الذي أدى إلى هذا الاستدعاء
fn user_content(&self) -> &Content;
}
CallbackContext
يضيف الوصول إلى الـartifact (يمتد ReadonlyContext):
pub trait CallbackContext: ReadonlyContext {
/// الوصول إلى تخزين الـartifact (إذا تم تكوينه)
fn artifacts(&self) -> Option<Arc<dyn Artifacts>>;
}
ToolContext
لتنفيذ الـtool (يمتد CallbackContext):
pub trait ToolContext: CallbackContext {
/// معرف استدعاء الدالة الذي أدى إلى تفعيل هذه الـtool
fn function_call_id(&self) -> &str;
/// الحصول على إجراءات الحدث الحالية (transfer, escalate، إلخ.)
fn actions(&self) -> EventActions;
/// تعيين إجراءات الحدث (على سبيل المثال، تفعيل transfer)
fn set_actions(&self, actions: EventActions);
/// البحث في الذاكرة طويلة المدى
async fn search_memory(&self, query: &str) -> Result<Vec<MemoryEntry>>;
}
InvocationContext
السياق الكامل لتنفيذ الـagent (يمتد CallbackContext):
pub trait InvocationContext: CallbackContext {
/// الـagent قيد التنفيذ
fn agent(&self) -> Arc<dyn Agent>;
/// خدمة الذاكرة (إذا تم تكوينها)
fn memory(&self) -> Option<Arc<dyn Memory>>;
/// الجلسة الحالية مع الحالة والسجل
fn session(&self) -> &dyn Session;
/// تهيئة التنفيذ
fn run_config(&self) -> &RunConfig;
/// إشارة إلى أن هذا الاستدعاء يجب أن ينتهي
fn end_invocation(&self);
/// التحقق مما إذا كان الاستدعاء قد انتهى
fn ended(&self) -> bool;
}
الجلسة والحالة
تتتبع الجلسات المحادثات. وتخزن الحالة البيانات داخل الجلسات.
الجلسة
pub trait Session: Send + Sync {
/// Unique session identifier
fn id(&self) -> &str;
/// Application this session belongs to
fn app_name(&self) -> &str;
/// User who owns this session
fn user_id(&self) -> &str;
/// Mutable state storage
fn state(&self) -> &dyn State;
/// Previous messages in this conversation
fn conversation_history(&self) -> Vec<Content>;
}
الحالة
تخزين قيمة-مفتاح مع بادئات محددة النطاق:
pub trait State: Send + Sync {
/// Get a value by key
fn get(&self, key: &str) -> Option<Value>;
/// Set a value
fn set(&mut self, key: String, value: Value);
/// Get all key-value pairs
fn all(&self) -> HashMap<String, Value>;
}
بادئات الحالة:
تستخدم مفاتيح الحالة بادئات للتحكم في النطاق والمثابرة:
| البادئة | النطاق | المثابرة | حالة الاستخدام |
|---|---|---|---|
user: | مستوى المستخدم | عبر جميع الجلسات | تفضيلات المستخدم، الإعدادات |
app: | مستوى التطبيق | على مستوى التطبيق | التكوين المشترك |
temp: | مستوى الدور | تُمسح في كل دور | بيانات الحساب المؤقتة |
| (لا شيء) | مستوى الجلسة | هذه الجلسة فقط | سياق المحادثة |
// In a callback or tool
let state = ctx.session().state();
// User preference (persists across sessions)
state.set("user:theme".into(), json!("dark"));
// Session-specific data
state.set("current_topic".into(), json!("weather"));
// Temporary data (cleared after this turn)
state.set("temp:step_count".into(), json!(1));
// Read values
if let Some(theme) = state.get("user:theme") {
println!("Theme: {}", theme);
}
معالجة الأخطاء
يستخدم adk-rust نوع خطأ موحد لجميع العمليات:
pub enum AdkError {
Agent(String), // أخطاء تنفيذ Agent
Tool(String), // أخطاء تنفيذ Tool
Model(String), // أخطاء واجهة برمجة تطبيقات LLM
Session(String), // أخطاء تخزين Session
Artifact(String), // أخطاء تخزين المادة
Config(String), // أخطاء التكوين
Io(std::io::Error), // أخطاء الإدخال/الإخراج للملف/الشبكة
Json(serde_json::Error), // أخطاء تحليل JSON
}
pub type Result<T> = std::result::Result<T, AdkError>;
معالجة الأخطاء في Tools:
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let city = args["city"]
.as_str()
.ok_or_else(|| AdkError::Tool("Missing 'city' parameter".into()))?;
let response = reqwest::get(&format!("https://api.weather.com/{}", city))
.await
.map_err(|e| AdkError::Tool(format!("API error: {}", e)))?;
Ok(json!({ "weather": "sunny" }))
}
EventStream
تعيد Agents تدفقًا من الأحداث بدلاً من استجابة واحدة:
pub type EventStream = Pin<Box<dyn Stream<Item = Result<Event>> + Send>>;
معالجة الأحداث:
use futures::StreamExt;
let mut stream = agent.run(ctx).await?;
while let Some(result) = stream.next().await {
match result {
Ok(event) => {
// التحقق من محتوى النص
if let Some(content) = event.content() {
for part in &content.parts {
if let Some(text) = part.text() {
print!("{}", text);
}
}
}
// التحقق من تغييرات الحالة
for (key, value) in event.state_delta() {
println!("State changed: {} = {}", key, value);
}
// التحقق مما إذا كانت هذه هي الاستجابة النهائية
if event.is_final_response() {
println!("\n[Done]");
}
}
Err(e) => {
eprintln!("خطأ: {}", e);
break;
}
}
}
سمة Llm
السمة التي تنفذها جميع موفري LLM:
#[async_trait]
pub trait Llm: Send + Sync {
/// معرّف النموذج (مثل "gemini-2.0-flash", "gpt-4o")
fn name(&self) -> &str;
/// إنشاء محتوى (بث أو غير بث)
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream>;
}
LlmRequest:
pub struct LlmRequest {
pub contents: Vec<Content>, // سجل المحادثة
pub tools: Vec<ToolDeclaration>, // الأدوات المتاحة
pub system_instruction: Option<String>, // موجه النظام
pub config: GenerateContentConfig, // درجة الحرارة، max_tokens، إلخ.
}
LlmResponse:
pub struct LlmResponse {
pub content: Option<Content>, // المحتوى الذي تم إنشاؤه
pub finish_reason: Option<FinishReason>, // لماذا توقف الإنشاء
pub usage: Option<UsageMetadata>, // عدد الرموز
pub partial: bool, // هل هذا جزء من البث؟
pub turn_complete: bool, // هل انتهى الدور؟
}
جميع الموفرين (Gemini, OpenAI, Anthropic, Ollama, إلخ.) ينفذون هذه السمة، مما يجعلهم قابلين للتبديل:
// غيّر الموفرين بتغيير سطر واحد
let model: Arc<dyn Llm> = Arc::new(GeminiModel::new(&key, "gemini-2.0-flash")?);
// let model: Arc<dyn Llm> = Arc::new(OpenAIClient::new(config)?);
// let model: Arc<dyn Llm> = Arc::new(AnthropicClient::new(config)?);
let agent = LlmAgentBuilder::new("assistant")
.model(model)
.build()?;
السابق: ← Introduction | التالي: Runner →