For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale

Sorties structurées du modèle

Assurez-vous que les réponses textuelles du modèle respectent le schéma JSON que vous définissez.

JSON est l’un des formats les plus utilisés au monde pour échanger des données entre applications.

Les sorties structurées garantissent que le modèle génère toujours des réponses conformes au schéma JSON que vous fournissez. Vous n’avez donc pas à craindre qu’il omette une clé obligatoire ou invente une valeur d’énumération non valide.

Les sorties structurées offrent notamment les avantages suivants :

  1. Respect fiable des types : plus besoin de valider les réponses mal formatées ni de relancer les requêtes correspondantes
  2. Refus explicites : les refus du modèle pour des raisons de sécurité sont désormais détectables par programmation
  3. Conception de prompts simplifiée : plus besoin de prompts insistants pour obtenir un format cohérent

En plus de prendre en charge JSON Schema dans l’API REST, les bibliothèques OpenAI pour Python et JavaScript permettent de définir des schémas d’objets à l’aide de pydantic.BaseModel et de z.object, respectivement. L’exemple ci-dessous montre comment extraire, à partir d’un texte non structuré, des informations conformes à un schéma défini dans le code.

Le SDK Ruby prend en charge les schémas définis avec T::Struct de Sorbet et renvoie des résultats analysés et typés.

Obtention d’une réponse structurée
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()


class CalendarEvent(BaseModel):
    name: str
    date: str
    participants: list[str]


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {"role": "system", "content": "Extract the event information."},
        {
            "role": "user",
            "content": "Alice and Bob are going to a science fair on Friday.",
        },
    ],
    text_format=CalendarEvent,
)

event = response.output_parsed

Modèles pris en charge

Les sorties structurées sont disponibles dans nos grands modèles de langage les plus récents, à partir de GPT-4o. Pour les nouveaux projets, commencez avec gpt-6-astra. Les modèles plus anciens, comme gpt-4-turbo et les versions antérieures, peuvent utiliser le mode JSON à la place.

Quand utiliser les sorties structurées avec l’appel de fonction ou avec text.format

Les sorties structurées sont disponibles sous deux formes dans l’API OpenAI :

  1. Avec l’appel de fonction
  2. Avec un format de réponse json_schema

L’appel de fonction est utile lorsque vous développez une application qui relie les modèles à ses fonctionnalités.

Par exemple, vous pouvez donner au modèle accès à des fonctions qui interrogent une base de données pour créer un assistant IA capable d’aider les utilisateurs avec leurs commandes, ou à des fonctions qui interagissent avec l’interface utilisateur.

En revanche, les sorties structurées via response_format conviennent mieux lorsque vous souhaitez définir un schéma à respecter pour les réponses du modèle à l’utilisateur, plutôt que pour ses appels d’outils.

Par exemple, si vous développez une application de soutien en mathématiques, vous pouvez souhaiter que l’assistant réponde à l’utilisateur selon un schéma JSON précis, afin de générer une interface qui affiche différemment les différentes parties de la sortie du modèle.

En pratique :

  • Si vous connectez le modèle à des outils, des fonctions, des données, etc. dans votre système, utilisez l’appel de fonction. Si vous souhaitez structurer la sortie du modèle lorsqu’il répond à l’utilisateur, utilisez un format structuré via text.format

La suite de ce guide porte sur les cas d’utilisation sans appel de fonction dans l’API Responses. Pour en savoir plus sur l’utilisation des sorties structurées avec l’appel de fonction, consultez la section

Appel de fonction

du guide.

Sorties structurées et mode JSON

Les sorties structurées sont une évolution du mode JSON. Les deux garantissent la production de JSON valide, mais seules les sorties structurées garantissent le respect du schéma. Les sorties structurées et le mode JSON sont tous deux pris en charge dans l’API Responses, l’API Chat Completions, l’API Assistants, l’API d’affinage et l’API de traitement par lots.

Nous vous recommandons de toujours utiliser les sorties structurées plutôt que le mode JSON lorsque c’est possible.

Toutefois, les sorties structurées avec response_format: {type: "json_schema", ...} ne sont prises en charge que par les versions de modèle gpt-4o-mini, gpt-4o-mini-2024-07-18 et gpt-4o-2024-08-06, ainsi que les versions ultérieures.

