Développement IA

Pourquoi les agents IA ont désormais besoin de JSON Schema : du Tool Calling aux workflows structurés

Environ 15 min

Avant 2024, la plupart des « agents » tenaient en un prompt et deux ou trois fonctions. En 2026, ce n'est plus tenable : Function Calling doit décrire les paramètres, MCP enregistrer les outils, Structured Outputs contraindre les retours, A2A négocier les capacités, et le Agents SDK router sur des objets handoff. Ces couches ont évolué chacune de leur côté, mais elles retombent sur le même langage de contrat — JSON Schema. Cet article explique pourquoi il est passé de « syntaxe de validation dans la doc » au runtime de l'agent, et où poser le Schema quand on quitte un Tool Calling unique pour un workflow multi-étapes.

Aperçu

Étape Sans Schema Avec Schema
Tool Calling unique Les paramètres sont des chaînes, les types au hasard, un champ en trop passe encore parameters / inputSchema interceptent avant l'appel
Agent multi-étapes L'aval parse de la prose, le handoff se fie à « on a parlé de billing » L'objet handoff porte output_type, le routage est testable
Réemploi inter-protocoles Function Calling, MCP, A2A : une copie chacun, elles dérivent Un Schema dans Git, exporté en trois adaptateurs

Pour le dire d'emblée : JSON Schema n'est pas « de la doc pour le modèle », c'est le validateur que l'hôte de l'agent exécute avant chaque étape, à chaque handoff et à l'évaluation.

Pourquoi « désormais » besoin, et pas depuis toujours ?

Le Tool Calling des débuts tenait parce que la chaîne était courte : le modèle émettait un nom de fonction et des paramètres, l'hôte exécutait, le résultat revenait dans le dialogue. En cas d'erreur, on réessayait ; l'utilisateur voyait. Le Schema, au mieux dans un README, restait du « nice to have ».

En 2025–2026, la chaîne s'est allongée. Un agent de tickets peut classer, consulter la commande, décider d'un remboursement ou d'un transfert humain, puis passer l'objet à un autre agent. Si un saut intermédiaire n'est que « du JSON en apparence », les étapes suivantes traitent l'erreur comme un fait. Le Schema passe alors de la documentation à un sas runtime : ça ne passe pas, on n'avance pas.

Une autre pression vient de la couche protocole. Dans MCP Tool Schema vs Function Calling, nous l'avons déjà dit : les API vendeurs utilisent parameters, MCP utilise inputSchema. A2A Extensions inscrit encore les capacités dans l'Agent Card. Les trois protocoles ne partagent pas de runtime, mais tous reconnaissent un sous-ensemble JSON Schema. Si vous ne figez pas le Schema dès le départ, vous maintiendrez trois définitions « presque identiques », puis découvrirez à la troisième semaine que order_id est string ici et number là.

Les prévisions DevDay 2026 le disent plus net : Structured Outputs, Function Calling et le runtime agent se referment sur le même tuyau JSON. Le Schema en est le calibre. Sans calibre fixé, quel que soit le protocole branché ensuite, ça fuit.

Tool Calling a déjà l'air « structuré » — où est le trou ?

Dans tools[] de Function Calling, il y a déjà un parameters qui ressemble à un Schema. Beaucoup d'équipes en concluent « nous sommes déjà structurés ». En production, les vrais incidents tombent souvent dans les quatre cas suivants.

Premier : arguments reste une chaîne. Le modèle renvoie :

{
  "tool_calls": [{
    "id": "call_8f2a",
    "type": "function",
    "function": {
      "name": "create_refund",
      "arguments": "{\"order_id\":\"A-1042\",\"amount\":\"99.00\",\"reason\":\"late\"}"
    }
  }]
}

L'hôte doit encore faire un JSON.parse. amount est-il une chaîne ou un nombre, reason est-il dans l'énumération, le note en trop peut-il entrer dans le système de facturation — si parameters n'est pas appliqué côté hôte, tout dépend de « l'humeur du modèle aujourd'hui ». Écrire le Schema ne l'active pas tout seul ; strict: true ne contraint que le côté modèle, l'hôte doit encore valider.

Deuxième : le résultat d'outil n'a pas de Schema. role: tool est le plus souvent une chaîne libre. L'étape suivante « comprend » à nouveau ce texte. Succès de requête, stock à 0, droits insuffisants : si les trois formes diffèrent, le workflow multi-étapes forkera au troisième saut vers la mauvaise branche.

Troisième : des types trop larges perdent tout pouvoir de filtre. Un priority en type: string seulement, et le modèle peut sortir "urgent!!!". Un Schema sans enum, required, additionalProperties: false n'est qu'un commentaire.

Quatrième : un succès unique masque l'échec du workflow. Au premier pas, lookup_order renvoie un status: "ok" de trop ; au second, l'agent de remboursement prend status pour l'état de commande. Un parcours local sans accroc ne le montre pas ; ça explose dans le jeu de régression. C'est là la valeur du Schema : il transforme « l'objet que ce saut peut passer au suivant » en assertion testable, pas en historique de conversation.

