Aller au contenu

Définir un agent et le consommer

Trois objets à poser, dans cet ordre. Aucun n'est facultatif : sauter le deuxième donne un agent dont la dépense n'est imputable à personne.

flowchart TD
    e["1 · Équipe<br/>budget commun, un projet"] --> c["2 · Clé virtuelle<br/>une par agent = imputation"]
    c --> a["3 · Agent<br/>carte + limites + contrat"]
    a --> u["Consommation<br/>script · workflow · chat · autre agent"]

1. L'équipe

Elle porte le budget d'un projet et regroupe ses clés. Un agent seul n'a pas besoin d'équipe ; deux agents du même projet, si — sinon leurs budgets ne s'additionnent nulle part.

curl -sX POST "$PROXY/team/new" -H "Authorization: Bearer $CLE_ADMIN" \
  -H 'Content-Type: application/json' \
  -d '{"team_alias":"<projet>","max_budget":0.5,"budget_duration":"1d"}'

2. La clé virtuelle — une par agent

C'est tout le mécanisme d'imputation. Sans elle, tout le monde dépense sous le même nom et le tableau de bord affiche des nombres qui ne répondent à aucune question.

curl -sX POST "$PROXY/key/generate" -H "Authorization: Bearer $CLE_ADMIN" \
  -H 'Content-Type: application/json' \
  -d '{"key_alias":"<projet>-<agent>","team_id":"<id>",
       "models":["<alias1>","<alias2>"],
       "max_budget":0.5,"budget_duration":"1d",
       "metadata":{"role":"agent-projet","agent":"<agent>"}}'

La clé part dans le coffre, immédiatement, jamais dans un fichier du dépôt : voir Lire un secret du coffre.

Un budget coupe

Atteint, la clé est refusée. Pour un agent de fond dont personne ne regarde la sortie, ça donne un service debout dont le travail est mort. Calibrer au-dessus du pic mesuré, et câbler l'alerte de budget (Câbler une notification).

3. L'agent

curl -sX POST "$PROXY/v1/agents" -H "Authorization: Bearer $CLE_ADMIN" \
  -H 'Content-Type: application/json' -d '{
    "agent_name": "<nom>",
    "agent_card_params": {
      "protocolVersion": "0.3",
      "name": "<Nom affiché>",
      "description": "<ce qu'"'"'il fait, et ce qu'"'"'il ne fera jamais>",
      "url": "http://<hôte>:<port>/a2a/<nom>",
      "version": "1.0.0",
      "defaultInputModes": ["text/plain"],
      "defaultOutputModes": ["text/markdown"],
      "skills": [{
        "id": "<verbe>", "name": "<verbe — ce qu'"'"'il établit>",
        "description": "<ce que cette compétence rend, et ce qu'"'"'elle ne rend pas>",
        "tags": ["<axe>"], "examples": ["<appel typique>"]
      }]
    },
    "object_permission": { "models": ["<alias1>","<alias2>"] },
    "rpm_limit": 6
  }'
Champ Ce qu'il décide
agent_name l'identifiant stable. C'est lui qu'on apparie, jamais l'agent_id : un agent recréé depuis l'UI change d'id et garde son nom
agent_card_params la carte A2A servie aux appelants — nom, description, compétences, formats
agent_card_params.url où le proxy va taper. S'il n'y a rien derrière, la carte ment
object_permission.models la liste blanche des modèles. Deuxième garde-fou, distinct de celui de la clé
rpm_limit, tpm_limit le débit. session_* borne une conversation, pas l'agent entier

Consommer l'agent

Quatre voies, de la plus universelle à la plus spécifique.

A. Comme un modèle ordinaire — la voie qui sert 90 % des cas

curl -sX POST "$PROXY/v1/chat/completions" -H "Authorization: Bearer $CLE" \
  -H 'Content-Type: application/json' \
  -d '{"model":"a2a/<nom>","messages":[{"role":"user","content":"<consigne>"}]}'

Le proxy reconnaît le préfixe a2a/, résout l'agent dans son registre, traduit en message/send et retraduit la réponse. Tout ce qui parle déjà à un modèle parle alors à l'agent : l'interface de chat le voit dans sa liste, le moteur de workflows aussi, et un script de trois lignes n'a rien de spécial à faire.

Avec une réserve qui n'est pas théorique : cette résolution lit le registre en mémoire du proxy. Après un redémarrage, elle échoue pour tout le monde tant que les agents n'ont pas été réappliqués — voir le piège n°4.

B. En A2A, pour un client qui connaît le protocole

curl -s "$PROXY/a2a/<id>/.well-known/agent-card.json" -H "Authorization: Bearer $CLE"

curl -sX POST "$PROXY/a2a/<id>/message/send" -H "Authorization: Bearer $CLE" \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"message/send","params":{"message":{
       "role":"user","parts":[{"kind":"text","text":"<consigne>"}],"messageId":"<uuid>"}}}'

