Protocole Agent-à-Agent (A2A)
ADK-Rust implémente le protocole A2A v1.0.0 pour la communication inter-agents entre réseaux. L’implémentation se trouve dans adk-server derrière l’indicateur de fonctionnalité a2a-v1 et couvre les 11 opérations JSON-RPC, les liaisons REST, la découverte des fiches d’agent et la négociation de version. Consultez Couverture des opérations pour connaître l’opération dont la sémantique est plus restreinte que ne l’autorise la spécification. Les types filaires sont fournis par a2a-protocol-types — le A2A SDK Rust vérifié par la Foundation, par @tomtom215 (a2a-rust).
Vue d’ensemble
A2A est utile pour :
- Intégrer des services d’agents tiers
- Concevoir des architectures de microservices avec des agents spécialisés
- Permettre la communication inter-agents entre différents langages (tout langage disposant d’un client A2A)
- Faire respecter des contrats formels entre les systèmes d’agents
Pour une organisation interne simple, utilisez des sous-agents locaux plutôt que A2A afin d’obtenir de meilleures performances.
Conformité v1.0.0
L’implémentation est entièrement conforme à la spécification du protocole A2A v1.0.0 :
| Fonctionnalité | Section de la spécification | Statut |
|---|---|---|
| Fiche d’agent avec déclaration des capacités | §8 | ✅ |
| Horodatages RFC 3339 pour tous les changements d’état des tâches | §5.6.1 | ✅ |
Idempotence de l’ID de message pour SendMessage | §3.3.1 | ✅ |
| Authentification des notifications push (Bearer + jeton) | §13.2 | ✅ |
| Flux de reprise multi-tours de INPUT_REQUIRED | §3.4.3 | ✅ |
| Validation des entrées (parties, identifiants, taille des métadonnées) | §3.3 | ✅ |
Content-Type: application/a2a+json dans les réponses | §9 | ✅ |
| Objet de tâche comme premier événement de streaming SSE | §3.1.2 | ✅ |
| Recherche de tâche limitée au contexte pour les interactions multi-tours | §3.4.1 | ✅ |
Négociation de version (en-tête A2A-Version) | §9.1 | ✅ |
| Validation de la machine à états (états terminaux) | §4.1.3 | ✅ |
Cartes d’agent
Chaque agent A2A expose une carte d’agent à /.well-known/agent-card.json décrivant ses capacités, ses compétences et ses interfaces prises en charge.
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),
);
La carte d’agent comprend :
- Le nom, la description et la version de l’agent
- Les interfaces prises en charge avec la liaison de protocole et la version
- Les capacités :
streaming,pushNotifications,extendedAgentCard - Les compétences dérivées de la configuration de l’agent
- Les modes d’entrée et de sortie par défaut
Les capacités sont désormais déclarées explicitement via le paramètre AgentCapabilities — fini les valeurs par défaut codées en dur.
Exposer un agent via A2A v1
Construisez un serveur A2A v1.0.0 complet avec l’intégration 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-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?;
Cela expose :
GET /.well-known/agent-card.json— Carte d’agent avec mise en cache ETagPOST /jsonrpc— Point de terminaison JSON-RPC (les 11 opérations de la v1 ; voir Couverture des opérations)- Les routes REST pour toutes les opérations
- La négociation des en-têtes
A2A-Versionsur toutes les routes
Opérations JSON-RPC
Les 11 opérations A2A v1.0.0 sont prises en charge :
| Méthode | Description |
|---|---|
SendMessage | Envoyer un message, créer/reprendre une tâche |
SendStreamingMessage | Identique à SendMessage, mais renvoie le flux SSE |
GetTask | Récupérer une tâche par ID |
CancelTask | Annuler une tâche en cours d’exécution |
ListTasks | Lister les tâches avec filtrage et pagination |
SubscribeToTask | S’abonner aux mises à jour de tâche via SSE |
CreateTaskPushNotificationConfig | Enregistrer un webhook pour les notifications push |
GetTaskPushNotificationConfig | Récupérer une configuration de notification push |
ListTaskPushNotificationConfigs | Répertorier les configurations push d’une tâche |
DeleteTaskPushNotificationConfig | Supprimer une configuration de notification push |
GetExtendedAgentCard | Récupérer la fiche étendue de l’agent |
SendMessage
{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"messageId": "msg-123",
"role": "ROLE_USER",
"parts": [{"text": "Research quantum computing"}]
}
}
}
La réponse inclut un objet Task avec un statut, un historique et des artefacts. La réponse utilise Content-Type: application/a2a+json.
SendStreamingMessage
Même format de requête que SendMessage. Renvoie un flux SSE où :
- Le premier événement est un objet
Taskcomplet (conformément à la spécification §3.1.2) - Les événements suivants sont des
TaskStatusUpdateEvent(Working, Completed, etc.) - Les événements d’artefact sont des
TaskArtifactUpdateEvent
Conversations multi-tours
Lorsqu’une tâche atteint l’état INPUT_REQUIRED, envoyez un message de suivi avec le même contextId pour la reprendre :
{
"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"}]
}
}
}
Le gestionnaire recherche automatiquement la tâche existante par contextId, la fait passer de INPUT_REQUIRED à Working, ajoute le nouveau message à l’historique et poursuit le traitement.
Idempotence
Les requêtes SendMessage en double avec le même messageId renvoient la tâche précédemment créée sans effectuer de nouveau traitement. Cela s’applique à la fois à SendMessage et à SendStreamingMessage.
Authentification des notifications push
Lorsqu’un client enregistre un webhook via CreateTaskPushNotificationConfig, le serveur inclut des en-têtes d’authentification dans les livraisons au webhook :
Authorization: Bearer <credentials>— lorsque le champauthenticationcontient des identifiants bearera2a-notification-token: <token>— lorsque le champtokenest présent
Les deux en-têtes peuvent être définis simultanément. La protection SSRF valide URLs du webhook par rapport aux plages d’adresses IP privées et à localhost.
Validation des entrées
Toutes les requêtes entrantes sont validées avant leur traitement :
| Validation | Erreur |
|---|---|
| Message sans parties | InvalidParams (-32602) |
| messageId vide ou composée uniquement d’espaces | InvalidParams (-32602) |
| messageId dépassant 256 caractères | InvalidParams (-32602) |
| taskId vide ou contenant uniquement des espaces | InvalidParams (-32602) |
| taskId dépassant 256 caractères | InvalidParams (-32602) |
| Métadonnées dépassant 64 Ko | InvalidParams (-32602) |
Consommer un agent distant
Utilisez RemoteA2aAgent pour communiquer avec un agent A2A distant :
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()?;
Client A2A
Pour une communication directe au niveau du protocole :
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?;
Gestion des erreurs
Les erreurs A2A correspondent à la fois aux codes JSON-RPC et aux codes d’état HTTP :
| Erreur | JSON-RPC Code | HTTP Statut |
|---|---|---|
| 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 |
| Interne | -32603 | 500 |
Exécution des exemples
Deux agents d’exemple complets A2A v1.0.0 sont inclus :
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
Le client valide : la découverte de la fiche de l’agent, SendMessage (les deux agents avec de véritables LLM), GetTask, ListTasks, le chemin d’erreur CancelTask, SendStreamingMessage, la CRUD de notification push, GetExtendedAgentCard, la négociation de version et les chemins d’erreur.
Bonnes pratiques
- Déclarer précisément les capacités — définir
streamingetpushNotificationsen fonction de ce que votre agent prend réellement en charge - Utiliser le streaming pour les opérations longues —
SendStreamingMessagefournit aux clients une progression en temps réel - Gérer les flux à plusieurs tours — utiliser
contextIdpour maintenir l’état de la conversation entre les messages - Valider URLs du webhook — la protection contre les SSRF est intégrée, mais utiliser HTTPS en production
- Définir des délais d’expiration appropriés — configurer les délais d’expiration des requêtes pour les appels à des agents distants
- Utiliser l’idempotence — les clients peuvent réessayer
SendMessageen toute sécurité avec le mêmemessageId
Voir aussi
- LlmAgent — Créer des agents
- Systèmes multi-agents — Sous-agents et hiérarchies
- Déploiement du serveur — Exécuter des agents en tant que serveurs HTTP
Précédent : ← Serveur | Suivant : Évaluation →
Couverture des opérations
Les 11 opérations v1 JSON-RPC sont toutes distribuées et implémentées.
| Opération | Statut |
|---|---|
SendMessage | Pilote l’agent et enregistre sa sortie en tant qu’artefact |
SendStreamingMessage | Pilote l’agent et diffuse les fragments de l’artefact au fur et à mesure de leur production |
GetTask, ListTasks | Complet |
CancelTask | Complet |
SubscribeToTask (tasks/resubscribe) | Instantané uniquement — voir ci-dessous |
CreateTaskPushNotificationConfig, GetTaskPushNotificationConfig, ListTaskPushNotificationConfigs, DeleteTaskPushNotificationConfig | Complet |
GetExtendedAgentCard | Complet |
SubscribeToTask est un instantané
L’opération renvoie la tâche et son état actuel, puis ferme le flux. Elle ne fournit pas les mises à jour ultérieures ; un client ne doit donc pas l’attendre pour suivre la progression.
Une reconnexion active nécessite une file d’événements par tâche qui survive à la requête
à l’origine de celle-ci. Les implémentations de référence l’obtiennent depuis leur
A2A SDKs — adk-python et adk-go délèguent toutes deux entièrement
tasks/resubscribe au gestionnaire de files de SDK, et aucune ne l’implémente dans du code
ADK. Ce serveur est développé manuellement sur a2a-protocol-types, qui fournit des types filaires
plutôt qu’un environnement d’exécution serveur ; la file n’existe donc pas encore.
Utilisez SendStreamingMessage lorsque des mises à jour en direct sont nécessaires.
Contrat des événements diffusés
SendStreamingMessage traduit les événements de l’agent au fur et à mesure de leur arrivée :
| Événement de l’agent | Événement A2A |
|---|---|
| premier, avant la sortie | Task, puis TaskStatusUpdateEvent — Working |
contenu, partial = true | TaskArtifactUpdateEvent — append, pas le dernier fragment |
contenu, partial = false | TaskArtifactUpdateEvent — dernier fragment |
| fin du flux | TaskStatusUpdateEvent — Completed |
| erreurs du flux | TaskStatusUpdateEvent — Failed |
Tous les fragments d’une même réponse partagent un identifiant d’artefact, afin qu’un client puisse les réassembler. Le texte assemblé est conservé, de sorte qu’un GetTask ultérieur renvoie ce qui a été diffusé. Cela correspond au contrat que adk-python et adk-go implémentent au-dessus de leur SDKs.