En 2026, les équipes qui maintiennent des agents OpenAI ne se font pas piéger par « le modèle ne sait pas parler », mais par du code qui traite encore json_object comme une sortie structurée, ou qui reste en Function Calling non strict sur Chat Completions. Pour un projet neuf, le point de départ recommandé est gpt-5.6. Function Calling et Structured Outputs reposent sur le même constrained decoding, mais l’entrée, le comportement par défaut de strict et le sous-ensemble JSON Schema ne sont pas les mêmes.
Vérifié le 18 août 2026. Champs et comportements suivent le guide Function Calling OpenAI et le guide Structured Outputs. Pas de latences, tarifs ou taux de succès inventés. Là où la doc publique ne fige rien, on indique « à confirmer en rejouant le trafic réel ».
Si vous basculez un backend compatible OpenAI vers un autre modèle, la sortie structurée et la boucle d’outils se valident séparément : changer le Base URL ne suffit pas. Voir la checklist de migration OpenAI API vers Kimi K3.
En bref : trois changements en même temps
Beaucoup de dépôts en sont encore au modèle mental 2024 : « renvoie du JSON » dans le prompt, puis une regex sur les blocs de code. En production 2026, ça casse le parseur via violations de schéma, champs manquants et hallucinations d’enum.
Ce qu’il faut changer, ce sont trois couches — pas un nom de modèle plus gros :
- Contrat de livraison
- L’objet final pour l’utilisateur ou le service aval : Structured Outputs (
text.formatcôté Responses,response_format.json_schemacôté Chat Completions). - Contrat d’exécution
- Quand le modèle appelle vos outils, les arguments doivent coller au JSON Schema de l’outil. C’est Function Calling, même constrained decoding que Structured Outputs.
- Contrat de compatibilité
- L’ancien JSON Mode (
json_object) garantit seulement « ça ressemble à du JSON », pas les champs, types ni enums contre le schéma. La doc le positionne comme prédécesseur de Structured Outputs. Ne plus en faire le chemin principal d’un projet neuf.
Autre changement produit souvent oublié : le nouveau code doit passer par l’API Responses. Chat Completions reste utilisable, mais la stratégie par défaut de strict diffère — Responses tente de normaliser le schéma en mode strict et ne recule qu’en cas d’échec ; Chat Completions reste non strict / best effort par défaut.
Modèle et voie API principale : gpt-5.6 + Responses
Structured Outputs existe depuis la génération GPT-4o. Pour les nouveaux projets, la reco officielle est gpt-5.6. Les snapshots plus anciens (gpt-4-turbo et avant) restent documentés pour JSON Mode, pas pour un json_schema strict complet.
Deux portes d’entrée à ne pas confondre
| Résultat visé | Entrée à utiliser | Point d’attention 2026 |
|---|---|---|
| Objet fixe pour l’utilisateur / l’aval | Responses : text.format ; ou Chat Completions : response_format: json_schema |
Activer strict: true ; SDK : Pydantic / Zod + parse() |
| Le modèle appelle vos fonctions, lit une base, mute un état | Outil function dans tools |
Le schéma des paramètres est aussi strict ; appels parallèles et boucles multi-outils : à vous d’écrire l’exécuteur |
| Surface d’outils trop large pour tout mettre en contexte | Chargement tardif tool_search |
Uniquement gpt-5.4 et plus récent ; les définitions d’outils comptent en tokens d’entrée |
| Les arguments ne sont pas du JSON, mais du texte libre ou une grammaire | Custom tools + CFG optionnelle | Adapté aux DSL et langages de requête ; ne pas forcer un JSON Schema de function |
Côté SDK, prenez l’habitude de ne pas écrire à la main un schéma qui oublie additionalProperties : générez-le depuis les types avec les helpers officiels. Python : client.responses.parse(..., text_format=YourModel) ; JavaScript : zodTextFormat. Avec un schéma manuscrit et strict: true, une contrainte non respectée fait refuser la requête — ce n’est pas « le modèle sort n’importe quoi et vous retryez ».
Face à la piste Gemini, « compatible SDK OpenAI » ne veut pas dire le même comportement de schéma. Pour les upgrades Google : 10 nouvelles fonctionnalités de Gemini 3.5 Pro. En copiant le même JSON Schema d’un fournisseur à l’autre, la règle additionalProperties sur les objets imbriqués est souvent le premier point de rupture.
JSON Mode, Structured Outputs, Function Calling
Accident de prod le plus fréquent : les logs montrent du JSON, donc on croit Structured Outputs actif. Sémantique officielle :
| Capacité | JSON valide | Conforme au schéma | Activation typique | Modèles |
|---|---|---|---|---|
| JSON Mode | Oui | Non | text.format.type = json_object |
Certains paliers compatibles GPT-5 ; courant sur les anciens snapshots |
| Structured Outputs | Oui | Oui (sous-ensemble de schéma supporté) | json_schema + strict: true |
gpt-4o-2024-08-06 / gpt-4o-mini et suivants ; nouveaux projets : gpt-5.6 |
| Function Calling + strict | Les arguments d’outil sont du JSON valide | Les arguments collent au schéma parameters | strict: true sur l’outil |
Modèles avec tools ; strict recommandé en permanence |
Quand ne pas utiliser Function Calling
Si le modèle n’a pas à toucher votre système (pas de stock, pas de tickets, pas de scripts) et doit seulement découper la réponse en cartes, étapes ou scores : Structured Outputs. Dès que la sortie est « exécute cet effet de bord », passez par tools — ne déguisez pas des arguments de fonction en schéma de réponse finale.
Un refus n’est plus du « mauvais JSON »
En refus de sécurité, le modèle n’est pas forcé dans votre schéma. Responses / Chat Completions exposent un champ refusal distinct. La couche de parse doit le traiter en citoyen de première classe : d’abord refusal, ensuite output_parsed — un objet vide n’est pas un succès.
Règles dures du JSON Schema strict
Une fois strict activé, OpenAI n’accepte qu’un sous-ensemble de JSON Schema, pas n’importe quel document Draft 2020-12. Trois erreurs de requête les plus fréquentes :
- Chaque champ de
propertiesdoit figurer dansrequired. - Chaque
object(y compris imbriqué) doit avoiradditionalProperties: false. - L’objet racine ne peut pas être un
anyOf; l’optionnel s’exprime par « required + null autorisé », par ex.["string", "null"].
Donc : retirer le champ de required pour faire semblant qu’il est optionnel — sous strict, c’est un 400 immédiat. La bonne forme : le champ reste required, le type est une union nullable, l’appli traite null comme « non fourni ».
Objet de prod courant « extraire un ticket » — l’objet imbriqué a aussi additionalProperties :
from openai import OpenAI
from pydantic import BaseModel
client = OpenAI()
class Ticket(BaseModel):
title: str
priority: str
assignee: str | None
tags: list[str]
response = client.responses.parse(
model="gpt-5.6",
input=[
{"role": "system", "content": "Extraire les champs du ticket à partir de la description utilisateur."},
{"role": "user", "content": "Page de connexion 500, assigner à Noah, priorité haute, tags auth et api."},
],
text_format=Ticket,
)
ticket = response.output_parsed
print(ticket.title, ticket.priority, ticket.assignee)
Si la requête est refusée, lisez d’abord quelle contrainte manque dans le message d’erreur — ne descendez pas le modèle en premier. Les schémas du Playground arrivent déjà en strict ; les copier tels quels dans le dépôt est souvent plus rapide que de recycler d’anciens prompts json_object.
En multi-fournisseurs, revérifiez : le même schéma « chaque object à false » peut devenir HTTP 400 sur certaines passerelles compatibles ou d’autres modèles. Faites une transformation par fournisseur, pas trois schémas métier.
Function Calling 2026 : strict, tool_search, custom tools
Function Calling et tool calling sont officiellement la même chose : un JSON Schema décrit les fonctions appelables, l’exécuteur applicatif joue les effets de bord. Quelques points 2026 changent directement votre boucle d’agent.
Ne pas deviner la valeur par défaut de strict
- Mettez toujours
strict: trueexplicitement. - Responses : si vous omettez strict, le serveur tente de normaliser le schéma ; en échec, repli non strict, l’outil dans la réponse affiche
strict: false. - Chat Completions : omission = non strict par défaut.
- Sur un modèle fine-tuné qui appelle plusieurs fonctions dans un tour, la doc indique que strict peut être désactivé pour ce tour.
Les définitions d’outils entrent dans le contexte et sont facturées en tokens d’entrée. Descriptions trop longues et 40 outils d’un coup gonflent la facture et dégradent le choix d’outil. Beaucoup d’outils : chargez les rares en différé avec tool_search — gpt-5.4 et plus seulement. La boucle peut d’abord voir tool_search_call / tool_search_output, puis le vrai function_call.
Custom tools : n’enfoncez pas un DSL dans un objet JSON
Les function tools collent aux paramètres structurés ; les custom tools au texte libre, éventuellement contraint par une grammaire hors contexte (CFG). Fragments SQL, langages de requête internes, formats dont les terminaux s’excluent : une CFG est plus stable qu’un champ string + un roman de prompt. Si la CFG signale unexpected tokens, cherchez d’abord des terminaux qui se chevauchent, pas la faute du modèle.
tools = [{
"type": "function",
"name": "get_order",
"description": "Consulter le statut d'une commande par numéro. N'appeler que si l'utilisateur donne un numéro de commande explicite.",
"strict": True,
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"locale": {"type": ["string", "null"]},
},
"required": ["order_id", "locale"],
"additionalProperties": False,
},
}]
La boucle d’exécution n’a pas changé : finish_reason / type d’item = appel d’outil → fonction locale → renvoyer le résultat en rôle tool → nouvelle requête. Ce qui change : plus besoin de miser sur json.loads. Il faut toujours persister le message assistant complet (y compris tool_calls), sinon les identifiants d’appel disparaissent au second tour.
En appels d’outils parallèles, l’ordre s’aligne sur l’ID d’appel
Une réponse peut porter plusieurs tool calls. Au renvoi, alignez sur call_id, pas sur l’indice de tableau. Logs de canary au minimum : nom d’outil, hash des arguments, durée, strict ou non, repli de schéma éventuel.
Checklist de migration des anciens projets
Séparez « ça tourne » et « c’est livrable ». Gardez un interrupteur vers l’ancien backend, rejouez d’abord le trafic réel dans un environnement isolé.
- Modèle : figer la nouvelle chaîne sur
gpt-5.6(ou le palier équivalent déjà ouvert sur le compte) ; la passerelle ne doit pas mapper en silence vers un vieux snapshot. - Sortie : remplacer
json_objectparjson_schema+strict: true, outext.formatcôté Responses. - Outils : chaque function avec
requiredcomplet etadditionalProperties: falseimbriqué ; champs optionnels en union nullable. - Parse : brancher
parse()et la brancherefusal; en streaming, vérifier la cohérence JSON incrémental / objet parsed final. - Surface d’outils : au-delà d’une dizaine, évaluer
tool_search; d’abord raccourcir les descriptions, puis le chargement tardif. - Contrôle : mêmes tâches figées — taux de retry, champs manquants, reprise manuelle entre ancien JSON Mode et nouveau chemin schéma.
Le critère n’est pas un 200, c’est : parseur sans filet regex, types d’arguments d’outils stables, refus observables, interrupteur de rollback déjà exercé. SDK longtemps ouvert, scripts de replay et sessions navigateur : un portable qui s’endort casse l’expérience — c’est le rôle du Mac mini cloud plus bas.
FAQ
Peut-on mélanger JSON Mode et Structured Outputs ?
Pas sur la même chaîne. JSON Mode ne garantit qu’un JSON valide ; Structured Outputs garantit le schéma. Mélanger rend le monitoring incapable de dire si un échec de parse est un problème de modèle ou de contrat. Le code neuf n’utilise que json_schema / text.format.
Faut-il encore écrire Chat Completions pour un projet neuf ?
Responses dès que c’est possible. Exemples officiels, helpers parse et normalisation strict y passent en priorité. Le stock Chat Completions peut rester, mais avec strict explicite, en acceptant le défaut non strict.
Pourquoi un 400 dès que j’active strict ?
Le plus souvent : required manquant, objet imbriqué sans additionalProperties:false, racine en anyOf, ou optionnel exprimé par « absent de required ». Complétez la contrainte citée dans l’erreur ; n’éteignez pas strict pour masquer un schéma faux, sauf repli non strict volontaire.
Function Calling doit-il toujours être strict ?
La reco officielle : toujours. Sans strict, les arguments sont best effort — l’exécuteur doit encore se défendre contre champs manquants et dérive de types. Responses peut réécrire un strict omis côté serveur ; journalisez la valeur finale de strict.
Quand tool_search vaut le coup ?
Quand les définitions d’outils occupent clairement le contexte, ou que la plupart des outils ne servent jamais dans une tâche. Il faut gpt-5.4 ou plus. Avant prod, rejouer la trajectoire en deux temps « chercher l’outil puis l’appeler » ; un ancien exécuteur qui ne connaît que function_call s’arrête net.
Le schéma tient : faut-il encore valider les valeurs métier ?
Oui. Le constrained decoding ne vérifie ni clés étrangères, ni droits, ni idempotence. Un enum légal n’a pas forcément de sens pour votre stock. Séparez logs schéma et logs métier, sinon l’incident est illisible.
Quelle différence gpt-5.6 / GPT-5.x plus ancien sur la sortie structurée ?
La doc marque gpt-5.6 comme défaut des nouveaux projets. L’alignement réel sur votre compte, région, batch et fine-tune se juge à la liste de modèles du moment et à une requête parse minimale — pas aux alias d’un blog pour deviner le mapping de passerelle.
Peut-on partager le même JSON Schema avec Claude / Grok ?
Le dialecte est proche de Draft 2020-12, les sous-ensembles diffèrent. OpenAI strict exige additionalProperties:false sur chaque object ; certains fournisseurs rejettent ce champ au niveau imbriqué. Une source métier, une couche de transformation par fournisseur.
Pour aller plus loin
Sur un Mac mini cloud, la validation de schéma peut tourner 24/7
La régression Function Calling / Structured Outputs, c’est une expérience de comparaison longue : deux SDK, jeu de replay figé, bac à sable d’outils, front streaming — sans portable qui s’endort au rabattement. La mémoire unifiée Apple Silicon convient pour faire tourner proxy local et debug navigateur en parallèle ; Homebrew, Docker et SSH sont prêts sur macOS. Un Mac mini M4 consomme environ 4 W en veille, adapté pour laisser l’environnement d’acceptation tourner la nuit.
S’il vous faut un Mac qui n’accapare pas la bande passante familiale, joignable en SSH en permanence pour rejouer des agents, le Mac mini M4 cloud Nuvcloud est l’option à faible friction pour séparer machine de dev et machine d’expérience — voir les offres, pour que le canary de schéma strict ne tienne plus à votre laptop.