DEV Community

Cover image for Comment utiliser les sorties structurées d'OpenAI
Antoine Laurent
Antoine Laurent

Posted on • Originally published at apidog.com

Comment utiliser les sorties structurées d'OpenAI

À la fin de ce guide, vous saurez appeler les sorties structurées d’OpenAI depuis votre code : fournir un schéma JSON au modèle, activer strict: true, lire une réponse conforme à la forme attendue, gérer les refus et la troncation, puis générer des collections de tests d’API dans Apidog pour vérifier que la charge utile respecte réellement le contrat.

Essayez Apidog aujourd’hui

Ce dont vous avez besoin avant de commencer

Les sorties structurées contraignent la génération du modèle afin que la sortie corresponde à un schéma JSON fourni. Avec strict: true, le modèle doit respecter les clés requises, les types, les énumérations et l’interdiction de champs supplémentaires si votre schéma l’exige.

Préparez :

  • une clé API OpenAI exposée dans OPENAI_API_KEY ;
  • un modèle compatible avec l’application stricte des schémas ;
  • un schéma JSON décrivant précisément la réponse attendue ;
  • un test de validation côté API pour détecter les dérives.

Les prompts du type « réponds uniquement en JSON » ne suffisent pas pour du code de production. Ils peuvent produire du JSON invalide, ajouter du texte autour de l’objet ou changer un type. Les sorties structurées déplacent ce contrat dans le décodage du modèle.

Choisissez le bon modèle

Les sorties structurées sont disponibles sur les modèles récents d’OpenAI, à partir de la famille GPT-4o et sur la série GPT-5. La documentation d’OpenAI recommande de démarrer les nouveaux projets sur son modèle phare actuel (gpt-5.5 au moment de la rédaction).

Les anciens modèles, notamment ceux de l’ère gpt-3.5, peuvent prendre en charge le mode JSON sans appliquer strictement un schéma. Si votre code dépend de strict: true, vérifiez l’ID exact du modèle avant de déployer.

Deux fonctionnalités sont proches, mais différentes :

Mode JSON Sorties structurées strictes
Paramètre response_format: {"type":"json_object"} response_format avec type: "json_schema" et strict: true
JSON valide Oui Oui
Conforme à votre schéma Non Oui
Champs requis appliqués Non Oui
Types et énumérations appliqués Non Oui
Validation aval Toujours nécessaire Recommandée

Avec Chat Completions, vous utilisez response_format. Avec la nouvelle API Responses, le même concept se configure sous text.format avec type: "json_schema". Le schéma reste le même ; seule l’enveloppe de la requête change.

Effectuez votre première requête

Exemple : extraire un ticket de support utilisateur dans un objet typé.

curl https://api.openai.com/v1/chat/completions \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.5",
    "messages": [
      { "role": "system", "content": "Extract the ticket into the schema." },
      { "role": "user", "content": "My checkout 500s every time I use a saved card. Started today. Account: acct_8842." }
    ],
    "response_format": {
      "type": "json_schema",
      "json_schema": {
        "name": "support_ticket",
        "strict": true,
        "schema": {
          "type": "object",
          "properties": {
            "summary":  { "type": "string" },
            "category": {
              "type": "string",
              "enum": ["billing", "bug", "account", "other"]
            },
            "severity": { "type": "integer" },
            "account_id": {
              "anyOf": [
                { "type": "string" },
                { "type": "null" }
              ]
            }
          },
          "required": ["summary", "category", "severity", "account_id"],
          "additionalProperties": false
        }
      }
    }
  }'
Enter fullscreen mode Exit fullscreen mode

Points importants dans ce schéma :

  • required contient toutes les propriétés ;
  • additionalProperties: false bloque les champs inventés ;
  • category est limitée à une énumération ;
  • account_id est toujours présent, mais peut valoir null.

Lisez la réponse

Le contenu du message est une chaîne JSON conforme au schéma :

{
  "summary": "Checkout returns HTTP 500 when paying with a saved card",
  "category": "bug",
  "severity": 3,
  "account_id": "acct_8842"
}
Enter fullscreen mode Exit fullscreen mode

