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 | メッセージを送信し、タスクを作成/再開します |
SendStreamingMessage | SendMessage と同じですが、SSE ストリームを返します |
GetTask | IDでタスクを取得 |
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 ストリームを返します。
- 最初のイベントは完全な
Taskオブジェクトです(仕様 §3.1.2 に準拠) - 後続のイベントは
TaskStatusUpdateEventです(Working、Completed など) - アーティファクトイベントは
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) |
| 空または空白のみの messageId | InvalidParams (-32602) |
| 256 文字を超える messageId | InvalidParams (-32602) |
| 空または空白のみの taskId | InvalidParams (-32602) |
| 256 文字を超える taskId | InvalidParams (-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 | -32001 | 404 |
| TaskNotCancelable | -32002 | 409 |
| PushNotificationNotSupported | -32003 | 400 |
| UnsupportedOperation | -32004 | 400 |
| ContentTypeNotSupported | -32005 | 415 |
| InvalidAgentResponse | -32006 | 502 |
| VersionNotSupported | -32009 | 400 |
| InvalidParams | -32602 | 400 |
| MethodNotFound | -32601 | 404 |
| 内部 | -32603 | 500 |
例の実行
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、バージョンネゴシエーション、およびエラーパスを検証します。
ベストプラクティス
- 機能を正確に宣言する — エージェントが実際にサポートする内容に基づいて
streaming、pushNotificationsを設定する - 長時間の操作にはストリーミングを使用する —
SendStreamingMessageにより、クライアントにリアルタイムの進行状況を提供できる - 複数ターンのフローを処理する —
contextIdを使用して、メッセージ間で会話状態を維持する - Webhook URLs を検証する — SSRF 保護は組み込まれているが、本番環境では HTTPS を使用する
- 適切なタイムアウトを設定する — リモートエージェント呼び出しのリクエストタイムアウトを設定する
- べき等性を使用する — クライアントは、同じ
messageIdを使用してSendMessageを安全に再試行できる
関連項目
- LlmAgent — エージェントの作成
- マルチエージェントシステム — サブエージェントと階層構造
- サーバーのデプロイ — エージェントを HTTP サーバーとして実行する
操作のカバレッジ
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 = true | TaskArtifactUpdateEvent — append、最後のチャンクではない |
コンテンツ、partial = false | TaskArtifactUpdateEvent — 最終チャンク |
| ストリーム終了 | TaskStatusUpdateEvent — Completed |
| ストリームエラー | TaskStatusUpdateEvent — Failed |
1 回のレスポンスのすべてのチャンクは、1 つのアーティファクト ID を共有します。結合されたテキストは永続化されるため、後続の GetTask はストリーミングされた内容を返します。これは、adk-python と adk-go がそれぞれの SDKs に対して実装するコントラクトに準拠しています。