Arrêter une consigne faible par un test automatique
Une équipe change une consigne le vendredi. Le lundi, les clients voient des réponses plus faibles. Personne n’a regardé le score, car rien ne les y obligeait.
Un test de régression dans la chaîne de contrôle l’évite. Le module sur l’évaluation et les tests compare deux exécutions à la main. Cette leçon rend la comparaison capable d’arrêter un changement toute seule.
À la fin de cette leçon
Remarque
Je peux faire échouer une chaîne de contrôle sur une consigne plus faible.
Mots clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Chaîne de contrôle | les vérifications qui tournent seules à chaque changement | |
| Jeu gelé | un jeu d’évaluation qui ne change pas d’une exécution à l’autre | |
| Seuil | le taux de réussite le plus bas qu’un changement peut avoir | 80 |
| Cas critique | un cas qui ne doit jamais passer de réussi à échoué | |
| Code de sortie | le nombre par lequel un programme se termine ; zéro veut dire tout va bien |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un jeu gelé et un seuil
La chaîne fait tourner votre fonction sur le même jeu gelé à chaque fois. Si le jeu changeait avec la consigne, vous ne pourriez plus comparer deux exécutions.
Vous choisissez ensuite un seuil, par exemple 80 %. Un changement qui reste sous le seuil fait échouer la chaîne.
Les cas qui ont changé comptent le plus
Un taux cache les mouvements. Un cas peut casser pendant qu’un autre se répare, et le taux reste le même.
La chaîne liste donc aussi les cas qui ont changé. Un cas cassé réussissait avant et échoue maintenant. Un cas corrigé fait l’inverse.
Exemple détaillé : douze cas, deux exécutions
| Mesure | Avant | Après |
|---|---|---|
| Cas réussis | 10 sur 12 | 9 sur 12 |
| Taux de réussite, en pourcentage entier | 83 | 75 |
| Cas cassés | - | 4 et 7 |
| Cas corrigés | - | 6 |
| Cas critique cassé | - | Oui, le cas 4 |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur courante
Attention
Beaucoup pensent qu’un taux au-dessus du seuil prouve qu’un changement est sans danger. Un cas critique peut casser pendant que le taux reste haut. Vérifiez d’abord les cas critiques, ensuite le taux.
Un code de sortie pilote la chaîne
Un programme se termine par un nombre. Zéro veut dire que tout va bien, tout autre nombre signale un problème. La chaîne s’arrête devant un nombre non nul.
Nous prenons 2 pour un cas critique cassé, 1 pour un taux sous le seuil et 0 pour un succès. Le contrôle des cas critiques passe en premier.
É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;' '}' > 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 };' >> 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 barrière, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const seuil = 80;' 'const lignes = [' ' \"1 c 1 1\", \"2 n 1 1\", \"3 n 1 1\", \"4 c 1 0\",' ' \"5 n 1 1\", \"6 n 0 1\", \"7 n 1 0\", \"8 n 1 1\",' ' \"9 c 1 1\", \"10 n 0 0\", \"11 n 1 1\", \"12 n 1 1\",' '].map(function (l) {' ' const p = l.split(\" \");' ' return { id: Number(p[0]), critique: p[1] === \"c\", avant: p[2] === \"1\", apres: p[3] === \"1\" };' '});' > barriere.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 barrière, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function taux(cle) {' ' const n = lignes.filter(function (l) { return l[cle]; }).length;' ' return Math.floor(n * 100 / lignes.length);' '}' 'const casses = lignes.filter(function (l) { return l.avant && l.apres === false; });' 'const corriges = lignes.filter(function (l) { return l.avant === false && l.apres; });' 'function numeros(liste) {' ' return liste.length === 0 ? \"aucun\" : liste.map(function (l) { return l.id; }).join(\", \");' '}' >> barriere.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 barrière, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function codeSortie() {' ' if (casses.some(function (l) { return l.critique; })) return 2;' ' if (taux(\"apres\") < seuil) return 1;' ' return 0;' '}' 'async function main() {' ' console.log(\"taux de réussite : \" + taux(\"avant\") + \" -> \" + taux(\"apres\") + \" (seuil \" + seuil + \")\");' ' console.log(\"cassés : \" + numeros(casses) + \" corrigés : \" + numeros(corriges));' ' console.log(\"code de sortie : \" + codeSortie());' >> barriere.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 barrière, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' const q = \"Écrivez une phrase calme pour une demande de fusion : le cas 4 réussissait avant et échoue maintenant.\";' ' const echantillon = \"Le cas 4 réussissait avec l’ancienne consigne et échoue avec celle-ci : la chaîne s’arrête.\";' ' const reponse = await demander([{ role: \"user\", content: q }], echantillon, 60);' ' console.log(\"commentaire : \" + reponse.texte);' '}' 'main();' >> barriere.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 la barrière
docker exec -w /home/node/project agent node barriere.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 la barrière affiche
| Ligne | Ce qu’elle montre |
|---|---|
| taux de réussite : 83 -> 75 (seuil 80) | Le taux avant et après, et le seuil |
| cassés : 4, 7 corrigés : 6 | Les cas qui ont changé |
| code de sortie : 2 | Le cas 4 est critique, donc la chaîne s’arrête |
| commentaire : ... | Une phrase du labo. Un échantillon enregistré si aucun modèle ne répond |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.