Pour déboguer ce JSON imbriqué, formatez et contrôlez la syntaxe en local, puis faites un Diff avec la définition qui tournait la semaine dernière. Ne collez pas une réponse d'outil contenant données utilisateur ou secrets sur un site JSON public.

JSON Schema a en réalité quatre rôles dans un agent

Réduire le Schema à « la notice des paramètres d'outil » fait manquer les trois couches qui grandissent vraiment en 2026. Le tableau complet :

  1. Entrée d'outil. parameters de Function Calling, inputSchema de MCP. Décide ce que le modèle « peut demander ».
  2. Sortie d'outil. Objets renvoyés par les lectures, écritures, API externes. Décide ce que le saut suivant « peut croire ». C'est la couche la plus souvent absente aujourd'hui.
  3. Handoff d'agent. Résultat de classification, intention, confiance, destinataire. Dans le Agents SDK, c'est le critère de routage, pas un résumé de chat.
  4. État de workflow et évaluation. Points de contrôle, traces, fixtures d'eval. Un travail long doit se reprendre depuis du JSON, pas en devinant l'avancement dans l'historique.

Exemple de Schema d'entrée (outil de remboursement) :

{
  "$id": "https://example.com/schemas/tools/create_refund.json",
  "type": "object",
  "properties": {
    "order_id": { "type": "string", "pattern": "^A-[0-9]{4,}$" },
    "amount": { "type": "number", "exclusiveMinimum": 0 },
    "reason": { "type": "string", "enum": ["late", "damaged", "duplicate", "other"] }
  },
  "required": ["order_id", "amount", "reason"],
  "additionalProperties": false
}

Exemple de Schema de handoff (triage → facturation) :

{
  "$id": "https://example.com/schemas/handoffs/triage_to_billing.json",
  "type": "object",
  "properties": {
    "intent": { "type": "string", "enum": ["refund", "exchange", "human"] },
    "order_id": { "type": "string" },
    "confidence": { "type": "number", "minimum": 0, "maximum": 1 },
    "from": { "const": "triage" },
    "to": { "const": "billing_agent" }
  },
  "required": ["intent", "order_id", "confidence", "from", "to"],
  "additionalProperties": false
}

Les points communs des deux fichiers importent plus que les différences : objets, énumérations, champs en trop interdits, versionnables dans Git, exécutables en CI par ajv (ou un équivalent). Changement d'instantané modèle, passage MCP ou Function Calling : le validateur ne se réécrit pas. Pour les mots-clés, voir le tutoriel JSON Schema.

De l'appel unique au workflow structuré : où le Schema pose des portes

Un Tool Calling unique n'a qu'une porte : les paramètres sont-ils valides. Un workflow d'agent structuré en a au moins quatre.

Porte 1 : avant de choisir l'outil. Au-delà de dix outils, fourrer tout le Schema dans une requête coûte cher et multiplie les mauvais choix. Quand on recherche puis appelle, le résultat de recherche doit lui-même être une short-list schématisée, pas une présentation d'outils générée par le modèle.

Porte 2 : avant d'exécuter. parse → validate → execute. Si validate échoue, renvoyer une erreur structurée (champ manquant, type faux, hors enum), ne pas jeter la chaîne brute au saut suivant. Cette étape bloque aussi les clés extra de type injection de prompt.

Porte 3 : avant le handoff. Le triage doit remettre un objet conforme au Schema de handoff, sinon billing ne démarre pas. La condition de routage s'écrit intent === "refund" && confidence >= 0.8, pas « le mot remboursement est apparu dans la réponse ».

Porte 4 : reprise et évaluation. Les longs travaux écrivent les points de contrôle en JSON valide :

{
  "run_id": "run_20260903_014",
  "step": "await_refund_ack",
  "state": {
    "order_id": "A-1042",
    "intent": "refund",
    "amount": 99.0
  },
  "schema": "https://example.com/schemas/state/refund_flow.json"
}

Le jeu d'évaluation est le même type d'objet : entrée donnée, on affirme que la sortie passe le Schema et que les champs clés sont égaux. Quand l'agent « a empiré », Diff d'abord Schema et fixtures de cette semaine et de la précédente, avant de soupçonner le modèle. Beaucoup d'incidents viennent d'un required en trop, ou d'une valeur enum en moins.

Un Schema, comment le servir à Function Calling, MCP et A2A

Que la couche protocole n'écrive pas chacun de son côté. La direction recommandée : un fichier de spécification, trois projections :

Protocole Où tombe le Schema Comment exporter depuis le même fichier
Function Calling function.parameters Copier type/properties/required, activer strict
MCP inputSchema Le même objet, partie de tools/list
Structured Outputs text.format.json_schema Handoff / réponse finale : la même définition d'objet
A2A Agent Card / Extension metadata La déclaration de capacité externe référence le même $id

À la projection, attention aux écarts de sous-ensemble : le mode strict des vendeurs exige souvent que toute property soit dans required, et désactive une partie de oneOf / $ref. Le fichier de spec peut rester plus complet ; l'exporteur taille selon la cible, et la CI fait un Diff de structure « spec vs projection » pour éviter une retouche manuelle d'un seul côté.