À utiliser quand on veut le cycle de tâche — un travail qui dure, un suivi d'état, des artefacts livrés au fil de l'eau.

C. Depuis un script du parc, sans passer par A2A

C'est le cas de figure d'un agent qui est un programme en ligne de commande sur une machine, pas un serveur. Le script lit sa définition dans le registre, puis lance l'exécutant lui-même :

agent, provenance, contrat = charge(AGENT_DE_SECOURS)   # GET /v1/agents
print(f"définition : {provenance}")                      # litellm | cache | code

Trois choses à respecter, et elles sont toutes coûteuses à redécouvrir :

  1. La lecture se fait avec la clé de l'agent lui-même. Le registre répond à une clé ordinaire : aucune clé d'administration n'a à traîner sur la machine qui exécute.
  2. Un cache local, et la provenance affichée. Un agent qui tourne sur son plan de secours rend la même sortie, dans le même temps, pour le même coût : cette ligne est la seule différence visible entre « à jour » et « proxy muet depuis trois jours ».
  3. Une étiquette d'imputation sur l'appel (x-litellm-tags), sinon la dépense est attribuable à la clé mais pas au travail.

D. Entre agents

Un agent enregistré peut en appeler un autre par les mêmes voies, avec sa clé. C'est là que object_permission.agents sert : borner qui peut appeler qui.

Les pièges, tous mesurés

1. L'interface d'administration écrase litellm_params

Le formulaire réécrit ce champ avec ses seuls champs. Toute donnée qu'un script y range — un contrat d'exécution, des métadonnées maison — disparaît au premier humain qui ouvre l'agent et enregistre. Sans message, sans trace ailleurs que dans updated_by.

Mesuré : 21 champs posés à 07:52, détruits à 08:13 le même jour. La carte, les limites et la liste de modèles, elles, étaient intactes.

À retenir : ce qui doit durer va dans la carte. Ce qu'on range dans litellm_params a besoin d'une source ailleurs et d'un geste qui le repose.

2. La carte jette ce qu'elle ne connaît pas, en rendant 200

capabilities.extensions — l'endroit normatif du protocole pour des métadonnées maison — est filtré à l'enregistrement. Seuls streaming, pushNotifications et stateTransitionHistory survivent. La création réussit, la carte revient amputée, rien ne le signale.

3. La sortie n'est pas une entrée valide

Relire une carte puis la renvoyer telle quelle échoue en 422, sur des champs de sécurité que le proxy a lui-même écrits. Les retirer du corps suffit : il les régénère. Tout aller-retour lire / modifier / réécrire doit les enlever.

4. Le registre vit en mémoire : un redémarrage du proxy le vide

La liste des agents n'est pas relue depuis la base au démarrage : elle ne contient que les agents créés ou modifiés depuis que le processus tourne. Après un redémarrage, ils existent toujours — on les atteint encore par leur identifiant — mais la liste est vide, et surtout a2a/<nom> cesse de résoudre : la résolution lit ce même registre. Tout ce qui consommait l'agent comme un modèle s'arrête, sans alarme, pendant que le proxy reste healthy et que les modèles répondent.

Mesuré deux fois dans la même heure le 2026-09-13, sur six agents.

Trois gestes, dans cet ordre : garder l'identifiant de chaque agent dans sa définition (quand la liste est amnésique, c'est le seul handle qui reste) ; faire nommer la condition par le contrôle plutôt que crier « absent », ce qui ferait tenter une création et rendrait « existe déjà » ; et réappliquer — un simple aller-retour d'écriture le remet dans le registre.

Le réarmement s'automatise sans risque parce que le désaccord est faux : la base et la source disent la même chose, il n'y a rien à arbitrer. Mais seulement pour ce cas-là — dès qu'un écart d'une autre nature s'y mêle, on prévient et on ne touche à rien, sinon on écrase le travail en cours de quelqu'un d'autre.

5. Une modification partielle remplace

Un PATCH ne fusionne pas : les champs absents du corps sont perdus. Toujours renvoyer l'objet complet. Et un contrat amputé se comporte exactement comme un contrat sain, puisque ce qui manque retombe sur les valeurs par défaut du code — d'où l'intérêt d'un consommateur qui annonce ce qu'il a réellement lu.

Contrôles

# La définition telle que le registre la sert, et rien d'autre
curl -s "$PROXY/v1/agents" -H "Authorization: Bearer $CLE" | jq '.[] | {agent_name, rpm_limit}'

# Ce que l'exécutant a réellement chargé, à son dernier run
<commande de l'agent> 2>&1 >/dev/null | head -1

# Qui a modifié l'agent en dernier — un humain dans l'UI s'y voit
curl -s "$PROXY/v1/agents/<id>" -H "Authorization: Bearer $CLE_ADMIN" \
  | jq '{updated_at, updated_by}'

Dernière vérification de cette page : 2026-09-13