Lancer la requête sur une réponse connue
Vérifier du SQL en le lançant, c’est essayer la requête d’un modèle sur un petit jeu de données. Vous en avez calculé la réponse à la main. La relecture attrape certains défauts ; le lancement attrape ceux qui ont l’air justes sur la page.
Le module 43 vérifie la requête d’une personne par ses lignes. Cette leçon vérifie les candidates d’un modèle, et se demande si les données séparent le juste du faux.
À la fin de cette leçon
Remarque
Je peux vérifier la requête d’un modèle en la lançant sur des données à la réponse connue.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Réponse connue | le résultat calculé à la main |
| Candidate | une requête proposée comme réponse |
| Lignes du résultat | ce qu’une requête renvoie |
| Jeu d’essai | de petites données fixes gardées pour vérifier |
| Distinguer | donner des résultats différents au juste et au faux |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Comparer les lignes à une réponse connue
Calculez la réponse à la main sur un petit jeu d’essai. Neuf prêts, cela se compte sur papier. Lancez ensuite chaque candidate et comparez ses lignes aux vôtres.
Il y a trois verdicts. Réussi veut dire que les lignes concordent. Mauvaises lignes veut dire qu’elle a tourné et donné autre chose. Erreur veut dire que SQLite l’a refusée.
Exemple détaillé : réponses connues de la médiathèque
| Question | Calculé à la main | Réponse connue |
|---|---|---|
| Membres différents au mois 3 | Prêts 1 à 4 : Amina, Amina, Bruno, Chloé | 3 |
| Livres encore dehors | Les prêts 3, 5, 6, 8 et 9 ont rendu = 0 | 5 |
| Titres empruntés par le membre 2 | Prêts 3, 6 et 8 : livres 3, 4 et 5 | Carte de la pluie, Moteurs tranquilles, Sept Bouilloires |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
L’ordre compte seulement s’il est demandé
Une liste de titres dans n’importe quel ordre répond à « quels titres ». Comparez des copies triées des deux listes : un autre ordre passe quand même.
Si la question dit « le plus récent d’abord », l’ordre fait partie de la réponse. Comparez alors les lignes exactement comme elles viennent.
Les données doivent séparer le juste du faux
Demandez combien de prêts du mois 5 sont encore dehors. Les deux prêts du mois 5 sont dehors : une requête sans filtre sur rendu donne aussi 2.
Ce jeu d’essai ne sépare pas les deux requêtes. Ajoutez une ligne qui les sépare, comme un prêt rendu au mois 5, et vérifiez de nouveau.
Une erreur courante
Attention
Beaucoup pensent qu’une candidate qui réussit sur des données d’essai est juste. Elle est juste pour ces données-là. Vérifiez que le jeu d’essai donne une autre réponse à la requête fausse probable, puis faites confiance à la réussite.
Écrire l’assistant 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\";' '// Les jetons d’une réponse, ou null si elle n’en donne pas.' 'function compter(donnees, nom) {' ' return donnees.usage && typeof donnees.usage[nom] === \"number\" ? donnees.usage[nom] : null;' '}' '// Une réponse peut entourer la requête d’un bloc délimité : on garde l’intérieur.' 'function sqlDe(texte) {' ' const morceaux = texte.split(String.fromCharCode(96).repeat(3));' ' const dedans = (morceaux.length > 2 ? morceaux[1] : texte).trim();' ' return dedans.toLowerCase().startsWith(\"sql\") ? dedans.slice(3).trim() : dedans;' '}' > 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’assistant du modèle, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '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 || 100 }),' ' signal: AbortSignal.timeout(30000),' ' });' ' if (res.ok === false) throw new Error(\"statut \" + res.status);' ' const d = await res.json();' ' return { texte: d.choices[0].message.content, entree: compter(d, \"prompt_tokens\"), sortie: compter(d, \"completion_tokens\"), enregistre: false };' ' } catch (erreur) {' ' console.log(\"Échantillon enregistré : aucun modèle n’a répondu (\" + erreur.message + \").\");' ' return { texte: echantillon, entree: null, sortie: null, enregistre: true };' ' }' '}' 'module.exports = { demander, sqlDe };' >> 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 la base de la médiathèque, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { DatabaseSync } = require(\"node:sqlite\");' '// Une médiathèque inventée : six livres, quatre membres, neuf prêts.' 'const LIVRES = [[1, \"Le Phare des oranges\", 2019], [2, \"Port de verre\", 2021], [3, \"Carte de la pluie\", 2015],' ' [4, \"Moteurs tranquilles\", 2023], [5, \"Sept Bouilloires\", 2018], [6, \"Au nord de midi\", 2022]];' 'const MEMBRES = [[1, \"Amina\"], [2, \"Bruno\"], [3, \"Chloé\"], [4, \"Driss\"]];' '// id, livre_id, membre_id, mois, rendu (1 rendu, 0 encore dehors)' 'const PRETS = [[1, 1, 1, 3, 1], [2, 2, 1, 3, 1], [3, 3, 2, 3, 0], [4, 1, 3, 3, 1], [5, 1, 1, 4, 0],' ' [6, 4, 2, 4, 0], [7, 2, 3, 4, 1], [8, 5, 2, 5, 0], [9, 2, 4, 5, 0]];' > mediatheque.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 la base de la médiathèque, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function remplir(db, table, lignes) {' ' const marques = lignes[0].map(function () { return \"?\"; }).join(\", \");' ' const inserer = db.prepare(\"INSERT INTO \" + table + \" VALUES (\" + marques + \")\");' ' lignes.forEach(function (ligne) { inserer.run(...ligne); });' '}' 'function ouvrir(fichier) {' ' const db = new DatabaseSync(fichier || \":memory:\");' ' db.exec(\"CREATE TABLE livres (id INTEGER PRIMARY KEY, titre TEXT, annee INTEGER)\");' ' db.exec(\"CREATE TABLE membres (id INTEGER PRIMARY KEY, nom TEXT)\");' ' db.exec(\"CREATE TABLE prets (id INTEGER PRIMARY KEY, livre_id INTEGER REFERENCES livres(id), \"' ' + \"membre_id INTEGER REFERENCES membres(id), mois INTEGER, rendu INTEGER)\");' ' remplir(db, \"livres\", LIVRES);' ' remplir(db, \"membres\", MEMBRES);' ' remplir(db, \"prets\", PRETS);' ' return db;' '}' 'module.exports = { ouvrir };' >> mediatheque.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 script de vérification, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander, sqlDe } = require(\"./model\");' 'const { ouvrir } = require(\"./mediatheque\");' 'const db = ouvrir();' 'function lignesDe(sql) {' ' try {' ' return db.prepare(sql).all().map(function (r) { return JSON.stringify(Object.values(r)); }).sort();' ' } catch (erreur) { return null; }' '}' 'function verifier(sql, attendu) {' ' const obtenu = lignesDe(sql);' ' if (obtenu === null) return \"erreur\";' ' const voulu = attendu.map(function (r) { return JSON.stringify(r); }).sort();' ' return obtenu.join(\"|\") === voulu.join(\"|\") ? \"réussi\" : \"mauvaises lignes \" + obtenu.join(\" \");' '}' > verifier.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 script de vérification, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const P = \"SELECT COUNT(*) FROM prets WHERE \";' 'const T = \"SELECT l.titre FROM prets p JOIN livres l ON \";' 'const cas = [' ' { attendu: [[3]], sql: [\"SELECT COUNT(membre_id) FROM prets WHERE mois = 3\",' ' \"SELECT COUNT(DISTINCT membre_id) FROM prets WHERE mois = 3\",' ' \"SELECT COUNT(DISTINCT membre) FROM prets WHERE mois = 3\"] },' ' { attendu: [[5]], sql: [\"SELECT COUNT(*) FROM prets\", P + \"rendu = 0\", P + \"rendu = 1\"] },' ' { attendu: [[\"Carte de la pluie\"], [\"Moteurs tranquilles\"], [\"Sept Bouilloires\"]],' ' sql: [T + \"p.id = l.id WHERE p.membre_id = 2\", T + \"p.livre_id = l.id WHERE p.membre_id = 2\",' ' T + \"p.livre_id = l.id WHERE p.membre_id = 2 ORDER BY l.titre DESC\"] },' ' { attendu: [[2]], sql: [P + \"mois = 5 AND rendu = 0\", P + \"mois = 5\"] },' '];' >> verifier.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 script de vérification, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' cas.forEach(function (c, i) {' ' c.sql.forEach(function (sql, j) { console.log((i + 1) + \"abc\"[j] + \" : \" + verifier(sql, c.attendu)); });' ' });' ' db.exec(\"UPDATE prets SET rendu = 1 WHERE id = 8\");' ' console.log(\"une fois le prêt 8 rendu, la réponse à 4 vaut 1 :\");' ' cas[3].sql.forEach(function (sql, j) { console.log(\"4\" + \"ab\"[j] + \" : \" + verifier(sql, [[1]])); });' ' db.exec(\"UPDATE prets SET rendu = 0 WHERE id = 8\");' ' await direct();' '}' >> verifier.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 script de vérification, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function direct() {' ' const q = \"prets(id, livre_id, membre_id, mois, rendu)\nQuestion : Combien de membres différents ont emprunté un livre au mois 3 ?\";' ' const echantillon = \"SELECT COUNT(DISTINCT membre_id) FROM prets WHERE mois = 3;\";' ' const reponse = await demander([{ role: \"system\", content: \"Répondez par une seule requête SELECT pour SQLite, et rien d’autre.\" },' ' { role: \"user\", content: q }], echantillon, 120);' ' const sql = sqlDe(reponse.texte);' ' console.log(\"le modèle a écrit : \" + sql);' ' console.log(\" vérifiée sur des données connues : \" + verifier(sql, [[3]]));' '}' 'main();' >> verifier.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 chaque candidate sur des données connues
docker exec -w /home/node/project agent node --experimental-sqlite --no-warnings verifier.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.
Ce que le Run affiche
| Ligne | Ce qu’elle montre |
|---|---|
| 1a : mauvaises lignes [4] | Amina comptée deux fois |
| 1c : erreur | Aucune colonne ne s’appelle membre |
| 2c : mauvaises lignes [4] | Les livres revenus ont été comptés |
| 3a : mauvaises lignes ["Au nord de midi"] ["Carte de la pluie"] | La jointure id sur id |
| 3c : réussi | Un autre ordre réussit quand même |
| 4a : réussi et 4b : réussi | Les données ne les séparent pas |
| 4b : mauvaises lignes [2], une fois le prêt 8 rendu | Une nouvelle ligne les sépare |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.