بروتوكول من وكيل إلى وكيل (A2A)

يطبّق ADK-Rust بروتوكول A2A الإصدار 1.0.0 للاتصال بين الوكلاء عبر الشبكات. يوجد التنفيذ في adk-server خلف علامة الميزات a2a-v1، ويغطي جميع العمليات الإحدى عشرة JSON-RPC، وارتباطات REST، واكتشاف بطاقة الوكيل، والتفاوض على الإصدار. راجع تغطية العمليات للاطلاع على العملية الوحيدة التي تكون دلالاتها أضيق مما تسمح به المواصفة. تتوفر أنواع البيانات السلكية عبر a2a-protocol-types — وهي A2A SDK لـ Rust موثّقة من المؤسسة، بواسطة @tomtom215 (a2a-rust).

نظرة عامة

تكون A2A مفيدة عند:

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

للتنظيم الداخلي البسيط، استخدم الوكلاء الفرعيين المحليين بدلًا من A2A للحصول على أداء أفضل.

التوافق مع الإصدار 1.0.0

يتوافق التنفيذ بالكامل مع مواصفة بروتوكول A2A الإصدار 1.0.0:

الميزةقسم المواصفاتالحالة
بطاقة الوكيل مع إعلان الإمكانات§8✅
طوابع زمنية بتنسيق RFC 3339 لجميع تغييرات حالة المهمة§5.6.1✅
خاصية عدم تكرار معرّف الرسالة لـ SendMessage§3.3.1✅
مصادقة الإشعارات الفورية (Bearer + الرمز المميز)§13.2✅
تدفق الاستئناف متعدد الأدوار في INPUT_REQUIRED§3.4.3✅
التحقق من صحة الإدخال (الأجزاء، والمعرّفات، وحجم البيانات الوصفية)§3.3✅
Content-Type: application/a2a+json في الاستجابات§9✅
كائن المهمة كأول حدث بث SSE§3.1.2✅
البحث عن المهام ضمن نطاق السياق للتفاعلات متعددة الأدوار§3.4.1✅
التفاوض على الإصدار (رأس A2A-Version)§9.1✅
التحقق من آلة الحالات (الحالات النهائية)§4.1.3✅

بطاقات الوكلاء

يعرِض كل وكيل A2A بطاقة وكيل في /.well-known/agent-card.json تصف إمكاناته ومهاراته وواجهاته المدعومة.

use adk_server::a2a::v1::card::build_v1_agent_card;
use a2a_protocol_types::{AgentCapabilities, AgentSkill};

let card = build_v1_agent_card(
    "my-agent",
    "A helpful research agent",
    "http://localhost:3001/jsonrpc",
    "1.0.0",
    vec![AgentSkill {
        id: "research".to_string(),
        name: "Research & Summarize".to_string(),
        description: "Researches topics and produces structured summaries".to_string(),
        tags: vec!["research".to_string()],
        examples: None,
        input_modes: None,
        output_modes: None,
        security_requirements: None,
    }],
    AgentCapabilities::none()
        .with_streaming(true)
        .with_push_notifications(true),
);

تتضمن بطاقة الوكيل:

  • اسم الوكيل ووصفه وإصداره
  • الواجهات المدعومة مع ربط البروتوكول وإصداره
  • الإمكانات: streaming، pushNotifications، extendedAgentCard
  • المهارات المشتقة من إعداد الوكيل
  • أوضاع الإدخال والإخراج الافتراضية

يتم الآن التصريح عن الإمكانات صراحةً عبر المعامل AgentCapabilities — ولم تعد هناك قيم افتراضية مضمّنة برمجيًا.

إتاحة وكيل عبر A2A v1

أنشئ خادمًا كاملًا A2A v1.0.0 مع تكامل LLM:

