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

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 que le modèle omette une clé obligatoire ou invente une valeur absente d’une énumération.

Les sorties structurées présentent plusieurs avantages :

  1. Respect des types : vous n’avez plus à valider des réponses mal formatées ni à relancer les requêtes correspondantes.
  2. Refus explicites : votre code peut désormais détecter les refus du modèle pour des raisons de sécurité.
  3. Prompts plus simples : inutile d’insister dans vos prompts pour obtenir un format cohérent.

Outre la prise en charge de JSON Schema dans l’API REST, les bibliothèques OpenAI pour Python et JavaScript permettent de définir des schémas d’objets avec pydantic.BaseModel et z.object respectivement. L’exemple ci-dessous montre comment extraire des informations d’un texte non structuré et les restituer selon un schéma défini dans le code.

Le SDK Ruby prend en charge les schémas définis avec le type T::Struct de Sorbet et renvoie des résultats typés après analyse de la réponse.

Obtenir 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 compatibles

Nos grands modèles de langage les plus récents prennent en charge les sorties structurées depuis GPT-4o. Pour un nouveau projet, commencez avec gpt-6-astra. Avec des modèles plus anciens, comme gpt-4-turbo et les versions antérieures, vous pouvez utiliser le mode JSON à la place.

Sorties structurées : choix entre l’appel de fonction et text.format

L’API OpenAI permet d’utiliser les sorties structurées de deux façons :

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

L’appel de fonction est utile pour donner aux modèles accès aux fonctionnalités de votre application.

Par exemple, vous pouvez créer un assistant IA qui aide les utilisateurs à gérer leurs commandes en donnant au modèle accès à des fonctions qui interrogent une base de données. Vous pouvez aussi lui donner accès à des fonctions qui interagissent avec l’interface utilisateur.

Les sorties structurées avec response_format conviennent mieux lorsque vous souhaitez définir le schéma que le modèle doit respecter dans sa réponse à l’utilisateur, plutôt que lors d’un appel d’outil.

Par exemple, si vous développez une application de soutien en mathématiques, vous pouvez demander à l’assistant de répondre à l’utilisateur selon un schéma JSON précis. Vous pouvez ainsi générer une interface qui présente chaque partie de la réponse de manière distincte.

En pratique :

  • Pour connecter le modèle aux outils, aux fonctions ou aux données de votre système, utilisez l’appel de fonction. Pour structurer la réponse du modèle à l’utilisateur, utilisez un format de réponse structuré avec text.format

La suite de ce guide traite des utilisations de l’API Responses sans appel de fonction. Pour savoir comment combiner sorties structurées et appel de fonction, consultez la section du guide sur

l’appel de fonction

consacrée au mode strict.

Sorties structurées et mode JSON

Les sorties structurées sont une évolution du mode JSON. Les deux garantissent une sortie JSON valide, mais seules les sorties structurées garantissent le respect du schéma. Les API Responses, Chat Completions, Assistants, Fine-tuning et Batch prennent en charge les sorties structurées et le mode JSON.

Nous recommandons de privilégier systématiquement les sorties structurées lorsque c’est possible.

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

Sorties structurées Mode JSON
Produit du JSON valide Oui Oui
Respecte le schéma Oui (voir les schémas pris en charge) Non
Modèles compatibles gpt-4o-mini, gpt-4o-2024-08-06 et versions ultérieures gpt-3.5-turbo, gpt-4-*, gpt-4o-* et modèles GPT-5 compatibles
Activation text: { 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 structurer sa réponse en étapes pour guider l’utilisateur tout au long de la résolution du problème.

Sorties structurées pour le tutorat en mathématiques avec un 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"
}

Utiliser les sorties structurées avec text.format

Changer le format de réponse en cours de conversation

Pour changer de schéma entre deux réponses, ajoutez un élément configuration_update à input avant le prochain message de l’utilisateur. Dans chaque requête, conservez la valeur initiale du paramètre de premier niveau text.format pour que le préfixe du prompt précédent reste admissible à la mise en cache des prompts.

