Appeler l'agent par API
L'agent expose une API A2A (Agent-to-Agent)
au format JSON-RPC 2.0 sur un seul point
d'entrée POST /. Ce guide explique, en langage
clair, comment l'appeler : les arguments attendus, la
sécurité, et les types d'appel supportés.
→ Pour une référence interactive (essayer les appels en direct), ouvrez la page Swagger / OpenAPI.
Vue d'ensemble
Tout passe par une seule URL et
une seule méthode HTTP : on envoie un objet
JSON-RPC en POST, et le champ method
de cet objet décide de l'action (envoyer un message, récupérer
une tâche, l'annuler…).
Content-Type : application/json •
Corps : enveloppe JSON-RPC 2.0 • Réponse : JSON
(ou flux SSE pour le streaming).
jsonrpc: "2.0", un id (numéro ou
chaîne), un method et un objet
params.
Deux déploiements : Portail vs Agent
Le même cœur logique est exposé de deux façons. Choisissez selon que l'appelant est un humain (navigateur) ou une machine (autre agent / script).
| Portail (IHM) | Agent autonome | |
|---|---|---|
| Pour qui | Un humain, via le navigateur | Une autre machine / un autre agent |
| Port par défaut | 8000 (PORT) |
8001 (AGENT_PORT) |
| Authentification | Connexion interactive Microsoft 365 (MSAL) |
Jeton utilisateur Microsoft 365
x-m365-id-token
|
| Sert l'interface web | Oui (HTML/CSS/JS sous public/) |
Non — uniquement l'API |
| Point d'entrée API |
Identique : POST / + JSON-RPC 2.0
|
|
Découverte : l'Agent Card
Avant d'appeler l'agent, un client peut lire sa carte d'identité (l'Agent Card) pour connaître son nom, ses capacités et la liste de ses skills.
// Réponse (extrait)
{
"name": "A2A Agent",
"description": "An A2A agent backed by Azure AI Foundry…",
"version": "1.0.0",
"supportedInterfaces": [
{ "url": "http://localhost:8001",
"protocolBinding": "JSONRPC",
"protocolVersion": "1.0" }
],
"capabilities": { "streaming": true, "pushNotifications": false },
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"],
"skills": [ /* … cartes de chaque skill … */ ]
}
Authentification & sécurité
1. Liaison réseau (binding)
Par défaut, l'agent n'écoute que sur
127.0.0.1 (boucle locale) : il n'est
pas accessible depuis le réseau. Pour
l'exposer, on définit HOST=0.0.0.0 — l'agent
standalone exige toujours un x-m365-id-token
valide, vérifié côté serveur.
M365_REQUIRE_AUTH et ne lancez
pas l'agent standalone sans M365_CLIENT_ID configuré :
le serveur refuse ce mode car il supprimerait la vérification
utilisateur.
2. Jeton utilisateur Microsoft 365
Chaque appel à l'agent standalone doit présenter un
id_token Microsoft Entra ID émis pour cette
application. Le serveur vérifie la signature, l'expiration,
l'audience et le tenant avant tout traitement.
x-m365-id-token: <id_token obtenu après authentification utilisateur>
En cas de jeton manquant ou invalide, l'agent répond
HTTP 401 avec l'en-tête
WWW-Authenticate: Bearer realm="m365" et une erreur JSON-RPC de
code -32010.
3. Autres protections en place
| Protection | Détail |
|---|---|
| En-têtes de sécurité |
Ajoutés à chaque réponse ; X-Powered-By
désactivé.
|
| Taille du corps |
Limitée à 8 Mo par défaut
(JSON_BODY_LIMIT) — assez pour une image
réduite jointe, mais borné pour éviter les abus.
|
| Proxy de confiance |
trust proxy réglé sur loopback
par défaut (TRUST_PROXY).
|
| Jetons jamais persistés | Les jetons utilisateur transmis ne vivent que le temps de la requête (jamais écrits sur disque). |
Jetons délégués (skills authentifiés)
Certains skills agissent au nom de l'utilisateur (par ex. lire son agenda Microsoft 365 ou publier sur un blog). L'appelant peut transmettre les jetons correspondants via des en-têtes dédiés. Ils sont propagés aux skills le temps de la requête, puis oubliés.
| En-tête | Usage |
|---|---|
x-m365-access-token |
Jeton Microsoft Graph (agenda, courrier…). Si absent, un repli par variable d'environnement peut s'appliquer. |
x-blog-access-token |
Jeton pour les skills du blog familial. |
Types d'appel supportés
Quatre méthodes JSON-RPC sont disponibles. Le champ
method de l'enveloppe choisit l'action.
| method | Réponse | Rôle |
|---|---|---|
SendMessage |
JSON | Envoie un message et reçoit la réponse complète d'un coup (cas le plus courant). |
SendStreamingMessage |
SSE | Idem mais en flux Server-Sent Events : événements task → artifact → status. |
GetTask |
JSON | Récupère une tâche déjà produite à partir de son identifiant. |
CancelTask |
JSON | Demande l'annulation d'une tâche. |
Structure d'un message
Pour SendMessage et
SendStreamingMessage, le paramètre obligatoire est
params.message. Un message est composé de
parts : du texte, et/ou un fichier (image).
| Champ | Obligatoire | Description |
|---|---|---|
message.role |
Oui | Toujours "ROLE_USER" côté appelant. |
message.parts |
Oui | Tableau (non vide) de parts — voir ci-dessous. |
message.contextId |
Non | Identifiant de conversation. Réutilisez-le pour garder l'historique (mémoire) d'un échange à l'autre. Généré automatiquement si absent. |
message.messageId |
Non | Identifiant du message (UUID généré si absent). |
Types de « part »
{ "text": "Quel temps fait-il à Sceaux ?" }
{
"file": {
"name": "photo.png",
"mimeType": "image/png",
"bytes": "<base64 sans préfixe data:>"
}
}
Exemples concrets
a) Appel simple en curl
curl -X POST http://localhost:8001/ \
-H "Content-Type: application/json" \
-H "x-m365-id-token: $M365_ID_TOKEN" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "SendMessage",
"params": {
"message": {
"role": "ROLE_USER",
"parts": [ { "text": "Bonjour, que sais-tu faire ?" } ]
}
}
}'
b) Appel en JavaScript (fetch)
const res = await fetch("http://localhost:8001/", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-m365-id-token": idToken,
},
body: JSON.stringify({
jsonrpc: "2.0",
id: "req-1",
method: "SendMessage",
params: {
message: {
role: "ROLE_USER",
parts: [{ text: "Météo à Sceaux demain ?" }],
contextId: "conv-42", // réutiliser pour la mémoire
},
},
}),
});
const data = await res.json();
console.log(data.result.status.message.parts[0].text);
c) Conserver le contexte (mémoire de conversation)
Renvoyez le même contextId à chaque tour : l'agent
retrouve l'historique et les compteurs de consommation
associés à cet échange.
Joindre ou modifier une image
Ajoutez une part fichier à côté de la part texte. C'est le modèle qui décide s'il s'agit d'analyser l'image ou de la modifier via le générateur d'image — guidé par votre instruction texte.
{
"jsonrpc": "2.0", "id": 7, "method": "SendMessage",
"params": {
"message": {
"role": "ROLE_USER",
"parts": [
{ "text": "Mets cette photo en noir et blanc" },
{ "file": { "mimeType": "image/png", "bytes": "iVBORw0KGgo…" } }
]
}
}
}
data:image/png;base64,.
Streaming (Server-Sent Events)
Avec SendStreamingMessage, la réponse n'est pas un
JSON unique mais un flux d'événements
text/event-stream. Chaque ligne
data: contient une enveloppe JSON-RPC : d'abord la
tâche (task), puis l'artefact (artifact,
marqué lastChunk), puis le statut final
(status = COMPLETED).
curl -N -X POST http://localhost:8001/ \
-H "Content-Type: application/json" \
-H "x-m365-id-token: $M365_ID_TOKEN" \
-d '{ "jsonrpc":"2.0", "id":2, "method":"SendStreamingMessage",
"params":{ "message":{ "role":"ROLE_USER",
"parts":[{"text":"Raconte une courte histoire"}] } } }'
// Flux reçu :
data: {"jsonrpc":"2.0","id":2,"result":{"task":{…,"status":{"state":"TASK_STATE_WORKING"}}}}
data: {"jsonrpc":"2.0","id":2,"result":{"artifactUpdate":{…,"lastChunk":true}}}
data: {"jsonrpc":"2.0","id":2,"result":{"statusUpdate":{…,"state":"TASK_STATE_COMPLETED"}}}
Structure de la réponse
Pour SendMessage, le champ result est
une tâche (Task). La réponse en
langage naturel se trouve dans
result.status.message.parts[0].text (et aussi dans
result.artifacts).
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"id": "<taskId>",
"contextId": "conv-42",
"status": {
"state": "TASK_STATE_COMPLETED",
"message": { "role": "ROLE_AGENT",
"parts": [{ "text": "Voici la réponse…" }] },
"timestamp": "2026-06-15T…Z"
},
"artifacts": [ { "parts": [{ "text": "Voici la réponse…" }] } ],
"metadata": {
"usage": { "prompt_tokens": 120, "completion_tokens": 60, "total_tokens": 180 },
"cost": { "prompt": 0.0001, "completion": 0.0002,
"image": 0, "total": 0.0003, "currency": "EUR" },
"imageUsage": { "input_tokens": 0, "output_tokens": 0, "mode": "generate" },
"tools": [ { "name": "get_weather" } ]
}
}
}
Champ de metadata |
Contenu |
|---|---|
usage |
Tokens du modèle texte (entrée / sortie / total). |
cost |
Coût détaillé du tour : texte (prompt +
completion), image,
total et currency.
|
imageUsage |
Présent si une image a été produite : tokens image et
mode = "generate" (création) ou
"edit" (modification d'une image jointe).
|
tools |
Liste des skills appelés pendant le tour. |
get_usage_totals restitue le cumul (tokens +
coût) de la conversation.
Codes d'erreur
En cas de problème, le champ result est remplacé
par error: { code, message }.
| Code | Signification |
|---|---|
-32010 |
Non authentifié — x-m365-id-token manquant
ou invalide
(HTTP 401).
|
-32600 |
Requête invalide — jsonrpc ≠ "2.0" ou
id absent.
|
-32601 |
Méthode inconnue (method non supporté). |
-32602 |
Paramètres invalides — par ex.
message.parts manquant.
|
-32603 |
Erreur interne du serveur. |
-32001 |
Tâche introuvable (GetTask). |
-32002 |
Tâche non annulable (CancelTask). |
authRequired dans metadata.
Variables d'environnement utiles
| Variable | Rôle |
|---|---|
AGENT_PORT |
Port de l'agent autonome (défaut 8001). |
PORT |
Port du portail (défaut 8000). |
HOST |
Interface d'écoute (défaut 127.0.0.1) ;
0.0.0.0 pour exposer sur le réseau.
|
M365_CLIENT_ID |
Identifiant public de l'application Entra ID dont les
id_token sont acceptés par le serveur.
|
JSON_BODY_LIMIT |
Taille max du corps JSON (défaut 8mb). |
TRUST_PROXY |
Réglage Express trust proxy (défaut
loopback).
|