use std::sync::Arc;
use a2a_protocol_types::{AgentCapabilities, AgentSkill};
use adk_agent::LlmAgentBuilder;
use adk_server::a2a::v1::card::{CachedAgentCard, build_v1_agent_card};
use adk_server::a2a::v1::executor::V1Executor;
use adk_server::a2a::v1::jsonrpc_handler::jsonrpc_handler;
use adk_server::a2a::v1::push::NoOpPushNotificationSender;
use adk_server::a2a::v1::request_handler::RequestHandler;
use adk_server::a2a::v1::rest_handler::rest_router;
use adk_server::a2a::v1::task_store::InMemoryTaskStore;
use adk_server::a2a::v1::version::version_negotiation;
use adk_runner::RunnerConfig;
use adk_session::InMemorySessionService;
use axum::Router;
use axum::routing::post;
use tokio::sync::RwLock;

// 1. Create your agent
let model = adk_model::GeminiModel::new(&api_key, "gemini-3.7-flash")?;
let agent = LlmAgentBuilder::new("my-agent")
    .description("A helpful agent")
    .model(Arc::new(model))
    .instruction("You are a helpful assistant.")
    .build()?;

// 2. Set up A2A infrastructure
let task_store = Arc::new(InMemoryTaskStore::new());
let executor = Arc::new(V1Executor::new(task_store.clone()));
let push_sender = Arc::new(NoOpPushNotificationSender);

// 3. Build agent card with capabilities
let card = build_v1_agent_card(
    "my-agent", "A helpful agent",
    "http://localhost:3001/jsonrpc", "1.0.0",
    vec![/* skills */],
    AgentCapabilities::none().with_streaming(true),
);
let cached_card = Arc::new(RwLock::new(CachedAgentCard::new(card)));

// 4. Create runner config for LLM invocation
let session_service = Arc::new(InMemorySessionService::new());
let runner_config = Arc::new(RunnerConfig {
    app_name: "my-agent".to_string(),
    agent: Arc::new(agent),
    session_service,
    artifact_service: None,
    memory_service: None,
    plugin_manager: None,
    run_config: None,
    compaction_config: None,
    context_cache_config: None,
    cache_capable: None,
    request_context: None,
    cancellation_token: None,
});

// 5. Wire up the handler and routes
let handler = Arc::new(RequestHandler::with_runner(
    executor, task_store, push_sender, cached_card, runner_config,
));

let app = Router::new()
    .route("/jsonrpc", post(jsonrpc_handler))
    .with_state(handler.clone())
    .merge(rest_router(handler))
    .layer(axum::middleware::from_fn(version_negotiation));

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

يتيح هذا:

  • GET /.well-known/agent-card.json — بطاقة الوكيل مع التخزين المؤقت باستخدام ETag
  • POST /jsonrpc — نقطة نهاية JSON-RPC (جميع العمليات الـ11 في v1؛ راجع تغطية العمليات)
  • مسارات REST لجميع العمليات
  • تفاوض ترويسة A2A-Version على جميع المسارات

عمليات JSON-RPC

يتم دعم جميع عمليات A2A الـ11 في الإصدار v1.0.0:

الطريقةالوصف
SendMessageإرسال رسالة، وإنشاء مهمة أو استئنافها
SendStreamingMessageمثل SendMessage، ولكنها تُرجع تدفق SSE
GetTaskاسترداد مهمة باستخدام المعرّف
CancelTaskإلغاء مهمة قيد التشغيل
ListTasksسرد المهام مع التصفية والتقسيم إلى صفحات
SubscribeToTaskالاشتراك في تحديثات المهام عبر SSE
CreateTaskPushNotificationConfigتسجيل خطاف ويب لإشعارات الدفع
GetTaskPushNotificationConfigاسترداد إعدادات إشعارات الدفع
ListTaskPushNotificationConfigsإدراج إعدادات الدفع لمهمة
DeleteTaskPushNotificationConfigإزالة إعدادات إشعارات الدفع
GetExtendedAgentCardاسترداد بطاقة الوكيل الموسّعة

SendMessage

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "msg-123",
      "role": "ROLE_USER",
      "parts": [{"text": "Research quantum computing"}]
    }
  }
}

يتضمن الرد كائن Task مع الحالة والسجل والعناصر الناتجة. يستخدم الرد Content-Type: application/a2a+json.

SendStreamingMessage