Vous pouvez mettre à jour le format de réponse en mode standard avec un seul agent sur les modèles compatibles à partir de GPT-6.

Supposons qu’au tour précédent, un message ait décrit en texte brut une expédition de 17 caisses rouges et de 26 caisses bleues. Poursuivez la conversation avec previous_response_id et passez le tableau suivant comme valeur de input.

Passer du texte brut à un décompte structuré des caisses
from openai import OpenAI
from pydantic import BaseModel, ConfigDict


class CrateCount(BaseModel):
    model_config = ConfigDict(extra="forbid")
    total_crates: int


client = OpenAI()
model = "gpt-6-astra"

first = client.responses.create(
    model=model,
    input="Summarize this shipment in one sentence: 17 red crates and 26 blue crates.",
    store=True,
)

second = client.responses.parse(
    model=model,
    previous_response_id=first.id,
    input=[
        {
            "type": "configuration_update",
            "text": {
                "format": {
                    "type": "json_schema",
                    "name": "crate_count",
                    "schema": CrateCount.model_json_schema(),
                    "strict": True,
                }
            },
        },
        {"role": "user", "content": "How many crates are in the shipment?"},
    ],
    parse_as=CrateCount,
)

if second.status == "completed" and second.output_parsed is not None:
    print(second.output_parsed.total_crates)

La réponse attendue est {"total_crates":43}. Le champ text.format de l’objet de réponse indique toujours le format initial défini au niveau de la requête. Après avoir vérifié que la réponse est complète et que le modèle n’a pas opposé de refus, analysez la réponse à l’aide du schéma fourni dans la dernière mise à jour du format.

Chaque mise à jour remplace le format dans son intégralité. Ce format reste en vigueur d’un tour à l’autre, y compris lorsque vous modifiez uniquement reasoning.effort. Pour revenir au texte brut, ajoutez {"type":"configuration_update","text":{"format":{"type":"text"}}}. Les mises à jour du format acceptent uniquement json_schema et text.

Si vous renvoyez manuellement l’historique, conservez chaque mise à jour à sa position d’origine. Après un compactage explicite, spécifiez à nouveau la valeur de chaque paramètre que vous avez modifié. Consultez la section sur la compatibilité des mises à jour de configuration.

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 à une 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 contient alors un nouveau champ, refusal, qui signale le refus du modèle.

Lorsque la propriété refusal figure dans l’objet de sortie, vous pouvez afficher le refus dans votre interface ou ajouter une condition dans le 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

Traiter les données fournies par les utilisateurs

Si votre application utilise des données fournies par les utilisateurs, précisez dans votre prompt ce que le modèle doit faire lorsque ces données ne permettent pas de produire une réponse valide.

Le modèle essaie toujours de respecter le schéma fourni. Si les données n’ont aucun rapport avec ce schéma, il risque donc d’inventer des informations.

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

Gérer les erreurs

Les sorties structurées peuvent tout de même contenir des erreurs. Si vous en constatez, essayez d’ajuster vos instructions, d’ajouter 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 en savoir plus sur la façon d’ajuster vos entrées.

Maintenir la cohérence entre le schéma JSON et les types

Pour éviter que votre schéma JSON et les types correspondants dans votre langage de programmation ne divergent, nous vous recommandons vivement d’utiliser les utilitaires de schéma intégrés au SDK lorsqu’ils sont disponibles.

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

Diffusion en continu

La diffusion en continu vous permet de traiter les réponses du modèle ou les arguments d’un appel de fonction à mesure que le modèle les génère et de les analyser sous forme de données structurées.

Vous pouvez ainsi commencer à traiter la réponse avant de l’avoir reçue en entier. C’est particulièrement utile si vous souhaitez afficher les champs JSON un par un ou traiter les arguments d’un appel de fonction dès qu’ils sont disponibles.

