Agent-to-Agent(A2A)プロトコル

ADK-Rust は、ネットワーク間のエージェント通信向けに A2A Protocol v1.0.0 を実装しています。実装は adk-server の a2a-v1 機能フラグの背後にあり、11 個すべての JSON-RPC 操作、REST バインディング、エージェントカードの検出、バージョンネゴシエーションをカバーしています。仕様で許可されているものよりセマンティクスが狭い 1 つの操作については、操作のカバレッジ を参照してください。ワイヤー型は a2a-protocol-types によって提供されます。これは、@tomtom215 による Foundation 検証済み Rust A2A SDK です(a2a-rust)。

概要

A2A は、次の場合に役立ちます。

  • サードパーティのエージェントサービスとの統合
  • 特化型エージェントを使用したマイクロサービスアーキテクチャの構築
  • クロスランゲージのエージェント通信の実現(A2A クライアントを持つ任意の言語)
  • エージェントシステム間の正式な契約の適用

単純な内部組織化には、パフォーマンス向上のため A2A ではなくローカルサブエージェントを使用してください。

v1.0.0 準拠

実装は A2A Protocol v1.0.0 仕様に完全準拠しています。

機能仕様セクションステータス
機能宣言を含むエージェントカード§8✅
すべてのタスクステータス変更における RFC 3339 タイムスタンプ§5.6.1✅
SendMessage のメッセージ ID の冪等性§3.3.1✅
プッシュ通知の認証(Bearer + トークン)§13.2✅
INPUT_REQUIRED のマルチターン再開フロー§3.4.3✅
入力検証(パーツ、ID、メタデータサイズ)§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 を介したエージェントの公開

LLM 統合を使用して、完全な A2A v1.0.0 サーバーを構築します。

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 エンドポイント(v1 の 11 個すべての操作。操作の対応範囲を参照)
  • REST — すべての操作に対応するルート
  • A2A-Version — すべてのルートでのヘッダーネゴシエーション

JSON-RPC 操作

A2A v1.0.0 の 11 個すべての操作がサポートされています。

メソッド説明
SendMessageメッセージを送信し、タスクを作成/再開します
SendStreamingMessageSendMessage と同じですが、SSE ストリームを返します
GetTaskIDでタスクを取得
CancelTask実行中のタスクをキャンセル
ListTasksフィルタリングとページネーションを使用してタスクを一覧表示
SubscribeToTaskSSEを介してタスクの更新を購読
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 に遷移させ、新しいメッセージを履歴に追加して処理を続行します。

冪等性

同じ messageId を持つ重複した SendMessage リクエストは、再処理せずに以前作成されたタスクを返します。これは SendMessage と SendStreamingMessage の両方に適用されます。

プッシュ通知の認証

クライアントが CreateTaskPushNotificationConfig を介して webhook を登録すると、サーバーは webhook 配信に認証ヘッダーを含めます。

  • authentication フィールドにベアラー認証情報がある場合 — Authorization: Bearer <credentials>
  • token フィールドが存在する場合 — a2a-notification-token: <token>

両方のヘッダーを同時に設定できます。SSRF 保護により、webhook の URLs がプライベート IP 範囲および localhost に対して検証されます。

入力の検証

受信したすべてのリクエストは、処理前に検証されます。

検証エラー
パーツがゼロのメッセージInvalidParams (-32602)
空または空白のみの messageIdInvalidParams (-32602)
256 文字を超える messageIdInvalidParams (-32602)
空または空白のみの taskIdInvalidParams (-32602)
256 文字を超える taskIdInvalidParams (-32602)
64 KB を超えるメタデータ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 の完全なサンプルエージェントが2つ含まれています。

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. Webhook URLs を検証する — SSRF 保護は組み込まれているが、本番環境では HTTPS を使用する
  5. 適切なタイムアウトを設定する — リモートエージェント呼び出しのリクエストタイムアウトを設定する
  6. べき等性を使用する — クライアントは、同じ messageId を使用して SendMessage を安全に再試行できる

前へ: ← サーバー | 次へ: 評価 →

操作のカバレッジ

v1 の JSON-RPC の全11操作がディスパッチされ、実装されています。

操作ステータス
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 上に手作業で構築されており、サーバーランタイムではなくワイヤー型を提供するため、 キューはまだ存在しません。

ライブ更新が必要な場合は SendStreamingMessage を使用してください。

ストリーミングイベントの契約

SendStreamingMessage はエージェントイベントを到着時に変換します。

エージェントイベントA2A イベント
最初、出力前Task、次に TaskStatusUpdateEvent — Working
コンテンツ、partial = trueTaskArtifactUpdateEvent — append、最後のチャンクではない
コンテンツ、partial = falseTaskArtifactUpdateEvent — 最終チャンク
ストリーム終了TaskStatusUpdateEvent — Completed
ストリームエラーTaskStatusUpdateEvent — Failed

1 回のレスポンスのすべてのチャンクは、1 つのアーティファクト ID を共有します。結合されたテキストは永続化されるため、後続の GetTask はストリーミングされた内容を返します。これは、adk-python と adk-go がそれぞれの SDKs に対して実装するコントラクトに準拠しています。