Agent-zu-Agent (A2A)-Protokoll
ADK-Rust implementiert das A2A Protocol v1.0.0 für die netzwerkübergreifende Kommunikation zwischen Agenten. Die Implementierung befindet sich in adk-server hinter dem Feature-Flag a2a-v1 und deckt alle 11 JSON-RPC-Operationen, REST-Bindungen, die Erkennung von Agentenkarten und die Versionsaushandlung ab. Siehe Abdeckung der Operationen für die eine Operation, deren Semantik enger gefasst ist, als es die Spezifikation erlaubt. Die Wire-Typen werden von a2a-protocol-types bereitgestellt — dem von der Foundation verifizierten Rust-A2A SDK von @tomtom215 (a2a-rust).
Überblick
A2A ist nützlich für:
- Die Integration von Agentendiensten Dritter
- Den Aufbau von Microservices-Architekturen mit spezialisierten Agenten
- Die Ermöglichung einer sprachübergreifenden Kommunikation zwischen Agenten (jede Sprache mit einem A2A-Client)
- Die Durchsetzung formaler Verträge zwischen Agentensystemen
Für eine einfache interne Organisation sollten Sie lokale Unteragenten anstelle von A2A verwenden, um eine bessere Leistung zu erzielen.
Konformität mit v1.0.0
Die Implementierung ist vollständig konform mit der Spezifikation des A2A Protocol v1.0.0:
| Funktion | Spezifikationsabschnitt | Status |
|---|---|---|
| Agentenkarte mit Fähigkeitendeklaration | §8 | ✅ |
| RFC-3339-Zeitstempel bei allen Änderungen des Aufgabenstatus | §5.6.1 | ✅ |
Idempotenz der Nachrichten-ID für SendMessage | §3.3.1 | ✅ |
| Authentifizierung von Push-Benachrichtigungen (Bearer + token) | §13.2 | ✅ |
| Ablauf zum Fortsetzen von INPUT_REQUIRED über mehrere Runden | §3.4.3 | ✅ |
| Eingabevalidierung (Teile, IDs, Metadatengröße) | §3.3 | ✅ |
Content-Type: application/a2a+json in Antworten | §9 | ✅ |
| Aufgabenobjekt als erstes Streaming-Ereignis SSE | §3.1.2 | ✅ |
| Kontextbezogene Aufgabensuche für mehrere Interaktionen | §3.4.1 | ✅ |
Versionsaushandlung (A2A-Version-Header) | §9.1 | ✅ |
| Zustandsautomatenvalidierung (Endzustände) | §4.1.3 | ✅ |
Agentenkarten
Jeder A2A-Agent stellt unter /.well-known/agent-card.json eine Agentenkarte bereit, die seine Fähigkeiten, Kompetenzen und unterstützten Schnittstellen beschreibt.
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),
);
Die Agentenkarte umfasst:
- Name, Beschreibung und Version des Agenten
- Unterstützte Schnittstellen mit Protokollbindung und Version
- Fähigkeiten:
streaming,pushNotifications,extendedAgentCard - Aus der Agentenkonfiguration abgeleitete Kompetenzen
- Standard-Eingabe-/Ausgabemodi
Fähigkeiten werden jetzt explizit über den Parameter AgentCapabilities deklariert – keine fest codierten Standardwerte mehr.
Einen Agenten über A2A v1 bereitstellen
Erstellen Sie einen vollständigen A2A-v1.0.0-Server mit LLM-Integration:
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?;
Dies stellt Folgendes bereit:
GET /.well-known/agent-card.json– Agentenkarte mit ETag-CachingPOST /jsonrpc– JSON-RPC-Endpunkt (alle 11 v1-Operationen; siehe Abdeckung der Operationen)- REST-Routen für alle Operationen
A2A-Version-Header-Aushandlung auf allen Routen
JSON-RPC-Operationen
Alle 11 A2A-v1.0.0-Operationen werden unterstützt:
| Methode | Beschreibung |
|---|---|
SendMessage | Eine Nachricht senden, eine Aufgabe erstellen/fortsetzen |
SendStreamingMessage | Wie SendMessage, gibt jedoch einen SSE-Stream zurück |
GetTask | Eine Aufgabe anhand ihrer ID abrufen |
CancelTask | Eine laufende Aufgabe abbrechen |
ListTasks | Aufgaben mit Filterung und Paginierung auflisten |
SubscribeToTask | Aufgabenaktualisierungen über SSE abonnieren |
CreateTaskPushNotificationConfig | Einen Webhook für Push-Benachrichtigungen registrieren |
GetTaskPushNotificationConfig | Eine Konfiguration für Push-Benachrichtigungen abrufen |
ListTaskPushNotificationConfigs | Push-Konfigurationen für eine Aufgabe auflisten |
DeleteTaskPushNotificationConfig | Eine Konfiguration für Push-Benachrichtigungen entfernen |
GetExtendedAgentCard | Erweiterte Agentenkarte abrufen |
SendMessage
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-123",
"role": "ROLE_USER",
"parts": [{"text": "Research quantum computing"}]
}
}
}
Die Antwort enthält ein Task-Objekt mit Status, Verlauf und Artefakten. Die Antwort verwendet Content-Type: application/a2a+json.
SendStreamingMessage
Dasselbe Anfrageformat wie SendMessage. Gibt einen SSE-Datenstrom zurück, bei dem:
- Das erste Ereignis ein vollständiges
Task-Objekt ist (gemäß Spezifikation §3.1.2) - Nachfolgende Ereignisse
TaskStatusUpdateEventsind (Working, Completed usw.) - Artefakt-Ereignisse
TaskArtifactUpdateEventsind
Mehrere Gesprächsrunden
Wenn eine Aufgabe den Zustand INPUT_REQUIRED erreicht, senden Sie eine Folgenachricht mit derselben contextId, um sie fortzusetzen:
{
"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"}]
}
}
}
Der Handler findet die vorhandene Aufgabe automatisch anhand von contextId, überführt sie von INPUT_REQUIRED in Working, fügt die neue Nachricht zum Verlauf hinzu und setzt die Verarbeitung fort.
Idempotenz
Doppelte SendMessage-Anfragen mit derselben messageId geben die zuvor erstellte Aufgabe zurück, ohne sie erneut zu verarbeiten. Dies gilt sowohl für SendMessage als auch für SendStreamingMessage.
Authentifizierung von Push-Benachrichtigungen
Wenn ein Client über CreateTaskPushNotificationConfig einen Webhook registriert, fügt der Server den Webhook-Zustellungen Authentifizierungs-Header hinzu:
Authorization: Bearer <credentials>— wenn das FeldauthenticationBearer-Zugangsdaten enthälta2a-notification-token: <token>— wenn das Feldtokenvorhanden ist
Beide Header können gleichzeitig gesetzt werden. Der SSRF-Schutz validiert URLs des Webhooks anhand privater IP-Bereiche und des Localhost.
Eingabevalidierung
Alle eingehenden Anfragen werden vor der Verarbeitung validiert:
| Validierung | Fehler |
|---|---|
| Nachricht mit null Teilen | InvalidParams (-32602) |
| Leere oder ausschließlich aus Leerzeichen bestehende messageId | InvalidParams (-32602) |
| messageId mit mehr als 256 Zeichen | InvalidParams (-32602) |
| Leere oder ausschließlich aus Leerzeichen bestehende taskId | InvalidParams (-32602) |
| taskId mit mehr als 256 Zeichen | InvalidParams (-32602) |
| Metadaten mit mehr als 64 KB | InvalidParams (-32602) |
Einen Remote-Agent konsumieren
Verwenden Sie RemoteA2aAgent, um mit einem Remote-A2A-Agenten zu kommunizieren:
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-Client
Für die direkte Kommunikation auf Protokollebene:
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?;
Fehlerbehandlung
A2A-Fehler werden sowohl JSON-RPC-Codes als auch HTTP-Statuscodes zugeordnet:
| Fehler | JSON-RPC Code | HTTP Status |
|---|---|---|
| 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 |
| Intern | -32603 | 500 |
Ausführen der Beispiele
Zwei vollständige Beispielagenten für A2A v1.0.0 sind enthalten:
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
Der Client validiert: die Erkennung der Agentenkarte, SendMessage (beide Agenten mit echtem LLM), GetTask, ListTasks, den CancelTask-Fehlerpfad, SendStreamingMessage, CRUD für Push-Benachrichtigungen, GetExtendedAgentCard, die Versionsaushandlung und Fehlerpfade.
Best Practices
- Fähigkeiten korrekt deklarieren —
streamingundpushNotificationsbasierend darauf festlegen, was Ihr Agent tatsächlich unterstützt - Streaming für lange Vorgänge verwenden —
SendStreamingMessageliefert Clients Fortschritt in Echtzeit - Mehrstufige Abläufe verarbeiten —
contextIdverwenden, um den Konversationsstatus über mehrere Nachrichten hinweg beizubehalten - Webhook-URLs validieren — der SSRF-Schutz ist integriert, aber in der Produktion HTTPS verwenden
- Geeignete Zeitüberschreitungen festlegen — Zeitüberschreitungen für Anfragen an entfernte Agenten konfigurieren
- Idempotenz verwenden — Clients können
SendMessagemit derselbenmessageIdsicher wiederholen
Verwandte Themen
- LlmAgent — Agenten erstellen
- Multi-Agenten-Systeme — Unteragenten und Hierarchien
- Serverbereitstellung — Agenten als HTTP-Server ausführen
Zurück: ← Server | Weiter: Evaluierung →
Abdeckung der Operationen
Alle 11 v1-JSON-RPC-Operationen werden weitergeleitet und implementiert.
| Vorgang | Status |
|---|---|
SendMessage | Steuert den Agenten und zeichnet seine Ausgabe als Artefakt auf |
SendStreamingMessage | Steuert den Agenten und streamt Artefaktabschnitte, sobald sie erzeugt werden |
GetTask, ListTasks | Vollständig |
CancelTask | Vollständig |
SubscribeToTask (tasks/resubscribe) | Nur Momentaufnahme — siehe unten |
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig | Vollständig |
GetExtendedAgentCard | Vollständig |
SubscribeToTask ist eine Momentaufnahme
Die Operation gibt die Aufgabe und ihren aktuellen Status zurück und schließt anschließend den Stream. Sie liefert keine nachfolgenden Aktualisierungen, daher darf ein Client nicht auf Fortschrittsmeldungen warten.
Eine erneute Live-Verbindung erfordert eine aufgabenbezogene Ereigniswarteschlange, die über die
ursprüngliche Anfrage hinaus bestehen bleibt. Die Referenzimplementierungen beziehen diese aus ihren
A2A SDKs — sowohl adk-python als auch adk-go delegieren
tasks/resubscribe vollständig an den Warteschlangenmanager von SDK und implementieren dies nicht im
ADK-Code.
Dieser Server ist auf a2a-protocol-types eigenständig implementiert, das Wire-Typen statt einer
Server-Laufzeitumgebung bereitstellt; daher existiert die Warteschlange noch nicht.
Verwenden Sie SendStreamingMessage, wenn Live-Aktualisierungen erforderlich sind.
Streaming-Ereignisvertrag
SendStreamingMessage übersetzt Agentenereignisse, sobald sie eintreffen:
| Ereignis des Agents | A2A Ereignis |
|---|---|
| zuerst, vor der Ausgabe | Task, dann TaskStatusUpdateEvent — Working |
Inhalt, partial = true | TaskArtifactUpdateEvent — append, nicht der letzte Chunk |
Inhalt, partial = false | TaskArtifactUpdateEvent — letzter Abschnitt |
| Stream endet | TaskStatusUpdateEvent — Completed |
| Stream-Fehler | TaskStatusUpdateEvent — Failed |
Alle Teile einer Antwort teilen sich eine Artefakt-ID, sodass ein Client sie wieder zusammensetzen kann. Der zusammengefügte Text wird dauerhaft gespeichert, sodass ein späterer GetTask das zurückgibt, was gestreamt wurde. Dies entspricht dem Vertrag, den adk-python und adk-go über ihr SDKs implementieren.