بروتوكول Agent-to-Agent (A2A)

يتيح بروتوكول A2A Agents للمُساعدين التواصل والتعاون عبر حدود الشبكة. يوفر adk-rust دعمًا كاملاً لكل من عرض الـ Agents عبر A2A واستهلاك الـ Agents البعيدة عبر A2A.

نظرة عامة

يُعد A2A مفيدًا عندما:

  • يتم الدمج مع خدمات الـ Agent الخاصة بجهات خارجية
  • يتم بناء معماريات الخدمات المصغرة (microservices) باستخدام Agents متخصصة
  • يتم تمكين تواصل الـ Agent عبر اللغات
  • يتم فرض عقود رسمية بين أنظمة الـ Agent

للتنظيم الداخلي البسيط، استخدم الـ sub-agents المحلية بدلاً من A2A للحصول على أداء أفضل.

بطاقات الـ Agent

يقوم كل A2A Agent بعرض بطاقة Agent تصف قدراته. يتم إنشاء البطاقة تلقائيًا وتقديمها في /.well-known/agent.json.

use adk_server::a2a::build_agent_card;

let agent_card = build_agent_card(&agent, "http://localhost:8080");

println!("Agent: {}", agent_card.name);
println!("Skills: {}", agent_card.skills.len());
println!("Streaming: {}", agent_card.capabilities.streaming);

تتضمن بطاقة الـ Agent ما يلي:

  • اسم الـ Agent ووصفه
  • عنوان URL الأساسي للتواصل
  • القدرات (streaming، state history، إلخ.)
  • المهارات المستمدة من الـ Agent والـ sub-agents الخاصة به

عرض Agent عبر A2A

لعرض Agent بحيث يمكن للـ Agents الأخرى استخدامه، قم بإنشاء خادم HTTP بنقاط نهاية A2A:

use adk_server::{create_app_with_a2a, ServerConfig};
use adk_agent::LlmAgentBuilder;
use adk_model::gemini::GeminiModel;
use std::sync::Arc;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create your agent
    let model = GeminiModel::new(&api_key, "gemini-2.5-flash")?;
    let agent = LlmAgentBuilder::new("weather_agent")
        .description("Answers weather questions")
        .model(Arc::new(model))
        .build()?;

    // Create server config
    let config = ServerConfig::new(
        Arc::new(SingleAgentLoader::new(Arc::new(agent))),
        Arc::new(InMemorySessionService::new()),
    );

    // Create app with A2A support
    let app = create_app_with_a2a(config, Some("http://localhost:8080"));

    // Serve
    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;

    Ok(())
}

يقوم هذا بعرض ما يلي:

  • GET /.well-known/agent.json - بطاقة Agent
  • POST /a2a - نقطة نهاية JSON-RPC لبروتوكول A2A
  • POST /a2a/stream - نقطة نهاية بث SSE

استهلاك Agent بعيد

استخدم RemoteA2aAgent للتواصل مع A2A Agent بعيد:

use adk_server::a2a::RemoteA2aAgent;

let remote_agent = RemoteA2aAgent::builder("prime_checker")
    .description("Checks if numbers are prime")
    .agent_url("http://localhost:8001")
    .build()?;

// Use as a sub-agent
let root_agent = LlmAgentBuilder::new("root")
    .model(Arc::new(model))
    .sub_agent(Arc::new(remote_agent))
    .build()?;

يقوم RemoteA2aAgent:

  • بجلب بطاقة الـ Agent تلقائيًا من عنوان URL البعيد
  • بتحويل أحداث ADK إلى/من رسائل بروتوكول A2A
  • بمعالجة استجابات الـ streaming
  • بالعمل بسلاسة كـ sub-agent

عميل A2A

للتواصل المباشر مع الـ Agents البعيدة، استخدم A2aClient:

use adk_server::a2a::{A2aClient, Message, Part, Role};

// Create client from URL (fetches agent card)
let client = A2aClient::from_url("http://localhost:8080").await?;

// Build a message
let message = Message::builder()
    .role(Role::User)
    .parts(vec![Part::text("What's the weather?".to_string())])
    .message_id(uuid::Uuid::new_v4().to_string())
    .build();

// Send message (blocking)
let response = client.send_message(message.clone()).await?;

