Structured Outputs
Les sorties structurées contraignent la réponse d’un modèle d’IA à respecter un schéma comme JSON Schema pendant le décodage, afin que les applications puissent analyser chaque réponse de façon fiable.
Les sorties structurées sont des réponses de modèle contraintes à respecter un format prédéfini, généralement un schéma JSON qui indique les clés obligatoires, leurs types et les valeurs autorisées, afin que les logiciels puissent analyser chaque réponse sans nettoyage. Un grand modèle de langage (LLM) auquel on demande poliment du JSON s'exécute généralement, mais il peut encore ajouter une phrase de prose, mal orthographier une clé ou s'arrêter au milieu d'un objet, ce qui suffit à casser le programme qui lit la réponse. Les sorties structurées éliminent ce risque grâce au décodage contraint : à chaque étape de la génération, le moteur d'inférence n'autorise que les tokens qui maintiennent la validité de la sortie au regard du schéma. Lorsqu'OpenAI a ajouté cette fonctionnalité à son API en août 2024, l'entreprise a indiqué que gpt-4o-2024-08-06 avec les sorties structurées avait obtenu 100 % à son évaluation du respect de schémas JSON complexes, contre moins de 40 % pour GPT-4 (gpt-4-0613) sans cette fonctionnalité.
Mode JSON et sorties structurées#
Les API des fournisseurs offrent deux niveaux de garantie. Le mode JSON, introduit par OpenAI en novembre 2023, garantit uniquement que la réponse est du JSON syntaxiquement valide ; des clés peuvent donc toujours manquer ou être mal nommées. Les sorties structurées garantissent que la réponse correspond au schéma fourni. Dans l'API Structured Outputs d'OpenAI, les développeurs définissent le schéma sous forme de modèle Pydantic en Python ou d'objet Zod en JavaScript, et un refus est renvoyé dans un champ distinct sans casser le format. Les sorties structurées de Claude d'Anthropic compilent chaque schéma en grammaire, ce qui ajoute de la latence lors de sa première utilisation, puis mettent cette grammaire compilée en cache pendant 24 heures. Les deux fournisseurs ne prennent en charge qu'un sous-ensemble de JSON Schema ; celui d'Anthropic, par exemple, ne prend pas en charge les schémas récursifs ni les limites numériques comme minimum.
Fonctionnement du décodage contraint#
À chaque étape, un LLM produit un score, ou logit, pour chaque token de son vocabulaire, et le décodage choisit dans la distribution softmax qui en résulte. Le décodage contraint attribue d'abord un score égal à moins l'infini à tous les tokens qui enfreindraient le format, ce qui rend leur sélection impossible. Brandon Willard et Rémi Louf ont montré comment effectuer cette opération efficacement dans Efficient Guided Generation for Large Language Models : une expression régulière ou un schéma JSON est compilé en automate à états finis, et un index construit une seule fois sur le vocabulaire répertorie les tokens valides pour chaque état, de sorte que chaque étape ne nécessite qu'une recherche. Leur bibliothèque libre Outlines implémente la méthode, et les moteurs de service comme vLLM proposent la génération guidée par grammaire pour les modèles ouverts.
L'exemple suivant applique manuellement cette idée à GPT-2 depuis Transformers de Hugging Face. Le code écrit le texte fixe du schéma — accolades, clés et guillemets — et, à chaque valeur, le modèle ne peut choisir que parmi les tokens autorisés par le schéma :
import json
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
model = AutoModelForCausalLM.from_pretrained("gpt2")
prompt = "Review: The battery died after two days and support never replied.\nJSON:"
# Décodage glouton sans contrainte : rien n'oblige la réponse à respecter un schéma
ids = tokenizer(prompt, return_tensors="pt").input_ids
free = model.generate(ids, max_new_tokens=16, do_sample=False, pad_token_id=tokenizer.eos_token_id)
print("free:", repr(tokenizer.decode(free[0, ids.shape[1] :])))
def choose(text, options):
"""Return the option GPT-2 scores highest after text, with every other token masked out."""
logits = model(tokenizer(text, return_tensors="pt").input_ids).logits[0, -1]
allowed = torch.tensor([tokenizer.encode(option)[0] for option in options])
masked = torch.full_like(logits, float("-inf"))
masked[allowed] = logits[allowed]
return tokenizer.decode(masked.argmax())
# Décodage contraint : le schéma écrit les clés, et le modèle ne peut choisir que des valeurs autorisées
with torch.no_grad():
text = prompt + ' {"sentiment": "'
text += choose(text, ["positive", "negative"]) + '", "stars": '
text += choose(text, ["1", "2", "3", "4", "5"]) + "}"
print("constrained:", json.loads(text[len(prompt) :]))Il affiche free: ' The battery died after two days and support never replied.\n\nJSON: The' et constrained: {'sentiment': 'positive', 'stars': 1}. Sans contrainte, GPT-2 répète l'avis au lieu d'écrire du JSON, tandis que la version contrainte produit toujours une sortie analysable. Le résultat analysé montre aussi la limite de la technique : le modèle de 124 millions de paramètres a jugé positifs une batterie déchargée et un service client silencieux, en attribuant une étoile. Le schéma a fixé le format, mais les valeurs qu'il contient restent issues du jugement du modèle.
Usages et compromis#
- Extraction de données : Extraire directement des lignes de base de données les noms, dates, totaux et postes de factures, de contrats ou de courriels.
- Appel de fonction : Un appel d'outil est une sortie structurée dont le schéma correspond aux paramètres de l'outil. L'utilisation stricte des outils dans les API d'OpenAI et d'Anthropic applique la même garantie aux arguments des outils.
- Flux de travail agentiques : Représenter les décisions de routage sous forme de valeurs d'énumération et transmettre les résultats intermédiaires entre les étapes d'une chaîne de prompts sous forme d'objets typés.
- Modèles vision-langage : Renvoi des détections sous forme de listes d'étiquettes et de coordonnées de boîtes que le code de dessin ou d'évaluation peut utiliser directement.
L’annotation intelligente guidée par les classes d’Ultralytics Platform dépend de ce dernier type de sortie. Elle demande à des modèles de vision de fournisseurs comme OpenAI, Anthropic et Google de détecter les classes d’un jeu de données, puis ajoute chaque objet renvoyé sous forme de boîte englobante modifiable. Chaque réponse doit donc nommer une classe et fournir des coordonnées dans un format analysable par du code. Ces modèles ne renvoient pas de scores de confiance, alors Platform masque les paramètres de confiance et d’IoU tant qu’un modèle de ce type est sélectionné. Le classement VLM compare la précision de détection d’objets de ces modèles sur des jeux de données du monde réel et classe les boîtes de chaque modèle selon leur ordre d’apparition.
Les formats stricts peuvent réduire la précision dans les tâches de raisonnement. Dans Let Me Speak Freely?, Zhi Rui Tam et ses collègues ont constaté une baisse significative du raisonnement des LLM sous des contraintes de format, les contraintes les plus strictes entraînant des baisses plus importantes. L'API d'OpenAI génère les clés dans l'ordre où elles figurent dans le schéma ; une solution courante consiste donc à placer un champ de raisonnement en texte libre avant le champ de réponse, afin que le modèle écrive sa chaîne de pensée avant de s'engager dans la réponse contrainte.










