Agent-to-Agent (A2A) Protocol
Agent-to-Agent (A2A) Protocol は、Agent がネットワーク境界を越えて通信し、コラボレーションすることを可能にします。ADK-Rust は、A2A を介した Agent の公開と、リモートの A2A Agent の利用の両方を完全にサポートしています。
概要
A2A は次の場合に役立ちます。
- サードパーティの Agent サービスとの連携
- 特化した Agent を使用したマイクロサービスアーキテクチャの構築
- 複数言語間での Agent 通信の有効化
- Agent システム間の正式な契約の強制
単純な内部構成には、パフォーマンス向上のため A2A の代わりにローカルの sub-agent を使用してください。
Agent Card
すべての A2A Agent は、その capabilities を記述する Agent card を公開します。この card は自動的に生成され、/.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 card には以下が含まれます。
- Agent の名前と説明
- 通信用のベース URL
- Capabilities (streaming、state history など)
- Agent とその sub-agent から派生した Skills
A2A を介した Agent の公開
他の Agent が利用できるように Agent を公開するには、A2A エンドポイントを持つ HTTP サーバーを作成します。
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 cardPOST /a2a- A2A protocol 用の JSON-RPC エンドポイントPOST /a2a/stream- SSE streaming エンドポイント
リモート Agent の利用
リモート A2A Agent と通信するには、RemoteA2aAgent を使用します。
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 は次のことを行います。
- リモート URL から Agent card を自動的にフェッチします
- ADK イベントを A2A protocol メッセージに、またその逆に変換します
- streaming レスポンスを処理します
- sub-agent としてシームレスに機能します
A2A Client
リモート Agent と直接通信するには、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 は JSON-RPC 2.0 を使用して A2A プロトコルを実装しています。サポートされているメソッド:
message/send
エージェントにメッセージを送信します:
{
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"message": {
"role": "user",
"messageId": "msg-123",
"parts": [{"text": "Hello!"}]
}
},
"id": 1
}
応答には、ステータスと成果物を含むタスクオブジェクトが含まれます。
message/stream
message/send と同じですが、ストリーミング応答のために Server-Sent Events (SSE) を返します。
tasks/cancel
実行中のタスクをキャンセルします:
{
"jsonrpc": "2.0",
"method": "tasks/cancel",
"params": {
"taskId": "task-123"
},
"id": 1
}
マルチエージェントの例
ローカルエージェントとリモートエージェントを組み合わせます:
// 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: 内部エラー
ベストプラクティス
- エージェントカードの使用: 通信する前に必ずエージェントカードを取得し、検証してください
- ストリーミングの処理: 時間のかかる操作にはストリーミングを使用してください
- エラー回復: ネットワーク障害のために再試行ロジックを実装してください
- タイムアウト: リモート呼び出しには適切なタイムアウトを設定してください
- セキュリティ: 本番環境では HTTPS を使用し、認証を実装してください
セキュリティ設定
本番環境のデプロイメントのために、CORS、タイムアウト、およびセキュリティヘッダーを設定します:
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秒)
- ボディサイズ制限: 最大リクエストボディサイズ (デフォルト: 10MB)
- セキュリティヘッダー: X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
- エラーサニタイズ: 内部エラーはログに記録されますが、本番環境ではクライアントに公開されません
関連
- LlmAgent - エージェントの作成
- Multi-Agent Systems - サブエージェントと階層
- Server Deployment - HTTP サーバーとしてのエージェントの実行