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

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

نظرة عامة

يكون A2A مفيدًا عندما:

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

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

الالتزام بـ v1.0.0

التنفيذ متوافق بالكامل مع مواصفة A2A Protocol v1.0.0:

الميزةقسم المواصفةالحالة
بطاقة الوكيل مع إعلان القدرات§8
طوابع زمنية وفق RFC 3339 على جميع تغييرات حالة المهمة§5.6.1
عدم تكرار معرّف الرسالة لـ SendMessage§3.3.1
مصادقة إشعار الدفع (Bearer + token)§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-2.5-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 (جميع عمليات v1 الـ 11؛ راجع تغطية العمليات)
  • مسارات REST لجميع العمليات
  • تفاوض ترويسة A2A-Version على جميع المسارات

عمليات JSON-RPC

جميع عمليات A2A v1.0.0 الـ 11 مدعومة:

الطريقةالوصف
SendMessageإرسال رسالة، إنشاء/استئناف مهمة
SendStreamingMessageنفس SendMessage ولكن يُرجع تدفّق SSE
GetTaskاسترجاع مهمة بواسطة المعرف
CancelTaskإلغاء مهمة قيد التشغيل
ListTasksعرض المهام مع التصفية والتقسيم إلى صفحات
SubscribeToTaskالاشتراك في تحديثات المهام عبر SSE
CreateTaskPushNotificationConfigتسجيل webhook لإشعارات الدفع
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"}]
    }
  }
}

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

SendStreamingMessage

نفس تنسيق الطلب كما في SendMessage. يعيد تدفق SSE حيث:

  1. الحدث الأول هو كائن Task كامل (وفق المواصفة §3.1.2)
  2. الأحداث اللاحقة هي TaskStatusUpdateEvent (Working، Completed، إلخ.)
  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)
معرف الرسالة يتجاوز 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 الخاص بـ webhook — الحماية من SSRF مدمجة، لكن استخدم HTTPS في الإنتاج
  5. اضبط مهلات مناسبة — قم بتهيئة مهلات الطلبات لاستدعاءات الوكيل البعيد
  6. استخدم قابلية التكرار — يمكن للعملاء إعادة محاولة SendMessage بأمان باستخدام messageId نفسه

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

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

تُرسَل وتُنفَّذ جميع عمليات JSON-RPC الـ 11 الخاصة بـ v1.

العمليةالحالة
SendMessageيدير agent، ويسجل مخرجاته كأثر
SendStreamingMessageيدير agent، ويبث أجزاء الأثر أثناء إنتاجها
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، الذي يوفر أنواع الأسلاك بدلًا من بيئة تشغيل للخادم، لذا فإن الطابور غير موجود بعد.

استخدم SendStreamingMessage عندما تكون التحديثات المباشرة مطلوبة.

عقد أحداث البث

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

حدث الوكيلحدث A2A
أولًا، قبل الإخراجTask، ثم TaskStatusUpdateEventWorking
المحتوى، partial = trueTaskArtifactUpdateEventappend، ليس الجزء الأخير
المحتوى, partial = falseTaskArtifactUpdateEvent — الجزء الأخير
ينتهي التدفقTaskStatusUpdateEventCompleted
أخطاء التدفقTaskStatusUpdateEventFailed

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