Côté application, parsez le contenu uniquement après avoir vérifié que la réponse n’est pas un refus.

Exemple en Python :

import json

msg = response.choices[0].message

if msg.refusal:
    handle_refusal(msg.refusal)
else:
    ticket = json.loads(msg.content)
    process_ticket(ticket)
Enter fullscreen mode Exit fullscreen mode

Respectez le sous-ensemble JSON Schema pris en charge

Les sorties structurées acceptent un sous-ensemble du schéma JSON. Restez dans ces règles pour éviter les rejets ou les comportements inattendus.

1. La racine doit être un objet

Ne mettez pas un tableau au niveau racine.

À éviter :

[
  { "id": "ticket_1" }
]
Enter fullscreen mode Exit fullscreen mode

À faire :

{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "id": { "type": "string" }
        },
        "required": ["id"],
        "additionalProperties": false
      }
    }
  },
  "required": ["items"],
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

2. Toutes les propriétés doivent être requises

Les sorties structurées n’utilisent pas le facultatif classique. Pour modéliser une valeur absente, rendez le champ annulable.

"account_id": {
  "anyOf": [
    { "type": "string" },
    { "type": "null" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

La clé account_id sera toujours présente, mais sa valeur pourra être null.

3. Interdisez les propriétés supplémentaires

Ajoutez additionalProperties: false sur chaque objet.

{
  "type": "object",
  "properties": {
    "status": {
      "type": "string",
      "enum": ["open", "closed"]
    }
  },
  "required": ["status"],
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

4. Gardez le schéma raisonnable

Des limites s’appliquent : environ 100 propriétés d’objet et jusqu’à 5 niveaux d’imbrication. Si votre schéma devient trop profond, aplatissez-le ou divisez la tâche en plusieurs appels.

5. Ne confondez pas structure et validation métier

Certains mots-clés comme pattern, format, minLength ou minimum ne sont pas garantis par le modèle. Si vous devez valider une expression régulière, un format de date ou une plage numérique, faites-le dans votre code ou dans vos tests.

Si vous utilisez déjà des champs optionnels ou d’union avec oneOf/anyOf/allOf, le principe est le même : le schéma verrouille la forme, mais vos tests valident les règles métier.

Gérez les refus et la troncation

Une sortie peut ne pas respecter votre schéma dans trois cas principaux.

Refus de sécurité

Si le modèle refuse une requête, il peut renvoyer un champ refusal au lieu du contenu conforme au schéma.

msg = response.choices[0].message

if msg.refusal:
    handle_refusal(msg.refusal)
else:
    ticket = json.loads(msg.content)
Enter fullscreen mode Exit fullscreen mode

Ne cherchez pas un refus en scannant du texte. Branchez votre logique sur le champ prévu.

Troncation

Si la réponse atteint max_tokens au milieu d’un objet, le JSON peut être incomplet. Prévoyez :

  • une limite max_tokens suffisante ;
  • une gestion d’erreur au parsing ;
  • un retry si la sortie est tronquée.
try:
    ticket = json.loads(msg.content)
except json.JSONDecodeError:
    retry_request()
Enter fullscreen mode Exit fullscreen mode

Appels d’outils parallèles

Les sorties structurées ne supportent pas les appels d’outils parallèles. Si vous combinez outils et sortie structurée, définissez :

"parallel_tool_calls": false
Enter fullscreen mode Exit fullscreen mode

Testez le contrat dans Apidog

Le mode strict aide à produire une réponse conforme. Il ne remplace pas les tests. Un modèle peut changer, un prompt peut évoluer, un champ peut être retiré du tableau required, ou un chemin de refus peut casser votre intégration.

C’est là qu’Apidog intervient.

La séparation des responsabilités est simple :

  • OpenAI applique le schéma au moment de la génération ;
  • Apidog valide la réponse reçue par rapport au contrat attendu ;
  • votre CI échoue si la charge utile dérive.

Flux de travail recommandé

  1. Créez la requête OpenAI dans Apidog

Ajoutez l’appel Chat Completions avec le bloc response_format.

   {
     "type": "json_schema",
     "json_schema": {
       "name": "support_ticket",
       "strict": true,
       "schema": {
         "type": "object",
         "properties": {
           "summary": { "type": "string" },
           "category": {
             "type": "string",
             "enum": ["billing", "bug", "account", "other"]
           },
           "severity": { "type": "integer" },
           "account_id": {
             "anyOf": [
               { "type": "string" },
               { "type": "null" }
             ]
           }
         },
         "required": ["summary", "category", "severity", "account_id"],
         "additionalProperties": false
       }
     }
   }
Enter fullscreen mode Exit fullscreen mode
  1. Enregistrez la requête dans une collection

Une collection rend l’appel reproductible pour votre équipe et votre pipeline.

  1. Ajoutez des assertions

Vérifiez que :

  • category appartient à l’énumération ;
  • severity est un entier ;
  • account_id est une chaîne ou null ;
  • aucun champ inattendu n’est présent.

Apidog peut valider la réponse par rapport au schéma JSON, ce qui permet de réutiliser le même contrat que celui transmis à OpenAI.

  1. Exécutez la collection en CI

Ajoutez la collection à votre pipeline. Chaque changement de modèle, de prompt ou de schéma relance la vérification.

  1. Mockez la réponse pour les consommateurs

Avant que l’appel OpenAI soit stable, créez une API mock qui renvoie des exemples conformes au schéma.

Votre frontend, vos tests d’intégration et vos services aval peuvent avancer sans dépendre de l’appel réel ni consommer de tokens.

Vous pouvez télécharger Apidog pour construire la requête, les assertions et la maquette au même endroit.

Questions fréquemment posées

Le mode JSON est-il déprécié ?

Non. Le mode JSON fonctionne toujours et garantit un JSON syntaxiquement valide. Il ne garantit pas la conformité à votre schéma.

Pour du nouveau code, utilisez les sorties structurées avec strict: true lorsque le modèle le supporte.

La racine du schéma peut-elle être un tableau ?

Non. Le niveau supérieur doit être un objet.

Utilisez une propriété comme items :

{
  "type": "object",
  "properties": {
    "items": {
      "type": "array",
      "items": { "type": "string" }
    }
  },
  "required": ["items"],
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

Comment rendre un champ facultatif ?

Vous ne le rendez pas facultatif au sens JSON Schema classique. Vous le rendez annulable.

{
  "type": "object",
  "properties": {
    "phone": {
      "anyOf": [
        { "type": "string" },
        { "type": "null" }
      ]
    }
  },
  "required": ["phone"],
  "additionalProperties": false
}
Enter fullscreen mode Exit fullscreen mode

La clé phone sera toujours présente.

strict: true permet-il d’ignorer toute validation ?

Non. La structure est contrainte, mais certaines règles métier restent à vérifier : formats, regex, bornes numériques, cohérence entre champs, refus et troncation.

Si vous débutez avec ce format, ce manuel d’introduction au schéma JSON couvre les bases.

Quels modèles utiliser ?

Utilisez un modèle qui prend en charge les sorties structurées strictes, comme GPT-4o ou une version ultérieure, y compris la série GPT-5. Vérifiez toujours l’ID exact du modèle dans la documentation OpenAI avant de vous appuyer sur strict: true.

En résumé

Le workflow complet est :

  1. choisir un modèle compatible avec les sorties structurées ;
  2. envoyer une requête avec response_format, json_schema et strict: true ;
  3. respecter le sous-ensemble JSON Schema pris en charge ;
  4. gérer explicitement refusal, la troncation et les appels d’outils ;
  5. valider la réponse dans vos tests ;
  6. exécuter ces tests en CI ;
  7. mocker le contrat pour les consommateurs aval.

Construisez la requête dans Apidog, validez la réponse par rapport au schéma, puis simulez l’API pour stabiliser le reste de votre stack. Le modèle promet la forme ; vos tests prouvent qu’elle reste conforme.

Top comments (0)