Le téléphone que personne n’a donné
Extraire une fiche d’un texte, c’est demander à un modèle de lire un paragraphe et de remplir un ensemble fixe de champs. C’est utile, car le modèle comblera un trou par une supposition si vous ne lui dites pas de s’en abstenir.
Le secrétariat d’une faculté reçoit une demande de salle avec un nom, une salle et une date, mais sans numéro de téléphone. L’outil affiche pourtant un numéro. Personne ne l’a donné : le modèle l’a inventé.
À la fin de cette leçon
Remarque
Je peux transformer un paragraphe en fiche sans inventer aucune valeur.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Extraire | sortir des faits nommés d’un texte | |
| Champ | un fait nommé dans une fiche | date |
| Null | la valeur qui dit qu’un champ est absent | |
| Texte source | le paragraphe d’où les faits doivent venir | |
| Ancré | trouvé dans le texte source, pas deviné | |
| Complétude | la part des champs qui ont une valeur |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Nommer les champs et permettre null
Dites exactement quels champs vous voulez et le type de chacun. Dites ensuite quoi faire quand un fait n’est pas dans le texte : mettre null, sans jamais deviner.
Sans cette règle, un modèle peut remplir tous les champs, parce qu’une fiche pleine ressemble à une bonne réponse. Donnez-lui la permission de laisser un trou.
Exemple travaillé : un message, cinq champs
| Champ | Dans le message ? | La bonne valeur |
|---|---|---|
| nom | Oui : « je m’appelle Camille Martin » | Camille Martin |
| salle | Oui : « la salle B12 » | B12 |
| date | Oui : « 2026-11-20 » | 2026-11-20 |
| etudiants | Oui : « 24 étudiants » | 24 |
| telephone | Non | null |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Vérifier que chaque valeur est ancrée
Ne vous fiez pas à la seule consigne. Après la réponse, comparez chaque valeur au texte source. Si la valeur n’y figure pas, considérez-la comme inventée et mettez null.
Ce contrôle est simple et a une faiblesse : un faux nombre d’étudiants de 2 serait gardé, car le chiffre 2 figure dans 2026-11-20. Un contrôle plus fin compare des mots entiers. Même le contrôle simple attrape le faux numéro de téléphone.
É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 l’extracteur, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '' 'const source = \"Bonjour, je m’appelle Camille Martin. Je voudrais réserver la salle B12 le 2026-11-20. Nous serons 24 étudiants et 3 enseignants. Merci de répondre par courriel.\";' 'const champs = [\"nom\", \"salle\", \"date\", \"etudiants\", \"telephone\"];' '' '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; }' '}' > extraction4.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’extracteur, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Garder une valeur seulement si elle figure vraiment dans le texte source.' 'function ancrer(fiche, source, champs) {' ' const resultat = {};' ' for (const nom of champs) {' ' const valeur = fiche === null ? null : fiche[nom];' ' if (valeur === null || valeur === undefined || valeur === \"\") {' ' resultat[nom] = null;' ' console.log(nom + \" : null (absent)\");' ' } else if (source.toLowerCase().includes(String(valeur).toLowerCase())) {' ' resultat[nom] = valeur;' ' console.log(nom + \" : \" + valeur + \" (gardé)\");' ' } else {' ' resultat[nom] = null;' ' console.log(nom + \" : \" + valeur + \" (pas dans le texte, écarté)\");' ' }' ' }' ' return resultat;' '}' >> extraction4.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’extracteur, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const consigne = \"Extrais ces champs du message sous forme d’objet JSON : nom (texte), salle (texte), date (texte, année-mois-jour), etudiants (nombre entier), telephone (texte). Si un champ n’est pas dans le message, mets null. Ne devine jamais. Réponds uniquement par le JSON.\n\nMessage : \" + source;' '// Échantillon enregistré, utilisé seulement si aucun modèle ne répond. Il invente un téléphone exprès.' 'const echantillon = JSON.stringify({ nom: \"Camille Martin\", salle: \"B12\", date: \"2026-11-20\", etudiants: 24, telephone: \"06 12 34 56 78\" });' >> extraction4.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’extracteur, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const reponse = await demander([{ role: \"user\", content: consigne }], echantillon, 150);' ' const fiche = ancrer(extraireJson(reponse), source, champs);' ' let remplis = 0;' ' for (const nom of champs) {' ' if (fiche[nom] === null) continue;' ' remplis = remplis + 1;' ' }' ' console.log(\"Remplis : \" + remplis + \" sur \" + champs.length);' '}' 'main();' >> extraction4.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 l’extracteur
docker exec -w /home/node/project agent node extraction4.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 peut montrer |
|---|---|
| nom, salle, date, etudiants | Chacun suivi d’une valeur et de (gardé) |
| telephone | null (absent), ou une valeur marquée (pas dans le texte, écarté) |
| Remplis | Remplis : 4 sur 5 si le modèle n’a trouvé que les quatre faits |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur fréquente
Attention
Beaucoup pensent qu’un champ rempli vaut mieux qu’un champ vide. Une valeur inventée ressemble à une vraie, et personne ne pense à la vérifier. Un null dit clairement qu’une personne doit regarder.