Structured Outputs avec les APIs OpenAI-compatible : guide JSON Schema

Guide pratique des JSON Schemas stricts avec Responses et Chat Completions, validation, gestion des échecs et tests de compatibilité.

Structured Outputs permet de demander un JSON conforme à un schéma défini, au lieu de simplement demander au modèle de « répondre en JSON ». Avec une API OpenAI-compatible, le même JSON Schema peut être utilisé via Responses ou Chat Completions, mais les champs d’enveloppe diffèrent et le support doit être vérifié sur le modèle et la route exacts.

Le modèle de production sûr comporte trois couches : utiliser un schéma strict lorsqu’il est pris en charge, parser et valider de nouveau le résultat dans l’application, puis appliquer des règles métier déterministes. La conformité au schéma réduit les erreurs de format ; elle ne garantit pas l’exactitude sémantique ou factuelle.

Structured Outputs n’est pas JSON mode

Méthode JSON valide Schéma imposé Usage courant
Prompt seul Non garanti Non Prototype tolérant les erreurs de parsing
JSON mode Oui si pris en charge et terminé Non JSON flexible validé par l’application
Structured Outputs avec strict: true Oui, avec gestion de fin et refusal Oui, dans le sous-ensemble supporté Extraction typée et workflows applicatifs

Le guide OpenAI Structured Outputs recommande cette méthode plutôt que JSON mode quand le modèle et l’endpoint la prennent en charge. Le format structuré sert à produire un objet de réponse prévisible ; Function Calling demande une action à l’application. Les deux peuvent partager un Schema, mais pas le contrat de Call IDs et de résultats.

Définir le schéma avant de choisir l’endpoint

Une classification de tickets nécessite quatre champs :

{
  "type": "object",
  "properties": {
    "category": { "type": "string", "enum": ["billing", "technical", "account"] },
    "priority": { "type": "integer", "minimum": 1, "maximum": 3 },
    "requires_human": { "type": "boolean" },
    "summary": { "type": "string" }
  },
  "required": ["category", "priority", "requires_human", "summary"],
  "additionalProperties": false
}

Rendre toutes les propriétés required et utiliser additionalProperties: false stabilise l’objet consommé en aval. Cela ne rend pas tous les keywords JSON Schema portables. OpenAI documente un sous-ensemble ; un autre fournisseur peut en prendre en charge un autre, ou ne pas proposer de Strict Schema Mode.

Envoyer le schéma avec Responses API

Responses place le schéma sous text.format :

{
  "model": "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>",
  "input": "The customer was charged twice and wants a refund.",
  "text": {
    "format": {
      "type": "json_schema",
      "name": "support_ticket",
      "schema": {
        "type": "object",
        "properties": {
          "category": { "type": "string", "enum": ["billing", "technical", "account"] },
          "priority": { "type": "integer", "minimum": 1, "maximum": 3 },
          "requires_human": { "type": "boolean" },
          "summary": { "type": "string" }
        },
        "required": ["category", "priority", "requires_human", "summary"],
        "additionalProperties": false
      },
      "strict": true
    }
  }
}

Envoyez ce body à POST https://modelflare.dev/v1/responses avec Authorization: Bearer <YOUR_API_KEY>. Le contenu généré se trouve dans des output items typés. Un SDK peut proposer un helper de texte ou parsed output ; un client HTTP doit néanmoins localiser le texte terminé et le parser explicitement.

Envoyer le même schéma avec Chat Completions

Chat Completions utilise response_format.json_schema :

{
  "model": "<MODEL_WITH_VERIFIED_STRUCTURED_OUTPUT_SUPPORT>",
  "messages": [
    { "role": "user", "content": "The customer was charged twice and wants a refund." }
  ],
  "response_format": {
    "type": "json_schema",
    "json_schema": {
      "name": "support_ticket",
      "schema": {
        "type": "object",
        "properties": {
          "category": { "type": "string", "enum": ["billing", "technical", "account"] },
          "priority": { "type": "integer", "minimum": 1, "maximum": 3 },
          "requires_human": { "type": "boolean" },
          "summary": { "type": "string" }
        },
        "required": ["category", "priority", "requires_human", "summary"],
        "additionalProperties": false
      },
      "strict": true
    }
  }
}

