Réponse courte
Décrivez la forme exacte que vous voulez sous forme de schéma, envoyez-le avec le mode strict activé, et la réponse aura cette forme à chaque fois, sauf si le modèle refuse ou manque de jetons. Votre code vérifie quand même ces deux cas, puis vérifie les valeurs elles-mêmes, parce qu’une structure parfaite peut contenir un nombre erroné.
Question de suivi Qu’est-ce qui doit encore être validé dans votre code?
Le 6 août, OpenAI a ajouté les Structured Outputs (sorties structurées) à son API, et la question pratique n’est plus « qu’est-ce que c’est? », mais « comment m’en servir sans me brûler? » En bref : vous notez les champs dont vous avez besoin, leurs types et ceux qui sont obligatoires, et l’API oblige le modèle à respecter ce plan. Ce que vous recevez est du JSON (JavaScript Object Notation, le format en texte brut que la plupart des programmes utilisent pour échanger des données) que votre code peut charger sans étape de réparation.
Ce plan s’appelle un JSON Schema, une façon standard de décrire à quoi ressemble un morceau de JSON valide. La suite de ce guide passe par une vraie tâche, extraire les détails clés d’une facture de fournisseur, du schéma jusqu’aux vérifications de sécurité.
Étape 1 : décider des champs avant d’écrire du code
Commencez sur papier. Pour une facture, un petit outil de tenue de livres pourrait avoir besoin de ceci :
| Champ | Type | Pourquoi c’est important |
|---|---|---|
| vendor_name | texte | Qui payer |
| invoice_number | texte | Évite de payer deux fois |
| invoice_date | texte, écrit sous la forme AAAA-MM-JJ | Le tri et les échéances |
| currency | texte, un code de trois lettres comme CAD | Les montants ne veulent rien dire sans lui |
| line_items | liste de description, quantité, prix unitaire | Vérifier le total |
| total | nombre | Ce que le fournisseur affirme que vous lui devez |
Restez modeste. Chaque champ de plus est une chose de plus que le modèle doit remplir et une chose de plus que vous devez vérifier. Vous pourrez ajouter des champs une fois que la première version fonctionnera.
Étape 2 : activer le schéma en mode strict
Il y a deux façons d’activer la fonction. Si votre application utilise déjà l’appel de fonctions, ajoutez strict: true à chaque définition de fonction. Si vous voulez simplement que la réponse elle-même ait une forme fixe, passez votre schéma par response_format avec le type json_schema. Cette deuxième forme fonctionne avec les instantanés les plus récents, gpt-4o-2024-08-06 et gpt-4o-mini, et c’est celle que nous utilisons ici.
La bibliothèque Python mise à jour d’OpenAI peut construire le schéma pour vous à partir d’une classe Pydantic (Pydantic est un paquet Python populaire pour décrire des données à l’aide d’annotations de type). Cette ébauche utilise l’outil parse de la bibliothèque, qui se trouve sous client.beta :
import openai
from openai import OpenAI
from pydantic import BaseModel
class LineItem(BaseModel):
description: str
quantity: float
unit_price: float
class Invoice(BaseModel):
vendor_name: str
invoice_number: str
invoice_date: str
currency: str
line_items: list[LineItem]
total: float
client = OpenAI() # lit OPENAI_API_KEY dans votre environnement
invoice_text = open("invoice.txt").read()
try:
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": (
"Extrais les détails de la facture. Recopie les noms et les nombres "
"exactement comme ils sont écrits. Écris les dates sous la forme AAAA-MM-JJ. "
"Si une valeur manque, utilise une chaîne vide pour le texte et 0 pour "
"les nombres. Ne devine jamais.")},
{"role": "user", "content": invoice_text},
],
response_format=Invoice,
max_tokens=1000,
)
except openai.LengthFinishReasonError:
raise SystemExit("Réponse coupée : augmentez max_tokens ou raccourcissez l’entrée.")
message = completion.choices[0].message
if message.refusal:
raise SystemExit("Le modèle a refusé : " + message.refusal)
invoice = message.parsed # un objet Invoice, pas du texte brut
print(invoice.vendor_name, invoice.total)
Si vous préférez envoyer du JSON brut plutôt que d’utiliser Pydantic, les exemples publiés suivent deux habitudes qui valent la peine d’être copiées : chaque propriété est énumérée sous required, et chaque objet fixe "additionalProperties": false, pour que le modèle ne puisse pas inventer de clés supplémentaires. Notez que seule une partie de la norme JSON Schema est prise en charge; des règles très élaborées peuvent donc être rejetées.
Attendez-vous à ce que le premier appel avec un nouveau schéma soit lent. OpenAI prépare et met en cache chaque schéma à sa première utilisation, ce qui prend en général moins de dix secondes et peut prendre jusqu’à une minute pour les schémas complexes. Les appels suivants s’exécutent à vitesse normale.
À essayer : collez le texte d’un vrai reçu ou d’une vraie facture dans un fichier, lancez le script ci-dessus, puis modifiez le reçu pour retirer la date et relancez-le. Vérifiez si vous obtenez une chaîne vide, comme demandé, ou une date inventée.
Étape 3 : traiter les deux exceptions comme des événements normaux
OpenAI décrit la garantie de format avec deux conditions, et le code ci-dessus vérifie les deux. D’abord, le modèle peut refuser une demande qu’il juge dangereuse; la réponse contient alors un message refusal au lieu de vos données. Ensuite, si la réponse atteint la limite max_tokens ou une autre condition d’arrêt, elle s’interrompt au milieu de l’objet et ne correspondra pas au schéma. L’API indique alors un finish_reason de length plutôt que stop, et l’outil parse lève une erreur au lieu de vous remettre une demi-facture. Aucun de ces deux cas n’est assez rare pour être ignoré dans un programme qui tourne tous les jours.
Deux autres limites pratiques : les Structured Outputs ne fonctionnent pas avec les appels de fonctions en parallèle, alors réglez parallel_tool_calls à false si vous utilisez des outils; et la fonction est offerte dans les API Chat Completions, Assistants et Batch, y compris pour les requêtes avec images. Une photo de facture peut passer par le même schéma.
Qu’est-ce qui doit encore être validé dans votre code?
Les valeurs. Le mode strict garantit que total est un nombre; il ne dit rien sur le fait que ce soit le bon nombre. OpenAI affirme clairement que le modèle peut encore se tromper à l’intérieur du JSON; les vérifications qui comptent maintenant portent donc sur le sens, pas sur la syntaxe. Pour la facture, un court ensemble de règles attrape la plupart des problèmes :
- L’arithmétique. Multipliez la quantité par le prix unitaire pour chaque ligne, additionnez et comparez avec
total. Tenez compte des taxes et de l’arrondi, mais signalez un écart important. - Les formats. Essayez de lire
invoice_datecomme une vraie date du calendrier, et comparezcurrencyà la courte liste de codes avec lesquels vous travaillez réellement. - Les valeurs vides. Un
invoice_numbervide est permis par le schéma, mais ne vous sert à rien. Décidez quels champs vides envoient la facture à une personne. - Les doublons et le contexte. Cherchez le fournisseur et le numéro de facture dans vos propres dossiers avant de payer quoi que ce soit.
Quand une vérification échoue, ne relancez pas la requête en silence jusqu’à ce que les chiffres aient l’air justes. Envoyez le document dans une file de révision humaine, avec les valeurs extraites à côté de l’original, et tenez un journal des champs qui échouent le plus souvent. Si un champ continue de poser problème, OpenAI suggère elle-même d’ajouter des exemples détaillés aux consignes ou de diviser la tâche en étapes plus petites, par exemple en extrayant les lignes de la facture dans un appel séparé. Le schéma vous donne des données propres à vérifier; la vérification, elle, reste votre travail.
Sources
- Introducing Structured Outputs in the API — OpenAI, 6 août 2024
- OpenAI has finally released the No. 1 feature developers have been desperate for — VentureBeat (Taryn Plumb), 6 août 2024