Sorties structuréesMode JSON
Produit du JSON valideOuiOui
Respecte le schémaOui (voir les schémas pris en charge)Non
Modèles compatiblesgpt-4o-mini, gpt-4o-2024-08-06 et versions ultérieuresgpt-3.5-turbo, gpt-4-*, gpt-4o-* et modèles GPT-5 compatibles
Activationtext: { format: { type: "json_schema", "strict": true, "schema": ... } }text: { format: { type: "json_object" } }

Exemples

Raisonnement détaillé (« chain-of-thought »)

Vous pouvez demander au modèle de fournir une réponse structurée, étape par étape, pour guider l’utilisateur vers la solution.

Sorties structurées pour le tutorat en mathématiques avec raisonnement détaillé (« chain-of-thought »)
from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()


class Step(BaseModel):
    explanation: str
    output: str


class MathReasoning(BaseModel):
    steps: list[Step]
    final_answer: str


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {
            "role": "system",
            "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
        },
        {"role": "user", "content": "how can I solve 8x + 7 = -23"},
    ],
    text_format=MathReasoning,
)

math_reasoning = response.output_parsed

Exemple de réponse

{
  "steps": [
    {
      "explanation": "Start with the equation 8x + 7 = -23.",
      "output": "8x + 7 = -23"
    },
    {
      "explanation": "Subtract 7 from both sides to isolate the term with the variable.",
      "output": "8x = -23 - 7"
    },
    {
      "explanation": "Simplify the right side of the equation.",
      "output": "8x = -30"
    },
    {
      "explanation": "Divide both sides by 8 to solve for x.",
      "output": "x = -30 / 8"
    },
    {
      "explanation": "Simplify the fraction.",
      "output": "x = -15 / 4"
    }
  ],
  "final_answer": "x = -15 / 4"
}

Comment utiliser les sorties structurées avec text.format

Refus avec les sorties structurées

Lorsque vous utilisez les sorties structurées avec des données fournies par les utilisateurs, les modèles OpenAI peuvent parfois refuser de répondre à la demande pour des raisons de sécurité. Comme un refus ne respecte pas nécessairement le schéma fourni dans response_format, la réponse de l’API inclut un nouveau champ nommé refusal pour indiquer que le modèle a refusé de répondre à la demande.

Lorsque la propriété refusal apparaît dans votre objet de sortie, vous pouvez afficher le refus dans votre interface utilisateur ou ajouter une logique conditionnelle au code qui traite la réponse pour gérer ce cas.

class Step(BaseModel):
    explanation: str
    output: str


class MathReasoning(BaseModel):
    steps: list[Step]
    final_answer: str


response = client.responses.parse(
    model="gpt-6-astra",
    input=[
        {
            "role": "system",
            "content": "You are a helpful math tutor. Guide the user through the solution step by step.",
        },
        {"role": "user", "content": "how can I solve 8x + 7 = -23"},
    ],
    text_format=MathReasoning,
)

for output in response.output:
    if output.type != "message":
        continue

    for item in output.content:
        if item.type == "refusal":
            # If the model refuses to respond, you will get a refusal message
            print(item.refusal)
            continue

        if not item.parsed:
            raise Exception("Could not parse response")

        print(item.parsed)

En cas de refus, la réponse de l’API ressemble à ceci :

{
  "id": "resp_1234567890",
  "object": "response",
  "created_at": 1721596428,
  "status": "completed",
  "completed_at": 1721596429,
  "error": null,
  "incomplete_details": null,
  "input": [],
  "instructions": null,
  "max_output_tokens": null,
  "model": "gpt-4o-2024-08-06",
  "output": [{
    "id": "msg_1234567890",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "refusal",
        "refusal": "I'm sorry, I cannot assist with that request."
      }
    ]
  }],
  "usage": {
    "input_tokens": 81,
    "output_tokens": 11,
    "total_tokens": 92,
    "output_tokens_details": {
      "reasoning_tokens": 0,
    }
  },
}

Conseils et bonnes pratiques

Gestion des données fournies par les utilisateurs

Si votre application utilise des données fournies par les utilisateurs, assurez-vous que votre prompt contient des instructions pour gérer les situations où ces données ne permettent pas de produire une réponse valide.

Le modèle essaiera toujours de respecter le schéma fourni, ce qui peut entraîner des hallucinations si les données d’entrée n’ont aucun rapport avec ce schéma.

Vous pouvez préciser dans votre prompt que le modèle doit renvoyer des paramètres vides ou une phrase spécifique s’il détecte que les données d’entrée sont incompatibles avec la tâche.

Gestion des erreurs