Le JSON se trouve généralement sous forme de string dans choices[0].message.content et doit être parsé.

Objectif Responses API Chat Completions
Conteneur text.format response_format.json_schema
Type text.format.type response_format.type
Nom text.format.name response_format.json_schema.name
Schéma text.format.schema response_format.json_schema.schema
Strict text.format.strict response_format.json_schema.strict
Résultat Output items / helper choices[0].message.content

La couche de compatibilité Modelflare convertit les deux wrappers sur les routes OpenAI/Codex prises en charge. Elle ne crée toutefois pas la fonction Structured Outputs dans un modèle upstream qui en est dépourvu. Pour les familles en Raw Chat Completions Pass-through, le contrat du fournisseur reste la source de vérité.

Valider séparément structure et sens

{
  "category": "billing",
  "priority": 2,
  "requires_human": true,
  "summary": "Customer reports a duplicate charge and requests a refund."
}

Validation structurelle

Un validateur JSON Schema confirme propriétés required, absence de propriétés inattendues, types, enum et plages. Même avec Strict Adherence, cette validation protège contre route non supportée, erreur d’intégration, truncation et évolution future du contrat.

Validation sémantique et métier

Le schéma ne sait pas si le client a réellement été débité deux fois, si priority: 2 est correct ou qui peut autoriser un remboursement. Traitez l’objet comme une classification proposée, comparez-le aux données de référence et imposez permissions et limites financières dans du code déterministe. Un objet valide ne doit jamais contourner autorisation, billing ou sécurité.

Gérer les sorties incomplètes ou exceptionnelles

Refusals

Vérifiez Status et représentation du refusal avant de rechercher le JSON. Un refus de sécurité n’est pas une erreur de parsing.

Truncation et limites

Si la génération s’arrête avant la fin de l’objet, le schéma ne peut pas reconstituer le suffixe. Contrôlez Completion Status et Stop Reason et configurez un Output Limit suffisant mais borné.

Keywords non pris en charge

Commencez avec Objects, Arrays, Primitive Types, Enums, Required et Additional Properties explicites. Vérifiez la documentation avant References, récursion, Unions complexes ou validations avancées.

Latence du premier schéma

Certains fournisseurs prétraitent et mettent en cache un nouveau schéma. Réutilisez des schémas stables et versionnés et mesurez séparément First Use et Steady State.

Modèle ou route incompatible

Un endpoint peut accepter Chat Completions, refuser json_schema ou ignorer strict. Un 200 contenant du JSON ne prouve pas Strict Adherence. Testez entrées invalides et cas limites.

Exécuter une matrice de compatibilité

Test Preuve à conserver
Objet required minimal Status, modèle, route, objet parsé et validation
Chaque valeur enum Production et parsing de chaque valeur
Information absente Valeur de repli bornée ou clarification
Entrée de sécurité Représentation du refusal et traitement
Output Limit faible Completion Status et truncation
Fonction non supportée Erreur explicite ou contrainte ignorée
Wrappers Responses et Chat Objets applicatifs équivalents
Schéma stable répété First Use face à Steady State

Conservez Schema, Prompt, Region, Streaming Mode et Output Limit constants lors d’une comparaison et notez la date du test.

Choisir l’endpoint après avoir testé le workflow

Responses convient aux output items typés, aux streaming events Responses et aux workflows Tool plus larges. Chat Completions convient à une intégration stable par messages lorsque response_format est pris en charge sur la route. Voir Responses API vs Chat Completions.

Dans tous les cas :

  1. conservez une Source of Truth versionnée pour le JSON Schema ;
  2. changez uniquement le wrapper, pas le sens ;
  3. validez la réponse terminée ;
  4. appliquez ensuite les règles métier déterministes ;
  5. surveillez séparément parsing, refusal, truncation et compatibilité.

Pour la migration, utilisez le guide OpenAI-Compatible API ; pour les streams incomplets, le guide AI API Streaming. Consultez Models & Pricing, puis testez Structured Outputs sur la route exacte de l’API Key.

Structured Outputs transforme la réponse en interface applicative plus fiable, mais ne remplace ni vérification factuelle, ni autorisation, ni logique de facturation. « OpenAI-compatible » reste une affirmation à tester fonction par fonction.