MCP gère l'offre d'outils, Function Calling la façon dont le modèle exprime l'intention d'appel, A2A la découverte et la négociation entre agents. Le Schema ne remplace pas ces trois couches : c'est leur dictionnaire de champs commun. Même conclusion que les deux articles précédents, sauf que le « dictionnaire » devient un fichier dans le dépôt.

Mise en œuvre : traiter JSON Schema comme source unique de vérité

Des gestes immédiats, qui ne dépendent d'aucune keynote :

  • Dans le dépôt : schemas/tools/, schemas/handoffs/, schemas/state/. Chaque fichier a un $id stable et une version.
  • Exporter par script les trois adaptations Function Calling / MCP / Structured Outputs ; interdiction de retoucher les champs à la main dans les fichiers d'adaptation.
  • La CI valide avec ajv (ou équivalent) : payloads d'exemple, fixtures historiques, échantillons de prod (après anonymisation) doivent passer le Schema courant.
  • Nouveaux outils : additionalProperties: false et enum explicite par défaut. Pour une extension ouverte, un objet metadata à part — ne pas élargir la racine.
  • Au-delà de dix outils, ajouter routage ou recherche ; ne pas verser tout le catalogue dans une requête.
  • L'hôte accepte arguments en chaîne ou en objet, pour qu'un resserrement de type côté vendeur ne fasse pas alerter tout le site.
  • Les payloads à secrets ou données utilisateur se regardent uniquement en local dans le navigateur : formater, Diff, zéro envoi.

Le catalogue peut rester mince. L'essentiel : d'abord le fichier, ensuite le prompt. « Merci de renvoyer du JSON » dans un prompt ne peut pas échouer en CI ; un Schema, si.

Pièges fréquents

  • Croire que coller le Schema dans le prompt système suffit. Contrainte côté modèle et validation côté hôte sont deux choses. Sans validation hôte, dès que strict est coupé ou que l'instantané change, le contrat disparaît.
  • Valider seulement l'entrée, pas la sortie ni le handoff. Les incidents d'agents multi-étapes naissent surtout de « l'étape d'avant a renvoyé une forme inattendue ».
  • Écrire à la main un Schema « à peu près pareil » par protocole. À la troisième semaine, ça diverge. Exportez, ne copiez pas.
  • Fuir le design derrière un type: object trop large. Un objet ouvert, c'est aucune porte. Pour vraiment étendre : un numéro de version ou un metadata explicite.
  • Traiter le Schema comme corpus d'entraînement du modèle. Il est d'abord pour le validateur et les tests. Une description claire aide à choisir l'outil ; elle ne remplace pas enum et required.
  • Changer les champs dans un orchestrateur visuel sans réécrire Git. Les nœuds de la toile ne sont pas la source de vérité. Exportez d'abord le Schema, puis faites lire le fichier à la toile.

FAQ

Peut-on mettre Tool Calling en production sans Schema ?

Un prototype, oui. Outils fixes, moins de trois étapes, échecs visibles par un humain : les paramètres en chaîne suffisent souvent. Dès qu'apparaissent des branches, des retries automatiques ou un second agent, les erreurs sans Schema se propagent en silence.

JSON Schema sera-t-il remplacé par Structured Outputs ou MCP ?

Non. Ce sont des couches qui consomment le Schema, pas un autre système de types. La tendance 2026 : davantage de protocoles référencent le même Schema, plutôt que d'inventer un nouveau langage de champs.

Faut-il draft-07 ou 2020-12 ?

Suivez votre bibliothèque de validation et le sous-ensemble vendeur. Ce dont un agent se sert vraiment : type, properties, required, enum, additionalProperties. Un sous-ensemble aligné entre CI et API modèle compte plus que le dernier draft.

API interne privée : faut-il encore MCP et A2A ?

Pas forcément. Une boucle interne tient avec Function Calling + Schema. L'intérêt de réserver MCP / A2A : une fois le même Schema dans le dépôt, brancher un protocole plus tard n'est qu'un exporteur de plus, sans réinventer les champs.

Comment déboguer un JSON d'outil / de handoff qui échoue ?

Formatez en local dans le navigateur, voyez quel required manque face au Schema, puis Diff avec la version précédente. JSONSort n'envoie rien. Ne collez pas une réponse à secrets sur un site public.

Conclusion

Les agents IA ont « désormais » besoin de JSON Schema, non parce que le modèle cesserait soudain de lire le naturel, mais parce que les workflows s'allongent, les protocoles se multiplient, et les erreurs doivent être arrêtées côté hôte. Tool Calling donne l'intention d'appel ; Structured Outputs, l'objet final ; MCP et A2A, l'offre et la négociation. Ce qui colle ces couches : le jeu de fichiers Schema du dépôt, validables, diffables, exportables.

Dès aujourd'hui, plutôt qu'un prompt plus long, écrivez d'abord schemas/tools/create_refund.json. Les modèles changeront, les protocoles aussi ; le calibre, lui, doit d'abord tenir.

Pour aller plus loin

Journal : première version