Du JSON valide, des données fausses
Valider du JSON avec un schéma, c’est comparer chaque champ d’une fiche à une liste de règles. Ces règles donnent le type, disent si le champ est requis et listent les valeurs permises. C’est utile, car un JSON qui s’analyse bien peut contenir un nombre de pages égal à « beaucoup ».
Une appli de bibliothèque demande une fiche de livre à un modèle. La réponse s’analyse sans erreur. Puis un nombre de pages arrive sous la forme du texte « 180 » et la page des totaux affiche n’importe quoi.
À la fin de cette leçon
Remarque
Je peux contrôler une réponse avec un schéma.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Schéma | une liste de contrôle des champs d’une fiche | |
| Type | le genre de valeur qu’un champ contient | entier |
| Requis | un champ qui doit être présent | |
| Valeurs permises | les seules valeurs qu’un champ peut prendre | roman, histoire, science |
| Faute | une façon d’enfreindre une règle | |
| Fiche | un objet qui regroupe des champs |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un schéma est une liste de contrôle
L’analyse dit que le texte est du JSON. Elle ne dit pas que les données sont justes. Pour cela, il faut un schéma : une règle par champ.
Une règle donne le type du champ, dit s’il est requis et indique parfois ses valeurs permises. Une norme appelée JSON Schema écrit ces règles dans un format fixe. Ici, vous écrivez votre propre liste de contrôle, en code.
Exemple travaillé : un schéma pour un livre
| Champ | Type | Requis | Valeurs permises |
|---|---|---|---|
| titre | texte | oui | n’importe quel texte |
| pages | entier | oui | n’importe quel nombre entier |
| genre | texte | oui | roman, histoire, science |
| note | nombre | non | n’importe quel nombre |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Relever toutes les fautes, pas seulement la première
Un contrôle qui s’arrête à la première faute en dit peu. Relevez toutes les fautes dans une liste, un court texte chacune, par exemple pages: mauvais type.
La liste sert à deux choses. Elle décide d’accepter ou non la fiche, et la leçon suivante la renvoie au modèle comme retour d’information.
Écrire l’aide du modèle, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const base = process.env.AI_URL || \"http://ai:4000\";' 'async function demander(messages, echantillon, max) {' ' try {' ' const res = await fetch(base + \"/v1/chat/completions\", {' ' method: \"POST\",' ' headers: { \"content-type\": \"application/json\", authorization: \"Bearer lab\" },' ' body: JSON.stringify({ model: \"lab\", messages: messages, max_tokens: max || 200 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' return (await res.json()).choices[0].message.content;' > model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire l’aide du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' const liste = [].concat(echantillon);' ' const tour = messages.filter(function (m) { return m.role === \"assistant\"; }).length;' ' return liste[Math.min(tour, liste.length - 1)];' ' }' '}' 'module.exports = { demander };' >> model.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '' '// Un schéma est une liste de contrôle : une règle par champ.' 'const schema = [' ' { nom: \"titre\", type: \"texte\", requis: true },' ' { nom: \"pages\", type: \"entier\", requis: true },' ' { nom: \"genre\", type: \"texte\", requis: true, permis: [\"roman\", \"histoire\", \"science\"] },' ' { nom: \"note\", type: \"nombre\", requis: false },' '];' > controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function estUneFiche(x) {' ' if (x === null) return false;' ' if (Array.isArray(x)) return false;' ' return typeof x === \"object\";' '}' '' 'function typeCorrect(valeur, type) {' ' if (type === \"texte\") return typeof valeur === \"string\";' ' if (type === \"entier\") return Number.isInteger(valeur);' ' if (type === \"nombre\") return typeof valeur === \"number\";' ' if (type === \"booleen\") return typeof valeur === \"boolean\";' ' return false;' '}' >> controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Relever toutes les fautes, pas seulement la première.' 'function fautes(fiche, schema) {' ' if (estUneFiche(fiche) === false) return [\"pas un objet\"];' ' const trouvees = [];' ' for (const regle of schema) {' ' if (Object.prototype.hasOwnProperty.call(fiche, regle.nom) === false) {' ' if (regle.requis) trouvees.push(regle.nom + \": absent\");' ' continue;' ' }' ' const valeur = fiche[regle.nom];' ' if (typeCorrect(valeur, regle.type) === false) trouvees.push(regle.nom + \": mauvais type\");' ' else if (regle.permis && regle.permis.includes(valeur) === false) trouvees.push(regle.nom + \": valeur interdite\");' ' }' ' return trouvees;' '}' >> controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function extraireJson(texte) {' ' const debut = texte.indexOf(\"{\");' ' const fin = texte.lastIndexOf(\"}\");' ' if (debut === -1 || fin < debut) return null;' ' try { return JSON.parse(texte.slice(debut, fin + 1)); } catch (erreur) { return null; }' '}' >> controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const fiches = [' ' { titre: \"Atlas des rivières\", pages: 240, genre: \"science\" },' ' { titre: \"Tables des marées\", pages: \"180\", genre: \"histoire\" },' ' { titre: \"Lettres de lune\", pages: 96, genre: \"poésie\", note: 4.5 },' ' { pages: 310, genre: \"roman\", note: \"bien\" },' '];' 'fiches.forEach(function (fiche, rang) {' ' const trouvees = fautes(fiche, schema);' ' console.log(\"Fiche \" + (rang + 1) + \" :\", trouvees.length === 0 ? \"ok\" : trouvees.join(\", \"));' '});' >> controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Écrire le contrôle du schéma, partie 6
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const messages = [{ role: \"user\", content: \"Invente un livre pour une bibliothèque universitaire. Réponds par un objet JSON avec les clés titre (texte), pages (un nombre entier) et genre (roman, histoire ou science). Réponds uniquement par le JSON.\" }];' ' // Échantillon enregistré, utilisé seulement si aucun modèle ne répond. Il casse deux règles exprès.' ' const echantillon = JSON.stringify({ titre: \"Le Port tranquille\", pages: \"210\", genre: \"policier\" });' ' const reponse = await demander(messages, echantillon, 100);' ' const fiche = extraireJson(reponse);' ' const trouvees = fautes(fiche, schema);' ' console.log(\"Fiche du modèle :\", JSON.stringify(fiche));' ' console.log(\"Fautes :\", trouvees.length === 0 ? \"aucune\" : trouvees.join(\", \"));' '}' 'main();' >> controle2.js"Affichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Lancer le contrôle
docker exec -w /home/node/project agent node controle2.jsAffichée ici, non exécutée. Créez un compte gratuit pour appuyer sur Exécuter et la saisir dans le terminal de votre propre labo.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| Fiche 1 | ok |
| Fiche 2 | pages: mauvais type |
| Fiche 3 | genre: valeur interdite |
| Fiche 4 | titre: absent, note: mauvais type |
| Fiche du modèle et Fautes | Changent à chaque exécution : une fiche du modèle, puis ses fautes ou aucune |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur fréquente
Attention
Beaucoup pensent que « 180 » et 180 sont la même chose. Ils se ressemblent, mais un programme ne peut ni additionner ni comparer un texte comme un nombre. Contrôlez le type, pas l’apparence de la valeur.