// Or send with streaming
let mut stream = client.send_streaming_message(message).await?;
while let Some(event) = stream.next().await {
    match event? {
        UpdateEvent::TaskArtifactUpdate(artifact) => {
            println!("Artifact: {:?}", artifact);
        }
        UpdateEvent::TaskStatusUpdate(status) => {
            println!("Status: {:?}", status.status.state);
        }
    }
}

بروتوكول JSON-RPC

ADK-Rust ينفذ بروتوكول A2A باستخدام JSON-RPC 2.0. الطرق المدعومة:

message/send

إرسال رسالة إلى Agent:

{
  "jsonrpc": "2.0",
  "method": "message/send",
  "params": {
    "message": {
      "role": "user",
      "messageId": "msg-123",
      "parts": [{"text": "Hello!"}]
    }
  },
  "id": 1
}

يتضمن الرد كائن task يحتوي على status و artifacts.

message/stream

مطابق لـ message/send ولكنه يُرجع Server-Sent Events (SSE) لتدفق الاستجابات.

tasks/cancel

إلغاء مهمة قيد التشغيل:

{
  "jsonrpc": "2.0",
  "method": "tasks/cancel",
  "params": {
    "taskId": "task-123"
  },
  "id": 1
}

مثال على Multi-Agent

الجمع بين agents المحلية والبعيدة:

// Local agent
let roll_agent = LlmAgentBuilder::new("roll_agent")
    .description("Rolls dice")
    .model(Arc::new(model.clone()))
    .tool(Arc::new(roll_die_tool))
    .build()?;

// Remote agent
let prime_agent = RemoteA2aAgent::builder("prime_agent")
    .description("Checks if numbers are prime")
    .agent_url("http://localhost:8001")
    .build()?;

// Root agent orchestrates both
let root_agent = LlmAgentBuilder::new("root_agent")
    .instruction("Delegate dice rolling to roll_agent and prime checking to prime_agent")
    .model(Arc::new(model))
    .sub_agent(Arc::new(roll_agent))
    .sub_agent(Arc::new(prime_agent))
    .build()?;

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

تُرجع عمليات A2A أخطاء ADK القياسية:

match client.send_message(message).await {
    Ok(response) => {
        if let Some(error) = response.error {
            eprintln!("RPC error: {} (code: {})", error.message, error.code);
        }
    }
    Err(e) => {
        eprintln!("Request failed: {}", e);
    }
}

رموز الأخطاء الشائعة:

  • -32600: طلب غير صالح
  • -32601: الطريقة غير موجودة
  • -32602: بارامترات غير صالحة
  • -32603: خطأ داخلي

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

  1. استخدم agent cards: قم دائمًا بجلب agent cards والتحقق من صحتها قبل الاتصال
  2. التعامل مع streaming: استخدم streaming للعمليات طويلة الأمد
  3. استعادة الأخطاء: نفذ منطق إعادة المحاولة لأخطاء الشبكة
  4. المهل الزمنية (Timeouts): اضبط مهلًا زمنية مناسبة للمكالمات البعيدة
  5. الأمان: استخدم HTTPS في بيئة الإنتاج وطبق authentication

إعدادات الأمان

قم بتكوين CORS، وtimeouts، وsecurity headers لعمليات النشر في بيئة الإنتاج:

use adk_server::{ServerConfig, SecurityConfig};
use std::time::Duration;

// Production configuration
let config = ServerConfig::new(agent_loader, session_service)
    .with_allowed_origins(vec![
        "https://myapp.com".to_string(),
        "https://admin.myapp.com".to_string(),
    ])
    .with_request_timeout(Duration::from_secs(30))
    .with_max_body_size(10 * 1024 * 1024);  // 10MB

// Or use presets
let dev_config = ServerConfig::new(agent_loader, session_service)
    .with_security(SecurityConfig::development());  // Permissive for dev

let prod_config = ServerConfig::new(agent_loader, session_service)
    .with_security(SecurityConfig::production(allowed_origins));

ميزات الأمان تتضمن:

  • CORS: مصادر مسموح بها قابلة للتكوين (افتراضي: متساهل للتطوير)
  • مهلة الطلب: مهلة قابلة للتكوين (افتراضي: 30 ثانية)
  • حد حجم النص الأساسي: الحد الأقصى لحجم نص الطلب (افتراضي: 10 ميجابايت)
  • رؤوس الأمان: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
  • تنقية الأخطاء: يتم تسجيل الأخطاء الداخلية ولكن لا يتم كشفها للعملاء في بيئة الإنتاج

ذات صلة


السابق: ← Server | التالي: Evaluation →