Les sorties structurées peuvent tout de même contenir des erreurs. Si vous en constatez, essayez d’ajuster vos instructions, de fournir des exemples dans les instructions système ou de décomposer les tâches en sous-tâches plus simples. Consultez le guide d’ingénierie de prompts pour obtenir des conseils supplémentaires sur la façon d’ajuster vos entrées.

Évitez les divergences du schéma JSON

Pour éviter que votre schéma JSON et les types correspondants dans votre langage de programmation divergent, nous vous recommandons vivement d’utiliser les fonctions utilitaires intégrées aux SDK pour les schémas, lorsqu’elles sont disponibles.

Si vous préférez spécifier directement le schéma JSON, vous pouvez ajouter des règles de CI qui signalent toute modification du schéma JSON ou des objets de données sous-jacents, ou ajouter une étape de CI qui génère automatiquement le schéma JSON à partir des définitions de types (ou inversement).

Streaming

Vous pouvez utiliser le streaming pour traiter les réponses du modèle ou les arguments d’appel de fonction au fur et à mesure de leur génération, et les analyser sous forme de données structurées.

Ainsi, vous n’avez pas à attendre que la réponse soit complète pour la traiter. C’est particulièrement utile si vous souhaitez afficher les champs JSON un par un ou traiter les arguments d’appel de fonction dès qu’ils sont disponibles.

Nous vous recommandons d’utiliser les SDK pour gérer le streaming avec les sorties structurées.

from openai import OpenAI
from pydantic import BaseModel


class EntitiesModel(BaseModel):
    attributes: list[str]
    colors: list[str]
    animals: list[str]


client = OpenAI()

with client.responses.stream(
    model="gpt-6-astra",
    input=[
        {"role": "system", "content": "Extract entities from the input text"},
        {
            "role": "user",
            "content": "The quick brown fox jumps over the lazy dog with piercing blue eyes",
        },
    ],
    text_format=EntitiesModel,
) as stream:
    for event in stream:
        if event.type == "response.refusal.delta":
            print(event.delta, end="")
        elif event.type == "response.output_text.delta":
            print(event.delta, end="")
        elif event.type == "response.error":
            print(event.error, end="")
        elif event.type == "response.completed":
            print("Completed")  # print(event.response.output)

    final_response = stream.get_final_response()
    print(final_response)

Schémas pris en charge

Les sorties structurées prennent en charge un sous-ensemble du langage JSON Schema.

Types pris en charge

Les types suivants sont pris en charge pour les sorties structurées :

  • Chaîne de caractères
  • Nombre
  • Booléen
  • Entier
  • Objet
  • Tableau
  • Énumération
  • anyOf

Propriétés prises en charge

En plus du type d’une propriété, vous pouvez définir certaines contraintes supplémentaires :

Propriétés prises en charge pour le type string :

  • pattern — Une expression régulière à laquelle la chaîne doit correspondre.
  • format — Des formats prédéfinis pour les chaînes. Les formats actuellement pris en charge sont :
    • date-time
    • time
    • date
    • duration
    • email
    • hostname
    • ipv4
    • ipv6
    • uuid

Propriétés prises en charge pour le type number :

  • multipleOf — Le nombre doit être un multiple de cette valeur.
  • maximum — Le nombre doit être inférieur ou égal à cette valeur.
  • exclusiveMaximum — Le nombre doit être strictement inférieur à cette valeur.
  • minimum — Le nombre doit être supérieur ou égal à cette valeur.
  • exclusiveMinimum — Le nombre doit être strictement supérieur à cette valeur.

Propriétés prises en charge pour le type array :

  • minItems — Le tableau doit contenir au moins ce nombre d’éléments.
  • maxItems — Le tableau doit contenir au plus ce nombre d’éléments.

Voici quelques exemples d’utilisation de ces contraintes de type :

{
    "name": "user_data",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "name": {
                "type": "string",
                "description": "The name of the user"
            },
            "username": {
                "type": "string",
                "description": "The username of the user. Must start with @",
                "pattern": "^@[a-zA-Z0-9_]+$"
            },
            "email": {
                "type": "string",
                "description": "The email of the user",
                "format": "email"
            }
        },
        "additionalProperties": false,
        "required": [
            "name", "username", "email"
        ]
    }
}

La racine doit être un objet et ne doit pas utiliser anyOf

La racine d’un schéma doit être un objet et ne doit pas utiliser anyOf. Une pratique courante avec Zod, par exemple, consiste à utiliser une union discriminée, qui produit un anyOf au niveau racine. Le code suivant ne fonctionnera donc pas :

