
Sorties structurées avec LLM : mode JSON, appel de fonction et quand les utiliser
nous avons beaucoup parlé des techniques populaires permettant d’optimiser les performances et le coût des applications d’IA, comme diffusion en continu des réponses ou mise en cache rapide. Aujourd’hui, je veux parler de quelque chose d’un peu différent mais tout aussi important pour créer de véritables applications d’IA. C’est, sorties structurées et lisibles par machine.
Jusqu’à présent, dans la plupart des exemples que j’ai partagés, nous avons eu affaire à des réponses en texte libre provenant d’un modèle d’IA. L’utilisateur pose une question, le modèle répond en langage naturel et nous affichons simplement cette réponse à l’utilisateur d’une manière ou d’une autre. Assez simple et direct. Mais que se passe-t-il lorsque nous avons besoin que le modèle renvoie des données dans un format spécifique (par exemple, un objet JSON) afin de pouvoir les traiter ultérieurement par programme ? Que se passe-t-il si nous avons besoin du modèle pour extraire des champs spécifiques d’un texte ou d’une image, remplir une entrée de base de données ou déclencher une action ultérieure en fonction de sa réponse ? Dans ces cas-là, récupérer un mur de texte ne sera pas très pratique. 🤔
Heureusement, il existe plusieurs solutions à ce problème. Il existe deux approches principales pour obtenir des résultats structurés et lisibles par machine à partir d’un LLM : Mode JSON et Appel de fonction (également appelé utilisation d’outils). Ces deux éléments sont souvent confondus (ce qui est normal puisqu’ils traitent tous deux de résultats structurés, duh), mais ils servent des objectifs très différents. En plus de cela, OpenAI a introduit une variante plus stricte de l’appel de fonction appelée Résultats structurésqui va encore plus loin dans l’application du schéma, comme nous le verrons. Dans cet article, nous examinerons de plus près les trois, comprendrons comment chacun fonctionne sous le capot et déterminerons quand les utiliser.
Alors, jetons un coup d’œil !
1. Qu’est-ce que le mode JSON ?
Mode JSON est l’approche la plus simple pour obtenir des résultats lisibles par machine à partir d’un LLM. Il s’agit essentiellement d’un paramètre que vous pouvez définir dans une requête API pour demander au modèle de toujours renvoie un objet JSON valide. Et c’est vraiment tout ce qu’il y a à faire ! Néanmoins, cette simplicité a un coût, car il n’y a aucune garantie sur la structure ou le schéma du JSON (rappelez-vous que nous n’avons défini aucun schéma, nom de champ ou type, ou quoi que ce soit de ce genre), juste qu’il sera valide et analysable.
Par exemple, en utilisant l’API d’OpenAI en Python, nous pouvons activer le mode JSON en ajoutant le paramètre response_format={"type": "json_object"} à notre appel au modèle. Plus précisément, cela ressemblerait à ceci :
from openai import OpenAI
client = OpenAI(api_key="your_api_key")
response = client.chat.completions.create(
model="gpt-4o-mini",
response_format={"type": "json_object"},
messages=[
{
"role": "system",
"content": "You are a helpful assistant. Always respond in JSON format."
},
{
"role": "user",
"content": "Extract the name, age, and city from this text: 'Maria is 32 years old and lives in Athens.'"
}
]
)
print(response.choices[0].message.content)
Et la réponse ressemblerait à ceci :
{
"name": "Maria",
"age": 32,
"city": "Athens"
}
Et voilà ! ✨ Avec un simple changement de paramètre, nous obtenons à chaque fois un JSON valide. Pas besoin d’analyse de chaînes ou de hacks d’expressions régulières étranges.
Il y a cependant un piège. Le mode JSON garantit que la sortie est JSON validemais c’est le cas pas garantir une structure spécifique. Si nous exécutons le même exemple plusieurs fois, nous pouvons obtenir à chaque fois des noms de champs légèrement différents ou une structure légèrement différente. Par exemple, une exécution peut renvoyer "name" et un autre "full_name". C’est un problème si nous essayons d’extraire de manière fiable des champs spécifiques par programme.
Une autre chose est qu’au-delà du réglage response_format={"type": "json_object"}c’est une bonne pratique de toujours demander explicitement au modèle de répondre en JSON dans l’invite système. Dans l’exemple ci-dessus, remarquez comment nous avons également ajouté « Toujours répondre au format JSON » dans l’invite du système. Sans cela, le modèle peut parfois renvoyer un JSON valide, mais pas toujours, car son comportement peut devenir imprévisible.
2. Qu’est-ce que l’appel de fonction ?
Appel de fonction (ou l’utilisation d’outils) est une approche plus avancée pour obtenir des résultats structurés et lisibles par machine à partir d’un LLM. Au lieu de simplement demander au modèle de formater sa réponse au format JSON, nous définissons un schéma. Autrement dit, nous définissons explicitement une description formelle de la structure que nous voulons que la sortie suive, et de cette manière, le modèle est plus contraint à renvoyer des données qui correspondent exactement à ce schéma. En d’autres termes, avec Function Calling, nous définissons à l’avance quels champs nous attendons, quels types ces champs doivent être, lesquels sont obligatoires et lesquels ne le sont pas, et ainsi de suite.
Voici à quoi ressemblerait le même exemple d’extraction en utilisant l’appel de fonction :
from openai import OpenAI
import json
client = OpenAI(api_key="your_api_key")
# define the schema of the output we expect
tools = [
{
"type": "function",
"function": {
"name": "extract_person_info",
"description": "Extract personal information from a text",
"parameters": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The full name of the person"
},
"age": {
"type": "integer",
"description": "The age of the person"
},
"city": {
"type": "string",
"description": "The city the person lives in"
}
},
"required": ["name", "age", "city"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
tools=tools,
tool_choice={"type": "function", "function": {"name": "extract_person_info"}},
messages=[
{
"role": "user",
"content": "Extract the name, age, and city from this text: 'Maria is 32 years old and lives in Athens.'"
}
]
)
# parse the structured output
tool_call = response.choices[0].message.tool_calls[0]
result = json.loads(tool_call.function.arguments)
print(result)
Et le résultat ressemblerait à ceci :
{
"name": "Maria",
"age": 32,
"city": "Athens"
}
Le résultat de cet exemple avec Function Calling est identique à celui que nous avons obtenu en utilisant le mode JSON. Néanmoins, la principale différence est que, contrairement au mode JSON, avec l’appel de fonction, la sortie sera cohérente ; ça va toujours suivez le schéma exact défini, avec des noms de champs, des types et tous les autres attributs cohérents que nous y définissons.
🍨 Crème de données est une newsletter proposant des histoires et des tutoriels sur l’IA, les données et la technologie. Si ces sujets vous intéressent, abonnez-vous ici!
Bonus : un peu plus sur les appels de fonctions
Avant de passer aux sorties structurées, il vaut la peine de s’arrêter et d’élaborer davantage sur la motivation et l’utilisation originales derrière l’appel de fonction, qui va bien au-delà de la simple obtention de sorties structurées. Essentiellement, le concept de Function Calling est le fondement des flux de travail d’IA agentique. Plus précisément, dans une configuration agentique, le LLM est je ne me contente pas de répondre à la question d’un utilisateur, mais c’est plutôt décider quelle action entreprendre ensuite en fonction de la saisie de l’utilisateur.
Par exemple, imaginons un assistant de support client qui peut soit rechercher une commande, émettre un remboursement ou transmettre la demande à un agent humain, en fonction de la demande de l’utilisateur. Avec l’appel de fonction, nous pouvons définir ces trois actions candidates comme des « outils » (fonctions), et la sortie du modèle définira laquelle appeler et avec quels arguments en fonction de son entrée.
tools = [
{
"type": "function",
"function": {
"name": "lookup_order",
"description": "Look up the status of a customer order",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string", "description": "The order ID"}
},
"required": ["order_id"]
}
}
},
{
"type": "function",
"function": {
"name": "issue_refund",
"description": "Issue a refund for a customer order",
"parameters": {
"type": "object",
"properties": {
"order_id": {"type": "string"},
"reason": {"type": "string"}
},
"required": ["order_id", "reason"]
}
}
}
]
response = client.chat.completions.create(
model="gpt-4o-mini",
tools=tools,
messages=[
{"role": "user", "content": "I want a refund for order #12345, it arrived broken."}
]
)
tool_call = response.choices[0].message.tool_calls[0]
print(tool_call.function.name) # "issue_refund"
print(tool_call.function.arguments) # '{"order_id": "12345", "reason": "arrived broken"}'
Ainsi, l’objet de réponse de l’API ressemble à ceci :
ChatCompletionMessage(
content=None,
role='assistant',
tool_calls=[
ChatCompletionMessageToolCall(
id='call_abc123',
type='function',
function=Function(
name='issue_refund',
arguments='{"order_id": "12345", "reason": "arrived broken"}'
)
)
]
)
Et les instructions print produiraient hypothétiquement :
issue_refund
{"order_id": "12345", "reason": "arrived broken"}
Alors, que se passe-t-il ici ? Le modèle renvoie un tool_calls objet au lieu d’une réponse textuelle normale (découvrez commentcontent est None). À l’intérieur du tool_calls objet, on voit que le modèle a décidé d’appeler issue_refund (pas lookup_order), et remplit lui-même les arguments en fonction de ce que l’utilisateur a dit. Nous analysons ensuite ces arguments et exécutons la logique de remboursement réelle dans notre système.
Remarquez comment le modèle n’a pas simplement renvoyé les données demandées, mais plutôt décidé laquelle des actions candidates est la plus appropriée à réaliser, puis remplit les arguments appropriés dans sa réponse. De cette façon, nous pouvons ensuite prendre ces arguments et exécuter réellement l’action correspondante dans notre système. C’est le véritable pouvoir de l’appel de fonctions, et c’est pourquoi il s’agit d’un composant si fondamental dans les applications d’IA agentique.
Mais revenons maintenant aux sorties lisibles par machine, et nous parlerons davantage des flux de travail d’IA agentique et des appels de fonctions dans un autre article.
3. Qu’en est-il des résultats structurés ?
Une variante plus stricte de l’appel de fonction est Résultats structurés. Même si l’appel de fonction guide le modèle pour fournir une sortie suivant un schéma défini, ce n’est pas le cas. vraiment fortement contraint. En pratique, cela signifie que certains écarts par rapport à ce schéma défini peuvent encore survenir. De tels écarts peuvent être :
- Un champ marqué comme obligatoire qui est en fait omis si le modèle a du mal à déterminer sa valeur
- Des champs supplémentaires non définis dans notre schéma sont ajoutés
- Un champ défini comme
integerrevient sous forme de chaîne"32"au lieu de32
…et ainsi de suite.
Cela se produit parce que, dans Function Calling, le modèle est en essayant suivre le schéma, mais il s’agit toujours d’une génération au mieux. Comme toute sortie LLM, la sortie ici est toujours fondamentalement des jetons prédits un par un, le schéma n’étant qu’un indice fort. Il y a encore de bonnes chances que cette génération jeton par jeton déraille quelque part le long du parcours et produise des résultats qui s’écartent du schéma défini.
Les sorties structurées, quant à elles, vont encore plus loin dans l’appel de fonction en garantissant que chaque champ du schéma défini apparaîtra toujours dans la sortie exactement comme défini, sans surprises, sans champs manquants ou supplémentaires. Le différenciateur clé est qu’OpenAI utilise décodage contraint dans les coulisses. Cela signifie qu’à chaque étape de jeton, le modèle est uniquement autorisé à générer des jetons qui maintiennent la sortie valide selon le schéma. En d’autres termes, le schéma est appliqué au niveau de la génération, au lieu d’être simplement demandé via l’invite système.
Les sorties structurées d’OpenAI peuvent être activées en définissant simplement strict: true dans la définition de la fonction :
tools = [
{
"type": "function",
"function": {
"name": "extract_person_info",
"strict": True, # enables Structured Outputs
"parameters": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"city": {"type": "string"}
},
"required": ["name", "age", "city"],
"additionalProperties": False
}
}
}
]
Mais encore une fois, cela a un coût. Les sorties structurées sont disponibles sur les modèles GPT-4o et ultérieurs, les anciens modèles revenant au mode JSON. Toutes les structures JSON ne sont pas prises en chargeet cela peut être un peu plus lent puisque OpenAI prétraite les résultats.
Néanmoins, il s’agit du moyen le plus strict et le plus sûr d’appliquer un schéma spécifique pour les sorties du modèle, sans possibilité d’écart. Pour les systèmes de production où la fiabilité et la cohérence comptent vraiment, il s’agit généralement de l’option la plus sûre.
Mais n’est-ce pas tout à fait la même chose ?
Le mode JSON, l’appel de fonction et les sorties structurées peuvent sembler faire la même chose, car ils vous récupèrent tous essentiellement le JSON du modèle. Néanmoins, comme nous l’avons déjà vu, ils diffèrent considérablement dans ce qu’ils garantissent et dans ce pour quoi ils sont conçus. En particulier:
- Application du schéma: Le mode JSON renvoie un JSON valide, mais sans garantie structurelle. L’appel de fonction renvoie un JSON valide qui correspond à un schéma défini, en suivant les noms de champs, les types et les champs obligatoires spécifiques, mais des écarts sont toujours possibles. Structured Outputs va encore plus loin, en appliquant ce schéma au niveau de la génération, rendant les écarts impossibles.
- Cas d’utilisation: Le mode JSON est destiné aux cas où nous avons besoin d’une réponse lisible par machine mais pouvons vivre avec un format variable. L’appel de fonction a été principalement conçu pour les cas où le modèle doit déclencher une action ou transmettre des arguments à un outil externe, c’est donc essentiellement le cas général des sorties lisibles par machine. Les sorties structurées sont des appels de fonctions avec une garantie de fiabilité, ce qui les rend idéales pour les pipelines de production où nous avons besoin de cohérence dans les sorties.
- Facilité de configuration : Le mode JSON est l’option la plus légère à configurer ; juste un seul changement de paramètre sans définition de schéma. D’un autre côté, pour les appels de fonctions et les sorties structurées, nous devons également réfléchir et configurer le schéma JSON.
Cela dit, OpenAI lui-même recommande de toujours utiliser les sorties structurées au lieu du mode JSON autant que possibleen règle générale.

Dans mon esprit
Obtenir des résultats lisibles par machine à partir des LLM et choisir l’approche appropriée pour y parvenir peut faire une énorme différence dans la fiabilité et la maintenabilité de toute application d’IA. Les réponses en texte libre sont idéales pour les interfaces conversationnelles, mais dès que notre LLM est un composant d’un système plus vaste (comme alimenter les données en aval, déclencher des actions, remplir des bases de données, etc.), les réponses structurées sont essentielles. Le mode JSON, l’appel de fonction et les sorties structurées peuvent fournir de telles sorties, chacune à un niveau de rigueur différent. Comme pour de nombreuses décisions en matière d’ingénierie de l’IA, le bon choix dépend de ce que vous construisez et du degré de variabilité que vous pouvez tolérer.
Si tu es arrivé jusqu’ici, les pialgorithmes pourraient vous être utiles — une plateforme que nous avons créée qui aide les équipes à gérer en toute sécurité les connaissances organisationnelles en un seul endroit.
Vous avez adoré cet article ? Rejoignez-moi sur 💌Sous-pile et 💼LinkedIn
Toutes les images sont de l’auteur, sauf mention contraire.



