Protocole Agent-Ă -Agent (A2A)
ADK-Rust implĂ©mente le A2A Protocol v1.0.0 pour la communication entre agents sur plusieurs rĂ©seaux. LâimplĂ©mentation se trouve dans adk-server derriĂšre le drapeau de fonctionnalitĂ© a2a-v1 et couvre les 11 opĂ©rations JSON-RPC, les liaisons REST, la dĂ©couverte de carte dâagent et la nĂ©gociation de version. Voir Couverture des opĂ©rations pour lâopĂ©ration dont la sĂ©mantique est plus restreinte que ce que la spĂ©cification autorise. Les types de transport sont fournis par a2a-protocol-types â le A2A SDK Rust vĂ©rifiĂ© par la Fondation par @tomtom215 (a2a-rust).
Vue dâensemble
A2A est utile lorsque :
- IntĂ©gration avec des services dâagents tiers
- Construction dâarchitectures de microservices avec des agents spĂ©cialisĂ©s
- Activation de la communication entre agents dans plusieurs langages (tout langage avec un client A2A)
- Application de contrats formels entre systĂšmes dâagents
Pour une organisation interne simple, utilisez des sous-agents locaux plutĂŽt que A2A pour de meilleures performances.
Conformité v1.0.0
LâimplĂ©mentation est entiĂšrement conforme Ă la spĂ©cification A2A Protocol v1.0.0 :
| Fonctionnalité | Section de spécification | Statut |
|---|---|---|
| Carte d'agent avec dĂ©claration des capacitĂ©s | §8 | â |
| Horodatages RFC 3339 sur tous les changements de statut de tĂąche | §5.6.1 | â |
Idempotence de lâID du message pour SendMessage | §3.3.1 | â |
| Authentification des notifications push (Bearer + token) | §13.2 | â |
| INPUT_REQUIRED flux de reprise multi-tours | §3.4.3 | â |
| Validation des entrĂ©es (parties, ID, taille des mĂ©tadonnĂ©es) | §3.3 | â |
Content-Type: application/a2a+json sur les rĂ©ponses | §9 | â |
| Objet Task comme premier Ă©vĂ©nement de streaming SSE | §3.1.2 | â |
| Recherche de tĂąche Ă portĂ©e de contexte pour les Ă©changes 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 | â |
Fiches dâagent
Chaque agent A2A expose une fiche dâagent Ă /.well-known/agent-card.json dĂ©crivant ses capacitĂ©s, ses compĂ©tences et les 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 fiche dâagent inclut :
- Nom, description et version de lâagent
- Interfaces prises en charge avec lâassociation de protocole et la version
- Capacités :
streaming,pushNotifications,extendedAgentCard - CompĂ©tences dĂ©rivĂ©es de la configuration de lâagent
- Modes dâentrĂ©e/sortie par dĂ©faut
Les capacitĂ©s sont dĂ©sormais dĂ©clarĂ©es explicitement via le paramĂštre AgentCapabilities â plus de valeurs par dĂ©faut codĂ©es en dur.
Exposer un agent via A2A v1
CrĂ©ez 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-2.5-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â Fiche dâagent avec mise en cache ETagPOST /jsonrpcâ point de terminaison JSON-RPC (les 11 opĂ©rations v1 ; voir Operation coverage)- Routes REST pour toutes les opĂ©rations
- NĂ©gociation dâen-tĂȘte
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 un 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 des tĂąches via SSE |
CreateTaskPushNotificationConfig | Enregistrer un webhook pour les notifications push |
GetTaskPushNotificationConfig | Récupérer une configuration de notification push |
ListTaskPushNotificationConfigs | Lister les configurations push pour une tĂąche |
DeleteTaskPushNotificationConfig | Supprimer une configuration de notification push |
GetExtendedAgentCard | Récupérer la fiche agent étendue |
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 status, history et artifacts. 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 (selon la spec §3.1.2) - Les événements suivants sont
TaskStatusUpdateEvent(Working, Completed, etc.) - Les Ă©vĂ©nements dâartifact sont
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 retrouve 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 créée prĂ©cĂ©demment sans retraitement. 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 lors des livraisons de 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 la URLs du webhook par rapport aux plages dâIP privĂ©es et Ă localhost.
Validation des entrées
Toutes les requĂȘtes entrantes sont validĂ©es avant traitement :
| Validation | Erreur |
|---|---|
| Message avec zéro partie | InvalidParams (-32602) |
| messageId vide ou contenant uniquement des espaces | InvalidParams (-32602) |
| messageId dépassant 256 caractÚres | InvalidParams (-32602) |
| taskId vide ou composé uniquement d'espaces | InvalidParams (-32602) |
| taskId dépassant 256 caractÚres | InvalidParams (-32602) |
| Métadonnées dépassant 64 KB | 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 se mappent Ă 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 exemples 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 carte de lâagent, SendMessage (les deux agents avec de vraies LLM), GetTask, ListTasks, le chemin dâerreur CancelTask, SendStreamingMessage, les notifications push CRUD, GetExtendedAgentCard, la nĂ©gociation de version et les chemins dâerreur.
Bonnes pratiques
- DĂ©clarer les capacitĂ©s avec prĂ©cision â dĂ©finissez
streaming,pushNotificationsen 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 â utilisez
contextIdpour conserver lâĂ©tat de la conversation entre les messages - Valider le webhook URLs â la protection SSRF est intĂ©grĂ©e, mais utilisez HTTPS en production
- DĂ©finir des dĂ©lais dâexpiration appropriĂ©s â configurez des dĂ©lais dâexpiration des requĂȘtes pour les appels Ă des agents distants
- Utiliser lâidempotence â les clients peuvent rĂ©essayer sans risque
SendMessageavec le mĂȘmemessageId
Liés
- LlmAgent â CrĂ©ation dâagents
- SystĂšmes multi-agents â Sous-agents et hiĂ©rarchies
- DĂ©ploiement du serveur â ExĂ©cution dâ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 | Ătat |
|---|---|
SendMessage | Pilote lâagent, enregistre sa sortie comme un artefact |
SendStreamingMessage | Pilote lâagent, diffuse les fragments dâ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, donc un client ne doit pas lâattendre pour suivre la progression.
Une rĂ©-attache en direct nĂ©cessite une file dâĂ©vĂ©nements par tĂąche qui survit Ă la requĂȘte dâorigine. Les
implĂ©mentations de rĂ©fĂ©rence obtiennent cela Ă partir de leur A2A SDKs â adk-python et adk-go dĂ©lĂšguent toutes deux
tasks/resubscribe entiĂšrement au gestionnaire de files de SDK, et aucune ne lâimplĂ©mente dans le code ADK.
Ce serveur est rĂ©alisĂ© Ă la main sur a2a-protocol-types, qui fournit des types de protocole plutĂŽt quâun
runtime de serveur, donc la file nâexiste pas encore.
Utilisez SendStreamingMessage lorsque des mises Ă jour en direct sont requises.
Contrat des événements en flux
SendStreamingMessage traduit les Ă©vĂ©nements dâagent au fur et Ă mesure de leur arrivĂ©e:
| ĂvĂ©nement de l'agent | A2A Ă©vĂ©nement |
|---|---|
| d'abord, avant la sortie | Task, puis TaskStatusUpdateEvent â Working |
contenu, partial = true | TaskArtifactUpdateEvent â append, pas le dernier bloc |
contenu, partial = false | TaskArtifactUpdateEvent â dernier segment |
| fin du flux | TaskStatusUpdateEvent â Completed |
| erreurs du flux | TaskStatusUpdateEvent â Failed |
Tous les fragments dâune rĂ©ponse partagent un identifiant dâartefact afin quâun client puisse les rĂ©assembler. Le texte assemblĂ©
est conservĂ©, donc une requĂȘte ultĂ©rieure GetTask renvoie ce qui a Ă©tĂ© diffusĂ©. Cela correspond au contrat
adk-python et adk-go implémentent sur leur SDKs.