import { z } from "zod";
import { zodResponseFormat } from "openai/helpers/zod";

const BaseResponseSchema = z.object({
  /* ... */
});
const UnsuccessfulResponseSchema = z.object({
  /* ... */
});

const finalSchema = z.discriminatedUnion("status", [
  BaseResponseSchema,
  UnsuccessfulResponseSchema,
]);

// Invalid JSON Schema for Structured Outputs
const json = zodResponseFormat(finalSchema, "final_schema");

Tous les champs doivent être déclarés dans required

Pour utiliser les sorties structurées, tous les champs ou paramètres de fonction doivent être déclarés dans required.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": "string",
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": ["location", "unit"]
    }
}

Bien que tous les champs soient obligatoires (et que le modèle renvoie une valeur pour chaque paramètre), il est possible de simuler un paramètre facultatif en utilisant un type union avec null.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "parameters": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": ["string", "null"],
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

La profondeur d’imbrication et la taille des objets sont limitées

Un schéma peut contenir jusqu’à 5 000 propriétés d’objet au total, avec un maximum de 10 niveaux d’imbrication.

Limites de longueur totale des chaînes

Dans un schéma, la longueur totale des chaînes correspondant aux noms de propriétés, aux noms de définitions, aux valeurs enum et aux valeurs const ne peut pas dépasser 120 000 caractères.

Limites de taille des énumérations

Un schéma peut contenir jusqu’à 1 000 valeurs enum, toutes propriétés enum confondues.

Pour une même propriété enum dont les valeurs sont des chaînes, la longueur totale de toutes les valeurs enum ne peut pas dépasser 15 000 caractères lorsqu’il y a plus de 250 valeurs enum.

Les objets doivent toujours définir additionalProperties: false

additionalProperties détermine si un objet peut contenir des paires clé-valeur supplémentaires qui ne sont pas définies dans le schéma JSON Schema.

Les sorties structurées permettent uniquement de générer les paires clé-valeur spécifiées. Les développeurs doivent donc définir additionalProperties: false pour activer les sorties structurées.

{
    "name": "get_weather",
    "description": "Fetches the weather in the given location",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "location": {
                "type": "string",
                "description": "The location to get the weather for"
            },
            "unit": {
                "type": "string",
                "description": "The unit to return the temperature in",
                "enum": ["F", "C"]
            }
        },
        "additionalProperties": false,
        "required": [
            "location", "unit"
        ]
    }
}

Ordre des clés

Avec les sorties structurées, les résultats sont générés dans le même ordre que les clés du schéma.

Certains mots-clés propres à un type ne sont pas encore pris en charge

  • Composition : allOf, not, dependentRequired, dependentSchemas, if, then, else

Pour les modèles affinés, les éléments suivants ne sont pas non plus pris en charge :

  • Pour les chaînes de caractères : minLength, maxLength, pattern, format
  • Pour les nombres : minimum, maximum, multipleOf
  • Pour les objets : patternProperties
  • Pour les tableaux : minItems, maxItems

Si vous activez les sorties structurées en fournissant strict: true et appelez l’API avec un schéma JSON Schema non pris en charge, vous recevrez une erreur.

Avec anyOf, chaque schéma imbriqué doit être un schéma JSON Schema valide qui respecte ce sous-ensemble

Voici un exemple de schéma anyOf pris en charge :

{
    "type": "object",
    "properties": {
        "item": {
            "anyOf": [
                {
                    "type": "object",
                    "description": "The user object to insert into the database",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "The name of the user"
                        },
                        "age": {
                            "type": "number",
                            "description": "The age of the user"
                        }
                    },
                    "additionalProperties": false,
                    "required": [
                        "name",
                        "age"
                    ]
                },
                {
                    "type": "object",
                    "description": "The address object to insert into the database",
                    "properties": {
                        "number": {
                            "type": "string",
                            "description": "The number of the address. Eg. for 123 main st, this would be 123"
                        },
                        "street": {
                            "type": "string",
                            "description": "The street name. Eg. for 123 main st, this would be main st"
                        },
                        "city": {
                            "type": "string",
                            "description": "The city of the address"
                        }
                    },
                    "additionalProperties": false,
                    "required": [
                        "number",
                        "street",
                        "city"
                    ]
                }
            ]
        }
    },
    "additionalProperties": false,
    "required": [
        "item"
    ]
}

Les définitions sont prises en charge

