إرشادات التطوير

يوفر هذا المستند إرشادات شاملة للمطورين المساهمين في adk-rust. يضمن اتباع هذه المعايير جودة الكود والاتساق وقابلية الصيانة عبر المشروع.

جدول المحتويات

البدء

المتطلبات الأساسية

  • 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 بترتيب التبعية:

  1. adk-core (لا توجد تبعيات داخلية)
  2. adk-telemetry
  3. adk-model
  4. adk-tool
  5. adk-session
  6. adk-artifact
  7. adk-memory
  8. adk-agent
  9. adk-runner
  10. adk-server
  11. adk-cli
  12. adk-realtime
  13. adk-graph
  14. adk-browser
  15. adk-eval
  16. adk-rust (شاملة)

نمط الكود

مبادئ عامة

  1. الوضوح فوق الذكاء: اكتب كودًا سهل القراءة والفهم
  2. الصراحة فوق الضمنية: فضل الأنواع الصريحة ومعالجة الأخطاء الواضحة
  3. الدوال الصغيرة: اجعل الدوال مركزة وأقل من 50 سطرًا كلما أمكن ذلك
  4. الأسماء ذات المعنى: استخدم أسماء متغيرة ودوال وصفية

التنسيق

استخدم rustfmt بالإعدادات الافتراضية:

cargo fmt --all

تفرض CI pipeline التنسيق. قم دائمًا بتشغيل cargo fmt قبل الالتزام.

اصطلاحات التسمية

النوعالاصطلاحمثال
Cratesadk-* (kebab-case)adk-core, adk-agent
Modulessnake_casellm_agent, function_tool
Types/TraitsPascalCaseLlmAgent, ToolContext
Functionssnake_caseexecute_tool, run_agent
ConstantsSCREAMING_SNAKE_CASEKEY_PREFIX_APP
Type parametersحرف كبير واحد أو PascalCaseT, 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 يتضمن:

  1. وصف موجز
  2. تعليمات التثبيت
  3. مثال سريع
  4. رابط للتوثيق الكامل

اختبارات التوثيق (Documentation Tests)

تأكد من أن أمثلة التوثيق تعمل بشكل صحيح:

cargo test --doc --all

عملية طلب السحب

قبل الإرسال

  1. تشغيل مجموعة الاختبار الكاملة:

    cargo test --all
  2. تشغيل clippy:

    cargo clippy --all-targets --all-features
  3. تنسيق الكود:

    cargo fmt --all
  4. تحديث التوثيق إذا كنت تضيف/تغير واجهة برمجة تطبيقات عامة

  5. إضافة اختبارات للوظائف الجديدة

إرشادات طلب السحب

  • العنوان: وصف واضح ومختصر للتغيير
  • الوصف: اشرح ماذا ولماذا (وليس كيف)
  • الحجم: حافظ على تركيز طلبات السحب؛ قسّم التغييرات الكبيرة
  • الاختبارات: تضمين اختبارات للوظائف الجديدة
  • التغييرات الجوهرية: وثّق بوضوح في الوصف

رسائل الالتزام

اتبع الالتزامات التقليدية:

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

المهام الشائعة

إضافة أداة جديدة

  1. إنشاء الأداة:
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 }))
    }
}
  1. إضافة إلى Agent:
let agent = LlmAgentBuilder::new("agent")
    .model(model)
    .tool(Arc::new(MyTool::new()))
    .build()?;

إضافة مزود نموذج جديد

  1. إنشاء وحدة في adk-model/src/:
// adk-model/src/mymodel/mod.rs
mod client;
pub use client::MyModelClient;
  1. تطبيق السمة 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
    }
}
  1. إضافة علامة ميزة في adk-model/Cargo.toml:
[features]
mymodel = ["dep:mymodel-sdk"]
  1. تصدير مشروط:
#[cfg(feature = "mymodel")]
pub mod mymodel;
#[cfg(feature = "mymodel")]
pub use mymodel::MyModelClient;

إضافة نوع Agent جديد

  1. إنشاء وحدة في 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
    }
}
  1. تصدير في adk-agent/src/lib.rs:
mod my_agent;
pub use my_agent::MyAgent;

نصائح التصحيح

  1. تمكين التتبع:

    adk_telemetry::init_telemetry();
  2. فحص الأحداث:

    while let Some(event) = stream.next().await {
       eprintln!("Event: {:?}", event);
    }
  3. استخدام RUST_LOG:

    RUST_LOG=debug cargo run --example myexample

السابق: ← التحكم في الوصول

أسئلة؟ افتح مشكلة على GitHub.