تنسيق الطلب نفسه المستخدم في SendMessage. يُرجع تدفق SSE حيث:

  1. يكون الحدث الأول كائن Task كاملًا (وفقًا للمواصفة §3.1.2)
  2. تكون الأحداث اللاحقة من نوع TaskStatusUpdateEvent (قيد العمل، مكتمل، وغير ذلك)
  3. تكون أحداث العناصر الناتجة من نوع TaskArtifactUpdateEvent

المحادثات متعددة الأدوار

عندما تصل مهمة إلى حالة INPUT_REQUIRED، أرسل رسالة متابعة مع نفس contextId لاستئنافها:

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "SendMessage",
  "params": {
    "message": {
      "messageId": "msg-456",
      "role": "ROLE_USER",
      "contextId": "ctx-original",
      "parts": [{"text": "Yes, include more details on error correction"}]
    }
  }
}

يعثر المعالج تلقائيًا على المهمة الحالية بواسطة contextId، وينقلها من INPUT_REQUIRED إلى Working، ويضيف الرسالة الجديدة إلى السجل، ثم يواصل المعالجة.

قابلية التنفيذ مرة واحدة

تُعيد طلبات SendMessage المكررة التي تتضمن نفس messageId المهمة التي أُنشئت سابقًا دون إعادة المعالجة. ينطبق ذلك على كل من SendMessage وSendStreamingMessage.

مصادقة إشعارات الدفع

عندما يسجل العميل عنوان webhook عبر CreateTaskPushNotificationConfig، يضمّن الخادم ترويسات المصادقة في عمليات تسليم webhook:

  • Authorization: Bearer <credentials> — عندما يحتوي الحقل authentication على بيانات اعتماد bearer
  • a2a-notification-token: <token> — عندما يكون الحقل token موجودًا

يمكن تعيين الترويستين في الوقت نفسه. تتحقق حماية SSRF من URLs الخاص بـ webhook مقابل نطاقات IP الخاصة وlocalhost.

التحقق من صحة الإدخال

تُتحقق صحة جميع الطلبات الواردة قبل معالجتها:

التحقق من الصحةالخطأ
رسالة بلا أجزاءInvalidParams (-32602)
messageId فارغ أو يتكون من مسافات بيضاء فقطInvalidParams (-32602)
messageId الذي يتجاوز 256 حرفًاInvalidParams (-32602)
taskId فارغ أو يتكون من مسافات بيضاء فقطInvalidParams (-32602)
taskId الذي يتجاوز 256 حرفًاInvalidParams (-32602)
البيانات الوصفية التي تتجاوز 64 كيلوبايتInvalidParams (-32602)

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

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

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 in a local agent hierarchy
let root_agent = LlmAgentBuilder::new("root")
    .model(Arc::new(model))
    .sub_agent(Arc::new(remote_agent))
    .build()?;

عميل A2A

للتواصل المباشر على مستوى البروتوكول:

use adk_server::a2a::client::v1_client::A2aV1Client;

// Discover agent card
let card = A2aV1Client::resolve_agent_card("http://localhost:3001").await?;
let client = A2aV1Client::new(card);

// Send message
let task = client.send_message(message).await?;

// Get task
let task = client.get_task(&task_id, Some(10)).await?;

// List tasks
let tasks = client.list_tasks(None, None, None, None).await?;

// Cancel task
client.cancel_task(&task_id).await?;

// Streaming
let response = client.send_streaming_message(message).await?;

// Push notification CRUD
let config = client.create_push_notification_config(config).await?;
client.delete_push_notification_config(&task_id, &config_id).await?;

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

تتوافق أخطاء A2A مع كلٍّ من رموز JSON-RPC ورموز الحالة HTTP:

الخطأJSON-RPC الرمزHTTP الحالة
TaskNotFound-32001404
TaskNotCancelable-32002409
PushNotificationNotSupported-32003400
UnsupportedOperation-32004400
ContentTypeNotSupported-32005415
InvalidAgentResponse-32006502
VersionNotSupported-32009400
InvalidParams-32602400
MethodNotFound-32601404
داخلي-32603500

تشغيل الأمثلة

يتضمن ذلك وكيلين كاملين من أمثلة A2A v1.0.0:

cargo run --manifest-path examples/a2a-research-agent/Cargo.toml
cargo run --manifest-path examples/a2a-writing-agent/Cargo.toml --bin a2a-writing-agent
cargo run --manifest-path examples/a2a-writing-agent/Cargo.toml --bin client

يتحقق العميل من: اكتشاف بطاقة الوكيل، وSendMessage (كلا الوكيلين مع LLM حقيقي)، وGetTask، وListTasks، ومسار الخطأ CancelTask، وSendStreamingMessage، وCRUD لإشعارات الدفع، وGetExtendedAgentCard، والتفاوض على الإصدار، ومسارات الخطأ.

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

  1. أعلن عن الإمكانات بدقة — اضبط streaming وpushNotifications بناءً على ما يدعمه وكيلك فعليًا
  2. استخدم البث للعمليات الطويلة — يوفّر SendStreamingMessage للعملاء تقدمًا في الوقت الفعلي
  3. تعامل مع التدفقات متعددة الأدوار — استخدم contextId للحفاظ على حالة المحادثة عبر الرسائل
  4. تحقق من URLs لخطاف الويب — الحماية من SSRF مضمّنة، لكن استخدم HTTPS في بيئة الإنتاج
  5. اضبط مهلات مناسبة — كوّن مهلات الطلبات لاستدعاءات الوكلاء البعيدة
  6. استخدم قابلية التكرار الآمن — يمكن للعملاء إعادة محاولة SendMessage بأمان باستخدام messageId نفسه

السابق: ← الخادم | التالي: التقييم →

تغطية العمليات

يتم توزيع جميع عمليات JSON-RPC الإحدى عشرة من الإصدار v1 وتنفيذها.

العمليةالحالة
SendMessageيشغّل الوكيل، ويسجّل مخرجاته كأثر
SendStreamingMessageيشغّل الوكيل، ويبث أجزاء الأثر عند إنتاجها
GetTask، ListTasksكامل
CancelTaskكامل
SubscribeToTask (tasks/resubscribe)لقطة فقط — انظر أدناه
CreateTaskPushNotificationConfig، GetTaskPushNotificationConfig، ListTaskPushNotificationConfigs، DeleteTaskPushNotificationConfigكامل
GetExtendedAgentCardكامل

SubscribeToTask هي لقطة

تعيد العملية المهمة وحالتها الحالية، ثم تغلق التدفق. وهي لا تسلّم التحديثات اللاحقة، لذلك يجب ألا ينتظر العميل التقدّم من خلالها.

تتطلب إعادة الاتصال المباشر قائمة انتظار أحداث لكل مهمة، تستمر بعد انتهاء الطلب الذي أنشأها. تحصل التنفيذات المرجعية على ذلك من A2A SDKs الخاصة بها — إذ يفوّض كلٌّ من adk-python وadk-go tasks/resubscribe بالكامل إلى مدير قوائم انتظار SDK، ولا ينفّذ أيٌّ منهما ذلك في شيفرة ADK. هذا الخادم مكتوب يدويًا باستخدام a2a-protocol-types، الذي يوفّر أنواع wire بدلًا من بيئة تشغيل للخادم، ولذلك لا توجد قائمة الانتظار بعد.

استخدم SendStreamingMessage عند الحاجة إلى التحديثات المباشرة.

عقد أحداث التدفق

يترجم SendStreamingMessage أحداث الوكيل فور وصولها:

حدث الوكيلA2A حدث
أولًا، قبل الإخراجTask، ثم TaskStatusUpdateEvent — Working
المحتوى، partial = trueTaskArtifactUpdateEvent — append، ليست القطعة الأخيرة
المحتوى، partial = falseTaskArtifactUpdateEvent — الجزء النهائي
انتهاء التدفقTaskStatusUpdateEvent — Completed
أخطاء التدفقTaskStatusUpdateEvent — Failed

تشترك جميع أجزاء الاستجابة الواحدة في معرّف قطعة أثرية، بحيث يمكن للعميل إعادة تجميعها. يُستمر النص المُجمّع، لذا يعيد استدعاء لاحق لـ GetTask ما تم بثّه. يتوافق هذا مع العقد الذي تنفّذه adk-python وadk-go عبر SDKs الخاص بهما.