Vous pouvez utiliser des définitions pour créer des sous-schémas auxquels vous faites référence dans l’ensemble de votre schéma. Voici un exemple simple.

{
    "type": "object",
    "properties": {
        "steps": {
            "type": "array",
            "items": {
                "$ref": "#/$defs/step"
            }
        },
        "final_answer": {
            "type": "string"
        }
    },
    "$defs": {
        "step": {
            "type": "object",
            "properties": {
                "explanation": {
                    "type": "string"
                },
                "output": {
                    "type": "string"
                }
            },
            "required": [
                "explanation",
                "output"
            ],
            "additionalProperties": false
        }
    },
    "required": [
        "steps",
        "final_answer"
    ],
    "additionalProperties": false
}

Les schémas récursifs sont pris en charge

Exemple de schéma récursif utilisant # pour indiquer une récursion vers la racine.

{
    "name": "ui",
    "description": "Dynamically generated UI",
    "strict": true,
    "schema": {
        "type": "object",
        "properties": {
            "type": {
                "type": "string",
                "description": "The type of the UI component",
                "enum": ["div", "button", "header", "section", "field", "form"]
            },
            "label": {
                "type": "string",
                "description": "The label of the UI component, used for buttons or form fields"
            },
            "children": {
                "type": "array",
                "description": "Nested UI components",
                "items": {
                    "$ref": "#"
                }
            },
            "attributes": {
                "type": "array",
                "description": "Arbitrary attributes for the UI component, suitable for any element",
                "items": {
                    "type": "object",
                    "properties": {
                        "name": {
                            "type": "string",
                            "description": "The name of the attribute, for example onClick or className"
                        },
                        "value": {
                            "type": "string",
                            "description": "The value of the attribute"
                        }
                    },
                    "additionalProperties": false,
                    "required": ["name", "value"]
                }
            }
        },
        "required": ["type", "label", "children", "attributes"],
        "additionalProperties": false
    }
}

Exemple de schéma récursif utilisant une récursion explicite :

{
    "type": "object",
    "properties": {
        "linked_list": {
            "$ref": "#/$defs/linked_list_node"
        }
    },
    "$defs": {
        "linked_list_node": {
            "type": "object",
            "properties": {
                "value": {
                    "type": "number"
                },
                "next": {
                    "anyOf": [
                        {
                            "$ref": "#/$defs/linked_list_node"
                        },
                        {
                            "type": "null"
                        }
                    ]
                }
            },
            "additionalProperties": false,
            "required": [
                "next",
                "value"
            ]
        }
    },
    "additionalProperties": false,
    "required": [
        "linked_list"
    ]
}

Mode JSON

Le mode JSON est une version plus simple de la fonctionnalité de sorties structurées. Alors que le mode JSON garantit que la sortie du modèle est un JSON valide, les sorties structurées assurent de manière fiable la conformité de cette sortie au schéma que vous spécifiez. Nous vous recommandons d’utiliser les sorties structurées si elles sont prises en charge pour votre cas d’utilisation.

Lorsque le mode JSON est activé, la sortie du modèle est garantie d’être un JSON valide, sauf dans certains cas limites que vous devez détecter et gérer de manière appropriée.

Pour activer le mode JSON avec l’API Responses, vous pouvez définir text.format sur { "type": "json_object" }. Si vous utilisez l’appel de fonction, le mode JSON est toujours activé.

Remarques importantes :

  • Lorsque vous utilisez le mode JSON, vous devez toujours demander au modèle de produire du JSON dans un message de la conversation, par exemple dans votre message système. Sans instruction explicite de générer du JSON, le modèle peut produire un flux ininterrompu de caractères d’espacement et la requête peut continuer jusqu’à atteindre la limite de tokens. Pour vous aider à ne pas oublier cette instruction, l’API renvoie une erreur si la chaîne « JSON » n’apparaît nulle part dans le contexte.
  • Le mode JSON ne garantit pas que la sortie respecte un schéma particulier, mais uniquement qu’elle constitue un JSON valide pouvant être analysé sans erreur. Utilisez les sorties structurées pour garantir la conformité à votre schéma ou, si ce n’est pas possible, utilisez une bibliothèque de validation et, au besoin, de nouvelles tentatives pour vous assurer que la sortie respecte le schéma souhaité.
  • Votre application doit détecter et gérer les cas limites dans lesquels la sortie du modèle risque de ne pas être un objet JSON complet (voir ci-dessous)

Ressources

Pour en savoir plus sur les sorties structurées, nous vous recommandons de consulter les ressources suivantes :