Définir un schéma
- Leçon 8
- 50 min
- Module 3 : Sorties structurées et validation
Leçon 8 sur 19 · Créer des applications fiables avec les API de LLMDéfinir un schéma
1. Commencer par une seule requête au modèle
2. La rédaction de requêtes, une tâche d'ingénierie
3. Sorties structurées et validation
4. Appeler des outils en toute sécurité
5. Conversations et diffusion en continu
6. Contrôles de fiabilité et de coût
Ce que vous saurez faire
Transformez la réponse de l’assistant en contrat que le code peut vérifier. Ainsi, un nom d’équipe incorrect, un champ manquant ou un identifiant de commande mal formaté ne passe jamais plus loin. Le service de soutien de TermeLarkfieldUne boutique en ligne d’articles pour la maison. Ce n’est pas une vraie entreprise. Tous les cours Nextia utilisent son service d’assistance et ses données comme exemple.En savoir plus L'IA dans les produits de tous les joursBases d'une carrière en IA : rôles, compétences et feuilles de route les bloque.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Le contrat de sortie de la requête, dans Préciser la tâche, et la commande
eval, dans Comparer des requêtes. finish_reason(raison de fin) et la limite de jetonsmax_completion_tokens, dans Les éléments d’une requête.- Les annotations de type et les classes de données, dans Les enregistrements structurés du cours Python pour l’ingénierie de l’IA en pratique. Le JSON en Python, dans Lire et écrire des fichiers : CSV et JSON. Les tests avec pytest, dans Des tests ciblés.
Ce qu’il vous faut sur votre ordinateur
- Le projet
ticket-assistantà la fin du module 2, dans~/projects/ticket-assistant, avec son environnement.venvactif.python -m pytestaffiche21 passed, 1 deselected(21 tests réussis, 1 désélectionné). - Si votre projet ne fonctionne plus ou manque, copiez l’instantané
end-of-m02en suivant Réinitialiser le projet. - Le dépôt des exercices dans
~/projects/nextia-learning-labs, présenté dans Un premier essai sans compte. L’étape 1 en copie de nouvelles réponses enregistrées.
Aucun compte ni aucune clé : chaque réponse du modèle vient du fournisseur simulé. Testé avec Python 3.12.13 et Pydantic 2.14.0 sur macOS (Apple silicon). Les commandes Windows ne sont pas testées.
Les modèles et les API changent vite. Les chiffres de ce module représentent un instantané. La méthode, elle, reste.
Un formulaire avec des cases
Section intitulée « Un formulaire avec des cases »Imaginez un formulaire au comptoir d’un bureau de poste. Chaque élément a sa case. « Pays » propose une liste : cochez un choix. « Date de naissance » comporte huit petits carrés, JJ MM AAAA. Certaines cases portent une étoile : vous devez les remplir. Le commis vérifie un formulaire en quelques secondes, car tous les formulaires ont la même structure.
Imaginez maintenant que le bureau de poste accepte plutôt des lettres : « Bonjour, je voudrais envoyer ceci à ma tante à Lyon… ». Chaque lettre le dit différemment. Le commis doit toutes les lire et deviner.
L’assistant de Larkfield est un programme. Le code qui lit ses réponses joue le rôle du commis.
Le problème : du texte qui ressemble à des données
Section intitulée « Le problème : du texte qui ressemble à des données »Dans le module 2, la requête demandait « un seul objet JSON, sans autre texte ». Le code essayait ensuite json.loads sur la réponse. La requête demandait. Rien ne vérifiait. Voici la réponse de TermeGemma 3 4BUn petit modèle ouvert de Google, avec 4 milliards de paramètres. Il peut fonctionner sur un portable avec Ollama, sans compte ni coût par appel.En savoir plus Modèle hébergé ou autohébergéLes LLM : comment ils fonctionnent et comment en choisir un, le modèle local gratuit, au billet de soutien de démonstration du cours. Elle a été enregistrée avec TermeOllamaUn programme gratuit qui télécharge des modèles ouverts et les fait fonctionner sur votre ordinateur. Votre code l’appelle à une adresse locale, comme une API hébergée.En savoir plus Modèle hébergé ou autohébergéLes LLM : comment ils fonctionnent et comment en choisir un (version abrégée) :
```json{ "language": "en", "team": "delivery", "needs_human": true, "reason": "The customer is inquiring about a delayed delivery and needs investigation, requiring human intervention to track the order.", ...}```(not JSON)Le JSON à l’intérieur est correct. Mais le modèle l’a entouré de délimiteurs TermeMarkdownUne façon de noter la mise en forme en texte simple, par exemple **gras** ou # pour un titre. Les modèles l’ajoutent souvent : un programme qui attend du JSON simple doit l’enlever ou refuser la réponse.. Ce sont les trois accents graves et le mot json que les applications de clavardage utilisent pour afficher du code. json.loads échoue donc dès le premier caractère. Sans sortie structurée, Gemma 3 4B a fait cela pour 138 réponses sur 138, avec les deux versions de requête.
Le TermeModèle hébergéUn modèle qui fonctionne sur les ordinateurs d’un fournisseur. Votre code lui envoie des requêtes par Internet, et vous payez chaque appel.En savoir plus Modèle hébergé ou autohébergéLes LLM : comment ils fonctionnent et comment en choisir un produit du texte plus propre, mais il peut quand même enfreindre le contrat. Le billet T-80009 dit « Ma commande LK-55190 n’est jamais arrivée ». L’identifiant a cinq chiffres, mais les identifiants de commande de Larkfield en ont six. gpt-6-luna (chat-small sur Azure) l’a copié tel quel :
"order": { "order_id": "LK-55190", "status": null},Le code du module 2 a accepté cette réponse telle quelle. Aucune ligne de code ne connaissait les règles. Un identifiant de commande doit comporter LK- et six chiffres. team (équipe) doit être l’un des cinq noms, et aucun champ ne peut manquer. Trois problèmes peuvent survenir avec du texte qui ressemble à des données :
- Ce n’est pas du JSON.
- C’est du JSON, mais un champ manque ou un champ supplémentaire apparaît.
- Tous les champs sont présents, mais une valeur est interdite : une sixième équipe, le texte
"maybe"pourneeds_human(intervention humaine nécessaire), un identifiant de commande à cinq chiffres.
L’idée : un schéma
Section intitulée « L’idée : un schéma »Le service de soutien a besoin du formulaire à cases. Il lui faut une description écrite de la structure de la réponse, que le code peut vérifier. C’est un schéma. Le mot vient du grec skhēma, « forme » ou « structure ». Un schéma indique :
- quels champs la réponse contient et lesquels sont obligatoires;
- le type de chaque valeur : texte (une string, ou chaîne de caractères), nombre, vrai ou faux (un boolean, ou booléen), objet, liste ou
null; - les valeurs permises : une liste fixe de noms est une énumération (enum, abréviation d’enumeration). Le texte peut aussi devoir respecter un motif;
- les objets imbriqués : un objet dans la réponse, avec ses propres champs.
JSON Schema est un format standard pour décrire ainsi des données JSON. Beaucoup d’outils le lisent, tout comme les fournisseurs de modèles, comme vous le verrez plus bas.
La validation est la vérification elle-même : cette réponse respecte-t-elle le schéma? Elle donne un oui clair, ou un non avec sa raison.
Un petit exemple : cinq réponses, un petit schéma
Section intitulée « Un petit exemple : cinq réponses, un petit schéma »Python propose une bibliothèque populaire pour cela, Pydantic. Vous écrivez une classe avec des annotations de type, comme la classe de données écrite dans le cours de Python. Pydantic vérifie ensuite les données selon ces annotations et indique exactement ce qui ne va pas. Il figure déjà dans le fichier requirements.txt du projet.
Voici un schéma à deux champs et cinq réponses à vérifier. Ces réponses sont écrites à la main pour l’exemple. Ce ne sont pas des sorties du modèle :
from typing import Literalfrom pydantic import BaseModel, ValidationError
class Mini(BaseModel): team: Literal["delivery", "returns", "payment", "warranty", "account"] needs_human: bool
answers = [ '{"team": "delivery", "needs_human": false}', '{"team": "shipping", "needs_human": false}', '{"team": "delivery"}', '{"team": "delivery", "needs_human": "maybe"}', '```json\n{"team": "delivery", "needs_human": false}\n```',]for text in answers: try: print("pass", Mini.model_validate_json(text)) except ValidationError as error: first = error.errors()[0] print("fail", first["loc"], first["msg"])La sortie :
pass team='delivery' needs_human=Falsefail ('team',) Input should be 'delivery', 'returns', 'payment', 'warranty' or 'account'fail ('needs_human',) Field requiredfail ('needs_human',) Input should be a valid boolean, unable to interpret inputfail () Invalid JSON: expected value at line 1 column 1Literal[...] définit l’énumération : seuls ces cinq textes sont permis. bool est le type. Un champ sans valeur par défaut est obligatoire. model_validate_json lit le texte JSON et le vérifie en une seule étape. Chaque échec indique le champ (loc, l’emplacement) et la raison. La dernière ligne contient les délimiteurs Markdown. Ce n’est pas du JSON, donc Pydantic s’arrête avant d’examiner les champs.
Le schéma de Larkfield
Section intitulée « Le schéma de Larkfield »La vraie réponse a sept champs, dont un objet imbriqué. L’image place une vraie réponse à côté des règles du schéma pour chaque champ.
Description textuelle du diagramme
À gauche, la réponse de chat-small au billet T-80008. À droite, les règles du schéma pour chaque champ. language (langue) : une énumération, « en » ou « fr ». team : une énumération, l’une des cinq équipes. needs_human : un booléen, vrai ou faux. reason (raison) : une chaîne de 300 caractères au maximum. confidence (confiance) : une énumération, « low » (faible), « medium » (moyenne) ou « high » (élevée). order (commande) : un objet imbriqué ou null. À l’intérieur, order_id (identifiant de commande) est une chaîne qui doit respecter le motif ^LK-\d{6}$. status (statut) est l’un des sept statuts de commande ou null. reply (réponse) : une chaîne de 1 à 1 200 caractères. En dessous : les sept champs sont obligatoires et aucun autre champ n’est permis (additionalProperties: false).
^LK-\d{6}$ est une expression régulière. ^ marque le début du texte, \d{6} signifie exactement six chiffres et $ marque la fin. Ainsi, LK-581106 correspond au motif, mais pas LK-55190.
Trois choix demandent une explication :
orderest obligatoire, mais peut êtrenull. Un billet sans identifiant de commande reçoit"order": null. Une clé absente ne dirait rien : le modèle a-t-il oublié le champ, ou n’y a-t-il aucune commande? Le schéma ne laisse pas le modèle maintenir cette ambiguïté.- Aucun autre champ n’est permis. Un champ supplémentaire, par exemple
"refund_approved": true, est une erreur, pas un élément à ignorer. statusvient uniquement d’une recherche. Le texte du billet peut affirmer n’importe quoi. La description du schéma demande donc au modèle de laisser ce champ ànull. Le module 4 ajoute la recherche.
Sortie structurée : envoyer le schéma avec la requête
Section intitulée « Sortie structurée : envoyer le schéma avec la requête »Pydantic vérifie une réponse après que le modèle l’a écrite. Beaucoup de fournisseurs peuvent aussi utiliser le schéma pendant que le modèle écrit. Vous envoyez le schéma dans la requête, dans le champ response_format (format de réponse). C’est une sortie structurée.
Comment un fournisseur peut-il imposer une structure? Le modèle écrit un jeton à la fois, choisi dans une liste de probabilités. Vous l’avez vu dans Prédire la suite, du cours Les LLM : comment ils fonctionnent et comment en choisir un. Avec la sortie structurée, le fournisseur retire chaque jeton qui enfreindrait le schéma avant le choix. Après "team": ", il ne reste que le début de l’un des cinq noms d’équipe. Le modèle ne peut pas écrire shipping (expédition), car aucun chemin dans le schéma ne le permet.
Le mode strict ("strict": true) demande au fournisseur de respecter exactement le schéma. En échange, le fournisseur impose des règles au schéma lui-même. La principale est : chaque champ doit être obligatoire.
TermeAmiraLa développeuse de Larkfield. Elle écrit le code des outils du service d’assistance dans les cours. a rencontré cette règle en pratique. Elle a donné une valeur par défaut au champ order. Pydantic l’a donc exclu de la liste des champs obligatoires. gpt-6-luna sur Azure a refusé la requête avant l’exécution du modèle :
HTTP 400: Invalid schema for response_format 'ticket_analysis': In context=(), 'required' is required to be supplied and to be an array including every key in properties. Missing 'order'.Ollama 0.40.1 a accepté la même requête destinée à Gemma 3 4B avec HTTP 200, et le modèle a répondu. Un fournisseur vérifie le schéma et le refuse; un autre l’accepte sans rien dire. Les fournisseurs prennent en charge différentes parties de JSON Schema. Lisez donc la liste de votre fournisseur avant de concevoir un schéma.
La sortie structurée a aussi un coût. Le schéma accompagne chaque requête. Pour la requête v2, l’entrée TermeMédianeLa valeur du milieu quand on trie les valeurs. La moitié est plus basse, l’autre plus haute. Un seul appel très lent la change peu : c’est un bon temps « habituel ».En savoir plus Mesurer la performance en pratiqueLes LLM : comment ils fonctionnent et comment en choisir un est passée de 1 662 à 1 904 jetons par billet sur Termechat-smallLe nom que le code de Larkfield donne à un petit modèle hébergé peu coûteux : gpt-6-luna sur Azure. Le code utilise ce nom, donc le modèle peut changer sans modifier le code.En savoir plus Ce qu'est une API de LLMCréer des applications fiables avec les API de LLM. Cela représente un passage de 0,32 $ US à 0,35 $ US pour 1 000 billets au tarif lors de l’enregistrement. Vérifiez la page des tarifs actuels.
Qu’est-ce que cela a changé? Le graphique compte les réponses qui sont du JSON complet et respectent le schéma. Il utilise les mêmes 69 billets et la requête v2.
- text : la requête demande du JSON
- schema : sortie structurée dans la requête
Description textuelle du graphique
Quatre barres. Gemma 3 4B sans sortie structurée : 0 réponse sur 69 respecte le schéma, car chaque réponse est entourée de délimiteurs Markdown. Gemma 3 4B avec sortie structurée : 67 sur 69. chat-small sans sortie structurée : 67 sur 69. Une réponse a été coupée à la limite de jetons. Une autre a copié l’identifiant de commande à cinq chiffres. chat-small avec sortie structurée : 69 sur 69.
Gemma est passé de 0 à 67. chat-small est passé de 67 à 69. Mais la barre de Gemma n’atteint pas 69, même avec le schéma. La section suivante montre pourquoi.
Valider quand même
Section intitulée « Valider quand même »Voici les deux réponses de Gemma qui ont enfreint le schéma avec la sortie structurée. Pour le billet T-64722, qui ne mentionne aucune commande, il a écrit :
"order": {"order_id": "null", "status": null}L’identifiant de commande est le texte "null", entre guillemets. Pour T-80009, il a écrit "order_id": "LK-55190", encore cinq chiffres. Les deux enfreignent le motif ^LK-\d{6}$. Ollama n’a pas appliqué le mot-clé pattern (motif) du schéma. Il a conservé la structure : les noms des champs, les types et les énumérations. Il a ignoré le motif sans le signaler.
La règle de ce module est donc simple : validez chaque réponse dans votre propre code, même si le fournisseur promet une structure. La vérification du fournisseur est une aide. Votre vérification est la garantie. Elle coûte des microsecondes, et le code reste le même pour chaque fournisseur.
Le modèle hébergé montre l’autre côté du problème. Avec la sortie structurée stricte, chat-small ne pouvait pas écrire cinq chiffres pour T-80009. Il en a donc écrit six et a inventé le dernier : LK-551902. Cet identifiant respecte le schéma, mais il est faux. La leçon Valider le sens le détecte.
Cette image montre où vous allez dans ce module. La vérification du schéma est la première barrière. La vérification du sens est la deuxième. Seule une réponse qui franchit les deux entre dans la base de données.
Description textuelle du diagramme
De gauche à droite : un billet du service de soutien, puis la génération, où le modèle écrit une réponse. Barrière 1, la structure : la réponse est-elle complète, est-ce du JSON, respecte-t-elle le schéma? Barrière 2, le sens : les règles d’affaires et la table des commandes. Enfin, la base de données, qui contient uniquement des résultats valides. Trois sorties mènent vers le bas. La génération peut échouer sans réponse du fournisseur. La barrière 1 rejette avec les codes refusal, truncated, malformed_json ou schema. La barrière 2 rejette avec des codes comme order_not_in_ticket et safety_needs_human. Après toute sortie, un agent traite le billet sans brouillon, et rien n’est enregistré.
Mise en pratique : le schéma dans le projet
Section intitulée « Mise en pratique : le schéma dans le projet »Le projet reçoit un nouveau fichier, assistant/schema.py, et une modification dans assistant/context.py. Travaillez dans ~/projects/ticket-assistant, avec .venv actif.
-
Récupérez les réponses enregistrées de ce module et les commandes de Larkfield. Elles viennent de l’instantané
end-of-m03dans le dépôt des exercices. Récupérez d’abord les fichiers les plus récents :Fenêtre de terminal git -C ../nextia-learning-labs pullSi vous avez téléchargé les exercices sous forme de fichier ZIP, téléchargez-le de nouveau. Copiez ensuite les fichiers :
Fenêtre de terminal cp ../nextia-learning-labs/C08/snapshots/end-of-m03/recordings/*.jsonl recordings/cp ../nextia-learning-labs/C08/snapshots/end-of-m03/data/orders.sqlite data/Fenêtre de terminal Copy-Item ..\nextia-learning-labs\C08\snapshots\end-of-m03\recordings\*.jsonl recordings\Copy-Item ..\nextia-learning-labs\C08\snapshots\end-of-m03\data\orders.sqlite data\recordings/contient maintenant six fichiers. Dansdata/,orders.sqlitese trouve à côté detickets.csv. Les commandes sont inventées pour ce cours, comme les billets. -
Observez d’abord le problème. Demandez la réponse enregistrée de Gemma au billet de démonstration, avec la requête v1 :
Fenêtre de terminal python -m assistant analyse T-80008 --model gemma3:4b --prompt v1La sortie se termine par :
```(not JSON)gemma3:4b | prompt v1 | finish_reason: stop | 1092 tokens in, 189 out | 5.795 s -
Créez le fichier
assistant/schema.py:assistant/schema.py """The shape of the assistant's answer, as a Pydantic model and as the JSON schema sent to the provider."""from typing import Literalfrom pydantic import BaseModel, ConfigDict, FieldTeam = Literal["delivery", "returns", "payment", "warranty", "account"]OrderStatus = Literal["processing", "shipped", "delivered", "return_requested", "return_received", "refunded","cancelled"]class OrderRef(BaseModel):model_config = ConfigDict(extra="forbid")order_id: str = Field(pattern=r"^LK-\d{6}$", description="The order ID exactly as the ticket writes it.")status: OrderStatus | None = Field(description="Only from an order lookup result; otherwise null.")class TicketAnalysis(BaseModel):model_config = ConfigDict(extra="forbid")language: Literal["en", "fr"]team: Teamneeds_human: boolreason: str = Field(max_length=300)confidence: Literal["low", "medium", "high"]order: OrderRef | Nonereply: str = Field(min_length=1, max_length=1200)def response_format() -> dict:"""Ask the provider for JSON that follows the schema (structured output, strict mode)."""return {"type": "json_schema","json_schema": {"name": "ticket_analysis", "strict": True, "schema": TicketAnalysis.model_json_schema()},}Ligne Signification extra="forbid"Aucun autre champ n’est permis : additionalProperties: falsedans le schéma JSON.order: OrderRef | NoneUn objet imbriqué ou null. Il n’a pas de valeur par défaut, donc le champ est obligatoire. Field(pattern=...)L’identifiant de commande doit respecter l’expression régulière. description=...Entre dans le schéma JSON, donc le modèle la lit aussi. model_json_schema()Écrit la classe sous forme de schéma JSON : les mêmes règles, au format du fournisseur. Les règles se trouvent à un seul endroit. Pydantic les utilise pour vérifier les réponses, et
response_format()envoie les mêmes règles au fournisseur. -
Ouvrez
assistant/context.pyet remplacez la fonctionbuild_requestpar cette version :assistant/context.py def build_request(ticket: Ticket, model: str, version: str = "v2", response_format: dict | None = None) -> dict:"""The body of one Chat Completions request. Only the fields that are set are sent."""request = {"model": model, "messages": build_messages(ticket, version), "max_completion_tokens": MAX_TOKENS}if response_format:request["response_format"] = response_formatreturn requestSans schéma, la requête reste exactement comme avant. Chaque commande du module 2 trouve donc encore son enregistrement.
-
Lancez
pythondans le dossier du projet et envoyez une requête avec sortie structurée au fournisseur simulé :Python, in ~/projects/ticket-assistant from assistant.context import build_requestfrom assistant.data import load_ticketfrom assistant.providers import MockProviderfrom assistant.schema import TicketAnalysis, response_formatmock = MockProvider()request = build_request(load_ticket("T-80008"), "chat-small", "v2", response_format())completion = mock.complete(request)print(completion.text)answer = TicketAnalysis.model_validate_json(completion.text)print(answer.team, answer.needs_human, answer.order.order_id)La sortie :
{"language":"en","team":"delivery","needs_human":false,"reason":"The customer is asking for a delivery date for an order that has shipped.","confidence":"high","order":{"order_id":"LK-581106","status":null},"reply":"I’m sorry you haven’t received a delivery date for your garden shed. Our delivery team can help check the shipment and tracking information for your order."}delivery False LK-581106answerest maintenant un objet Python avec des attributs, pas un dictionnaire.answer.order.order_idne peut pas manquer et contient toujours six chiffres. Quittez Python avecexit(). -
Créez le fichier
tests/test_schema.py. Les tests empêchent les règles de changer par accident :tests/test_schema.py import pytestfrom pydantic import ValidationErrorfrom assistant.schema import TicketAnalysis, response_formatGOOD = {"language": "en", "team": "delivery", "needs_human": False, "reason": "Late order.", "confidence": "high","order": {"order_id": "LK-581106", "status": None}, "reply": "We are checking your order."}def test_a_good_answer_passes():assert TicketAnalysis.model_validate(GOOD).team == "delivery"@pytest.mark.parametrize("change", [{"team": "shipping"}, # not one of the five teams{"needs_human": "maybe"}, # not a boolean{"order": {"order_id": "LK-55190", "status": None}}, # not LK- and 6 digits{"order": {"order_id": "LK-581106", "status": "lost"}}, # not a known status{"reply": ""}, # empty reply{"extra": "field"}, # a field the contract does not have])def test_impossible_answers_are_rejected(change):with pytest.raises(ValidationError):TicketAnalysis.model_validate({**GOOD, **change})def test_a_missing_field_is_rejected():answer = dict(GOOD)del answer["needs_human"]with pytest.raises(ValidationError):TicketAnalysis.model_validate(answer)def test_the_schema_sent_to_the_provider_is_strict():rf = response_format()schema = rf["json_schema"]["schema"]assert rf["json_schema"]["strict"] is Trueassert schema["additionalProperties"] is Falseassert set(schema["required"]) == set(schema["properties"]) # strict mode: every field is requiredLe dernier test protège la règle du mode strict. Si quelqu’un donne plus tard une valeur par défaut à un champ, ce test échoue sur son ordinateur. Il évite ainsi une erreur HTTP 400 en production.
-
Lancez les tests :
Fenêtre de terminal python -m pytestLa sortie se termine par :
30 passed, 1 deselected in 0.37sLe test paramétré compte comme six tests, un pour chaque réponse impossible.
À vous de jouer : le modèle local avec une sortie structurée
Section intitulée « À vous de jouer : le modèle local avec une sortie structurée »Faites d’abord une prédiction : la réponse enregistrée de Gemma pour T-64722 a été produite avec une sortie structurée. TicketAnalysis l’acceptera-t-il? Modifiez ensuite l’étape 5 : utilisez le billet "T-64722" et le modèle "gemma3:4b", puis relancez le programme. Faites la même chose pour "T-80009".
Afficher la réponse
Les deux réponses sont du JSON valide avec la bonne structure, et les deux sont rejetées. Pour T-64722, la sortie se termine par cette erreur (abrégée) :
pydantic_core._pydantic_core.ValidationError: 1 validation error for TicketAnalysisorder.order_id String should match pattern '^LK-\d{6}$' [type=string_pattern_mismatch, input_value='null', input_type=str]Pour T-80009, vous obtenez la même erreur avec input_value='LK-55190'. Le schéma contenant le motif figurait dans la requête, mais Ollama n’a pas appliqué pattern (motif). Votre propre vérification a détecté les deux problèmes.
Erreur courante
Le fournisseur promettait de respecter le schéma, alors le code a omis la vérification
Symptôme : TermeTomásL’ingénieur en IA de Larkfield. Il propose souvent un raccourci rapide, et la leçon montre pourquoi il échoue. passe l’assistant au modèle local pour économiser, avec la sortie structurée activée. Il retire la vérification Pydantic : « Le fournisseur respecte maintenant le schéma, alors json.loads suffit. » Le lendemain, un agent voit l’identifiant de commande null (valeur nulle) dans le service de soutien pour le billet T-64722. Une étape ultérieure tente de chercher une commande appelée "null".
Cause : la sortie structurée est une fonctionnalité du fournisseur, et chaque fournisseur prend en charge différentes parties de JSON Schema. Ollama 0.40.1 a conservé les noms des champs, les types et les énumérations, mais n’a pas appliqué pattern. Le texte "null" a le bon type, alors il a été accepté.
Correction : conservez TicketAnalysis.model_validate_json (ou model_validate) pour chaque réponse, quel que soit le fournisseur. Conservez un test qui soumet une mauvaise réponse enregistrée à la vérification. La fonctionnalité du fournisseur rend les réponses valides plus probables; votre code décide de ce qui est valide.
Vérifiez votre compréhension
Pourquoi le schéma de Larkfield rend-il « order » obligatoire, tout en permettant une valeur nulle? Que changerait une valeur par défaut?
Afficher la réponse
Une valeur nulle signifie « ce billet ne mentionne aucune commande ». Une clé absente ne dit rien : le modèle a peut-être oublié le champ. Un champ obligatoire qui accepte une valeur nulle force le modèle à décider. Avec une valeur par défaut, Pydantic retire order (commande) de la liste des champs obligatoires. La sortie structurée stricte échoue alors : Azure a répondu avec une erreur HTTP 400, « Missing ‘order’ » (champ « order » manquant). Le test test_the_schema_sent_to_the_provider_is_strict détecte ce problème avant l’envoi d’une requête.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Gemma 3 4B a donné 0 réponse conforme au schéma sans sortie structurée, et 67 avec celle-ci. Votre code doit-il encore vérifier les réponses? Donnez les preuves.
Afficher la réponse
Oui. Deux réponses échouent avec la sortie structurée : T-64722 avec l’identifiant de commande « null », et T-80009 avec « LK-55190 ». Les deux sont du JSON valide avec les bons noms de champs et les bons types. Ollama n’a pas appliqué le mot-clé pattern. Seule la vérification dans le code du projet les bloque donc. Les fournisseurs n’appliquent pas tous les mêmes règles; votre propre vérification est la même pour chaque fournisseur.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Faites une prédiction : avec la sortie structurée stricte, chat-small peut-il encore écrire l'identifiant de commande LK-55190 pour le billet T-80009? Qu'a-t-il fait?
Afficher la réponse
Non. La sortie structurée stricte sur Azure permet seulement au modèle d’écrire du texte qui correspond à ^LK-\d{6}$. Cinq chiffres sont donc impossibles. Le modèle a écrit six chiffres et inventé le dernier : LK-551902 avec la requête v2 (et LK-551904 avec v1). La réponse respecte le schéma, mais reste fausse. Le schéma peut imposer une structure; il ne peut pas rendre une valeur vraie.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
- Un schéma décrit la structure de la réponse : champs, types, champs obligatoires, énumérations, motifs et objets imbriqués. La validation vérifie si une réponse le respecte.
assistant/schema.pycontient le schéma de Larkfield sous forme de modèle Pydantic,TicketAnalysis. Les mêmes règles sont transmises au fournisseur sous forme de JSON Schema.- La sortie structurée envoie le schéma avec la requête. Gemma 3 4B est passé de 0 à 67 réponses valides sur 69, et chat-small de 67 à 69. Cela coûte environ 240 jetons (tokens) d’entrée par billet.
- Le mode strict exige que chaque champ figure dans la liste des champs obligatoires. Azure a refusé un schéma contenant un champ facultatif.
- Ollama n’a pas appliqué
pattern, alors le projet valide chaque réponse dans son propre code.
Ensuite, vous traiterez les réponses incomplètes : refus, réponses coupées à la limite de jetons et texte qui n’est pas du JSON.
Leçon suivante : Traiter les résultats incomplets