إرشادات التطوير
يوفر هذا المستند إرشادات شاملة للمطورين المساهمين في adk-rust. يضمن اتباع هذه المعايير جودة الكود والاتساق وقابلية الصيانة عبر المشروع.
جدول المحتويات
- البدء
- هيكل المشروع
- نمط الكود
- معالجة الأخطاء
- أنماط Async
- الاختبار
- التوثيق
- عملية طلب السحب
- المهام الشائعة
البدء
المتطلبات الأساسية
- Rust: 1.75 أو أعلى (تحقق باستخدام
rustc --version) - Cargo: أحدث إصدار مستقر
- Git: للتحكم في الإصدار
إعداد بيئتك
# Clone the repository
git clone https://github.com/zavora-ai/adk-rust.git
cd adk-rust
# Build the project
cargo build
# Run all tests
cargo test --all
# Check for lints
cargo clippy --all-targets --all-features
# Format code
cargo fmt --all
متغيرات البيئة
لتشغيل الأمثلة والاختبارات التي تتطلب مفاتيح API:
# Gemini (default provider)
export GOOGLE_API_KEY="your-api-key"
# OpenAI (optional)
export OPENAI_API_KEY="your-api-key"
# Anthropic (optional)
export ANTHROPIC_API_KEY="your-api-key"
هيكل المشروع
adk-rust مُنظَّم كمساحة عمل Cargo تحتوي على عدة crates:
adk-rust/
├── adk-core/ # السمات والأنواع الأساسية (Agent, Tool, Llm, Event)
├── adk-telemetry/ # تكامل OpenTelemetry
├── adk-model/ # موفرو LLM (Gemini, OpenAI, Anthropic)
├── adk-tool/ # نظام Tool (FunctionTool, MCP, AgentTool)
├── adk-session/ # إدارة Session (في الذاكرة، SQLite)
├── adk-artifact/ # تخزين الـ artifact الثنائية
├── adk-memory/ # ذاكرة طويلة الأمد مع البحث
├── adk-agent/ # تطبيقات Agent (LlmAgent, عملاء سير العمل)
├── adk-runner/ # وقت تشغيل التنفيذ
├── adk-server/ # واجهة برمجة تطبيقات REST وبروتوكول A2A
├── adk-cli/ # مشغل سطر الأوامر
├── adk-realtime/ # عملاء بث الصوت/الصوت في الوقت الفعلي
├── adk-graph/ # مسارات عمل بأسلوب LangGraph
├── adk-browser/ # أدوات أتمتة المتصفح
├── adk-eval/ # إطار تقييم Agent
├── adk-rust/ # crate شاملة (تعيد تصدير الكل)
└── examples/ # أمثلة عملية
تبعيات Crate
يجب نشر Crates بترتيب التبعية:
adk-core(لا توجد تبعيات داخلية)adk-telemetryadk-modeladk-tooladk-sessionadk-artifactadk-memoryadk-agentadk-runneradk-serveradk-cliadk-realtimeadk-graphadk-browseradk-evaladk-rust(شاملة)
نمط الكود
مبادئ عامة
- الوضوح فوق الذكاء: اكتب كودًا سهل القراءة والفهم
- الصراحة فوق الضمنية: فضل الأنواع الصريحة ومعالجة الأخطاء الواضحة
- الدوال الصغيرة: اجعل الدوال مركزة وأقل من 50 سطرًا كلما أمكن ذلك
- الأسماء ذات المعنى: استخدم أسماء متغيرة ودوال وصفية
التنسيق
استخدم rustfmt بالإعدادات الافتراضية:
cargo fmt --all
تفرض CI pipeline التنسيق. قم دائمًا بتشغيل cargo fmt قبل الالتزام.
اصطلاحات التسمية
| النوع | الاصطلاح | مثال |
|---|---|---|
| Crates | adk-* (kebab-case) | adk-core, adk-agent |
| Modules | snake_case | llm_agent, function_tool |
| Types/Traits | PascalCase | LlmAgent, ToolContext |
| Functions | snake_case | execute_tool, run_agent |
| Constants | SCREAMING_SNAKE_CASE | KEY_PREFIX_APP |
| Type parameters | حرف كبير واحد أو PascalCase | T, State |
عمليات الاستيراد
رتب عمليات الاستيراد بهذا الترتيب:
// 1. المكتبة القياسية
use std::collections::HashMap;
use std::sync::Arc;
// 2. الكريتات الخارجية
use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use tokio::sync::RwLock;
// 3. الكريتات الداخلية (adk-*)
use adk_core::{Agent, Event, Result};
// 4. الوحدات المحلية
use crate::config::Config;
use super::utils;
Clippy
يجب أن يجتاز كل الكود اختبار clippy دون أي تحذيرات:
cargo clippy --all-targets --all-features
عالج تحذيرات clippy بدلاً من قمعها. إذا كان القمع ضروريًا، فقم بتوثيق السبب:
#[allow(clippy::too_many_arguments)]
// يتطلب نمط Builder العديد من المعاملات؛ وإعادة الهيكلة قد تضر بقابلية الاستخدام
fn complex_builder(...) { }
معالجة الأخطاء
استخدام adk_core::AdkError
يجب أن تستخدم جميع الأخطاء نوع الخطأ المركزي:
use adk_core::{AdkError, Result};
// إرجاع Result<T> (مُعاد تسميته إلى Result<T, AdkError>)
pub async fn my_function() -> Result<String> {
// استخدم ؟ للنشر
let data = fetch_data().await?;
// أنشئ أخطاء بأنواع مناسبة
if data.is_empty() {
return Err(AdkError::Tool("No data found".into()));
}
Ok(data)
}
أنواع الأخطاء
استخدم نوع الخطأ المناسب:
| النوع | حالة الاستخدام |
|---|---|
AdkError::Agent(String) | أخطاء تنفيذ Agent |
AdkError::Model(String) | أخطاء موفر LLM |
AdkError::Tool(String) | أخطاء تنفيذ Tool |
AdkError::Session(String) | أخطاء إدارة Session |
AdkError::Artifact(String) | أخطاء تخزين Artifact |
AdkError::Config(String) | أخطاء التكوين |
AdkError::Network(String) | أخطاء HTTP/الشبكة |
رسائل الخطأ
اكتب رسائل خطأ واضحة وقابلة للتنفيذ:
// جيد: محدد وقابل للتنفيذ
Err(AdkError::Config("API key not found. Set GOOGLE_API_KEY environment variable.".into()))
// سيء: مبهم
Err(AdkError::Config("Invalid config".into()))
أنماط عدم التزامن (Async Patterns)
استخدام Tokio
تستخدم جميع أكواد عدم التزامن (async) وقت تشغيل Tokio:
use tokio::sync::{Mutex, RwLock};
// يفضل RwLock للبيانات التي يكثر قراءتها
let state: Arc<RwLock<State>> = Arc::new(RwLock::new(State::default()));
// استخدم Mutex لحالات البيانات التي يكثر كتابتها أو الحالات البسيطة
let counter: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
السمات غير المتزامنة (Async Traits)
استخدم async_trait لأساليب السمات غير المتزامنة (async trait methods):
use async_trait::async_trait;
#[async_trait]
pub trait MyTrait: Send + Sync {
async fn do_work(&self) -> Result<()>;
}
التدفق (Streaming)
استخدم EventStream لاستقبال الاستجابات كتدفق:
use adk_core::EventStream;
use async_stream::stream;
use futures::Stream;
fn create_stream() -> EventStream {
let s = stream! {
yield Ok(Event::new("inv-1"));
yield Ok(Event::new("inv-2"));
};
Box::pin(s)
}
أمان الخيوط (Thread Safety)
يجب أن تكون جميع الأنواع العامة Send + Sync:
// جيد: آمن للخيوط
pub struct MyAgent {
name: String,
tools: Vec<Arc<dyn Tool>>, // Arc للملكية المشتركة
}
// التحقق من خلال فحوصات وقت الترجمة
fn assert_send_sync<T: Send + Sync>() {}
fn _check() {
assert_send_sync::<MyAgent>();
}
الاختبار (Testing)
تنظيم الاختبارات (Test Organization)
crate/
├── src/
│ ├── lib.rs # اختبارات الوحدة في نهاية الملف
│ └── module.rs # اختبارات خاصة بالوحدة
└── tests/
└── integration.rs # اختبارات التكامل
اختبارات الوحدة (Unit Tests)
ضع اختبارات الوحدة في نفس ملف الكود:
pub fn add(a: i32, b: i32) -> i32 {
a + b
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add() {
assert_eq!(add(2, 3), 5);
}
#[tokio::test]
async fn test_async_function() {
let result = async_function().await;
assert!(result.is_ok());
}
}
اختبارات التكامل (Integration Tests)
ضعها في دليل tests/:
// tests/integration_test.rs
use adk_core::*;
#[tokio::test]
async fn test_full_workflow() {
// الإعداد
let service = InMemorySessionService::new();
// التنفيذ
let session = service.create(request).await.unwrap();
// التأكيد
assert_eq!(session.id(), "test-session");
}
اختبارات المحاكاة (Mock Testing)
استخدم MockLlm للاختبار بدون استدعاءات API:
use adk_model::MockLlm;
#[tokio::test]
async fn test_agent_with_mock() {
let mock = MockLlm::new(vec![
"First response".to_string(),
"Second response".to_string(),
]);
let agent = LlmAgentBuilder::new("test")
.model(Arc::new(mock))
.build()
.unwrap();
// اختبار سلوك Agent
}
أوامر الاختبار (Test Commands)
# تشغيل جميع الاختبارات
cargo test --all
# تشغيل اختبارات Crate معين
cargo test --package adk-core
# التشغيل مع إظهار المخرجات
cargo test --all -- --nocapture
# تشغيل الاختبارات المتجاهلة (تتطلب مفاتيح API)
cargo test --all -- --ignored
التوثيق (Documentation)
تعليقات التوثيق (Doc Comments)
استخدم /// للعناصر العامة:
/// ينشئ LlmAgent جديدًا مع التكوين المحدد.
///
/// # الوسائط (Arguments)
///
/// * `name` - معرّف فريد لهذا Agent
/// * `model` - مزود LLM لاستخدامه في الاستدلال
///
/// # أمثلة (Examples)
///
/// ```rust
/// use adk_agent::LlmAgentBuilder;
///
/// let agent = LlmAgentBuilder::new("assistant")
/// .model(Arc::new(model))
/// .build()?;
/// ```
///
/// # الأخطاء (Errors)
///
/// يُعيد `AdkError::Agent` إذا لم يتم تعيين النموذج.
pub fn new(name: impl Into<String>) -> Self {
// ...
}
توثيق الوحدة (Module Documentation)
أضف توثيقًا على مستوى الوحدة في بداية lib.rs:
//! # adk-core
//!
//! الأنواع والسمات الأساسية لـ ADK-Rust.
//!
//! ## نظرة عامة (Overview)
//!
//! توفر هذه الكريت الأنواع التأسيسية...
ملفات README
يجب أن تحتوي كل crate على ملف README.md يتضمن:
- وصف موجز
- تعليمات التثبيت
- مثال سريع
- رابط للتوثيق الكامل
اختبارات التوثيق (Documentation Tests)
تأكد من أن أمثلة التوثيق تعمل بشكل صحيح:
cargo test --doc --all
عملية طلب السحب
قبل الإرسال
-
تشغيل مجموعة الاختبار الكاملة:
cargo test --all -
تشغيل clippy:
cargo clippy --all-targets --all-features -
تنسيق الكود:
cargo fmt --all -
تحديث التوثيق إذا كنت تضيف/تغير واجهة برمجة تطبيقات عامة
-
إضافة اختبارات للوظائف الجديدة
إرشادات طلب السحب
- العنوان: وصف واضح ومختصر للتغيير
- الوصف: اشرح ماذا ولماذا (وليس كيف)
- الحجم: حافظ على تركيز طلبات السحب؛ قسّم التغييرات الكبيرة
- الاختبارات: تضمين اختبارات للوظائف الجديدة
- التغييرات الجوهرية: وثّق بوضوح في الوصف
رسائل الالتزام
اتبع الالتزامات التقليدية:
feat: add OpenAI streaming support
fix: correct tool parameter validation
docs: update quickstart guide
refactor: simplify session state management
test: add integration tests for A2A protocol
المهام الشائعة
إضافة أداة جديدة
- إنشاء الأداة:
use adk_core::{Tool, ToolContext, Result};
use async_trait::async_trait;
use serde_json::Value;
pub struct MyTool {
// fields
}
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"Does something useful"
}
fn parameters_schema(&self) -> Option<Value> {
Some(serde_json::json!({
"type": "object",
"properties": {
"input": { "type": "string" }
},
"required": ["input"]
}))
}
async fn execute(&self, ctx: Arc<dyn ToolContext>, args: Value) -> Result<Value> {
let input = args["input"].as_str().unwrap_or_default();
Ok(serde_json::json!({ "result": input }))
}
}
- إضافة إلى Agent:
let agent = LlmAgentBuilder::new("agent")
.model(model)
.tool(Arc::new(MyTool::new()))
.build()?;
إضافة مزود نموذج جديد
- إنشاء وحدة في
adk-model/src/:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
- تطبيق السمة Llm:
use adk_core::{Llm, LlmRequest, LlmResponse, LlmResponseStream, Result};
pub struct MyModelClient {
api_key: String,
}
#[async_trait]
impl Llm for MyModelClient {
fn name(&self) -> &str {
"my-model"
}
async fn generate_content(
&self,
request: LlmRequest,
stream: bool,
) -> Result<LlmResponseStream> {
// Implementation
}
}
- إضافة علامة ميزة في
adk-model/Cargo.toml:
[features]
mymodel = ["dep:mymodel-sdk"]
- تصدير مشروط:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;
إضافة نوع Agent جديد
- إنشاء وحدة في
adk-agent/src/:
// adk-agent/src/my_agent.rs
use adk_core::{Agent, EventStream, InvocationContext, Result};
use async_trait::async_trait;
pub struct MyAgent {
name: String,
}
#[async_trait]
impl Agent for MyAgent {
fn name(&self) -> &str {
&self.name
}
fn description(&self) -> &str {
"My custom agent"
}
async fn run(&self, ctx: Arc<dyn InvocationContext>) -> Result<EventStream> {
// Implementation
}
}
- تصدير في
adk-agent/src/lib.rs:
mod my_agent;
pub use my_agent::MyAgent;
نصائح التصحيح
-
تمكين التتبع:
adk_telemetry::init_telemetry(); -
فحص الأحداث:
while let Some(event) = stream.next().await { eprintln!("Event: {:?}", event); } -
استخدام RUST_LOG:
RUST_LOG=debug cargo run --example myexample
السابق: ← التحكم في الوصول
أسئلة؟ افتح مشكلة على GitHub.