Nous vous recommandons d’utiliser les SDK pour gérer la diffusion en continu des 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 sorties structurées prennent en charge les types suivants :

  • 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 spécifier les contraintes suivantes :

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

  • pattern — Expression régulière à laquelle la chaîne doit correspondre.
  • format — Format prédéfini de la chaîne. Les formats actuellement pris en charge sont les suivants :
    • 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 — Nombre minimal d’éléments que doit contenir le tableau.
  • maxItems — Nombre maximal d’éléments que peut contenir le tableau.

Voici quelques exemples d’utilisation de ces contraintes :

{
    "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 peut pas utiliser anyOf

La racine du schéma doit être de type objet et ne doit pas utiliser anyOf. Avec Zod, par exemple, une union discriminée produit un anyOf à la 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 figurer dans required

Pour utiliser les sorties structurées, indiquez dans required tous les champs ou paramètres de fonction.

{
    "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"]
    }
}

Tous les champs sont obligatoires et le modèle renvoie une valeur pour chaque paramètre. Vous pouvez toutefois simuler un paramètre facultatif en utilisant un type union qui accepte 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"
        ]
    }
}

Limites de profondeur d’imbrication et de taille des objets

Un schéma peut contenir jusqu’à 5 000 propriétés d’objet au total et jusqu’à 10 niveaux d’imbrication.

Limite de longueur cumulée des chaînes

Dans un schéma, la longueur cumulée des noms de propriétés, des noms de définitions, des valeurs d’énumération et des valeurs de 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 d’énumération, toutes propriétés d’énumération confondues.

Si une propriété d’énumération contient plus de 250 valeurs de type chaîne, leur longueur cumulée ne peut pas dépasser 15 000 caractères.

Chaque objet doit spécifier additionalProperties: false

additionalProperties détermine si un objet peut contenir des paires clé-valeur supplémentaires que le schéma JSON ne définit pas.

Les sorties structurées ne peuvent générer que les paires clé-valeur prévues par le schéma. Pour utiliser les sorties structurées, spécifiez donc additionalProperties: false.

{
    "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

Dans les sorties structurées, les clés apparaissent dans le même ordre que dans le 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 ajustés, les mots-clés suivants ne sont pas non plus pris en charge :

  • Pour les chaînes : 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 spécifiant strict: true et que vous appelez l’API avec un schéma JSON non pris en charge, l’API renvoie une erreur.

Avec anyOf, chaque schéma imbriqué doit être un schéma JSON valide et respecter le sous-ensemble pris en charge

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

{
    "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"
    ]
}

Prise en charge des définitions

Les définitions permettent de créer des sous-schémas auxquels vous pouvez faire référence à différents endroits du 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
}

Prise en charge des schémas récursifs

Voici un schéma récursif qui utilise # pour faire référence à sa 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
    }
}

Voici un schéma qui utilise la 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 des sorties structurées. Le mode JSON garantit que le modèle produit du JSON valide, tandis que les sorties structurées garantissent que sa réponse respecte le schéma que vous spécifiez. Nous vous recommandons d’utiliser les sorties structurées si votre cas d’usage le permet.

Lorsque le mode JSON est activé, le modèle produit du JSON valide, sauf dans certains cas limites que votre application doit détecter et traiter.

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

Points importants :

  • En mode JSON, vous devez toujours demander au modèle de produire du JSON dans l’un des messages de la conversation, par exemple dans le message système. Sans instruction explicite, le modèle risque de générer des caractères d’espacement en continu, et la requête peut se poursuivre jusqu’à la limite de tokens. Pour éviter les oublis, l’API renvoie une erreur si la chaîne « JSON » n’apparaît nulle part dans le contexte.
  • Le mode JSON garantit uniquement que la sortie est valide et peut être analysée sans erreur. Pour garantir qu’elle respecte un schéma précis, utilisez les sorties structurées. Si ce n’est pas possible, utilisez une bibliothèque de validation et relancez la requête si nécessaire pour obtenir une sortie conforme au schéma souhaité.
  • Votre application doit détecter et traiter les cas limites dans lesquels le modèle risque de produire un objet JSON incomplet (voir ci-dessous).

Ressources

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