Réunir les pièces dans une boucle
Un harnais d’agent est le programme autour du modèle : il construit la fenêtre, exécute les outils, applique le mode et enregistre les changements. Le modèle ne fait que proposer une étape. Le harnais décide de ce qui arrive.
Cette leçon réunit les cinq précédentes dans une boucle que vous pouvez lancer et rembobiner.
À la fin de cette leçon
Remarque
Je peux réunir contexte, outils, modes, isolation et annulation dans un harnais que je peux lancer et rembobiner.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Harnais | le programme qui fait tourner un agent autour d’un modèle |
| Boucle | demander une étape, décider, agir, noter, recommencer |
| Modèle scripté | une liste fixe d’étapes qui remplace un modèle |
| Transcription | le relevé de chaque étape et de son issue |
| Fenêtre | les derniers résultats envoyés au modèle ensuite |
| Rembobiner | annuler tous les changements après une étape choisie |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
La boucle, étape par étape
À chaque tour, le harnais demande une étape au modèle. Il décide selon le mode : autoriser, demander ou refuser. S’il doit demander, la personne répond. Si l’étape est autorisée, le harnais note ce qu’elle va remplacer, puis l’exécute.
Le résultat entre dans une fenêtre, qui ne garde que les derniers. Puis la boucle recommence jusqu’à ce que le modèle dise qu’il a fini.
Ce qu’ajoute chaque leçon précédente
| Leçon | Sa place dans la boucle |
|---|---|
| Contexte | La fenêtre ne garde que les derniers résultats |
| Outils | Chaque étape nomme un outil étroit |
| Modes | Une fonction décide autoriser, demander ou refuser |
| Isolation | Une tâche annexe rend un court résultat dans la fenêtre |
| Annulation | Une entrée de journal est écrite avant un changement |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur courante
Attention
Beaucoup laissent une étape refusée disparaître. Le modèle n’apprend alors jamais qu’elle a été refusée et la redemande. Notez le refus comme un résultat, pour que la fenêtre suivante le montre.
É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 le harnais, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const RISQUE = { lire_fichier: \"lecture\", ecrire_fichier: \"ecriture\", supprimer_fichier: \"destructif\", executer: \"execution\" };' 'const script = [' ' { outil: \"lire_fichier\", chemin: \"app.py\" },' ' { outil: \"ecrire_fichier\", chemin: \"app.py\", texte: \"x = 2\" },' ' { outil: \"executer\", chemin: \"pytest\" },' ' { outil: \"supprimer_fichier\", chemin: \"notes.txt\" },' ' { outil: \"ecrire_fichier\", chemin: \".env\", texte: \"CLE=1\" },' ' { outil: \"fini\" },' '];' 'const fichiers = { \"app.py\": \"x = 1\", \"notes.txt\": \"a faire\" };' 'const journal = [];' > harnais6.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 harnais, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function decider(mode, risque, chemin) {' ' if (mode === \"plan\") return risque === \"lecture\" ? \"autoriser\" : \"refuser\";' ' if (chemin.startsWith(\".env\") || risque === \"destructif\") return \"demander\";' ' if (mode === \"auto\" && (risque === \"lecture\" || risque === \"ecriture\")) return \"autoriser\";' ' if (mode === \"demande\" && risque === \"lecture\") return \"autoriser\";' ' return \"demander\";' '}' >> harnais6.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 harnais, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function agir(s, n) {' ' if (s.outil === \"lire_fichier\") return s.chemin in fichiers ? fichiers[s.chemin] : \"absent\";' ' if (s.outil === \"executer\") return \"exécuté \" + s.chemin;' ' journal.push({ etape: n, chemin: s.chemin, avant: s.chemin in fichiers ? fichiers[s.chemin] : null });' ' if (s.outil === \"ecrire_fichier\") { fichiers[s.chemin] = s.texte; return \"écrit \" + s.chemin; }' ' delete fichiers[s.chemin];' ' return \"supprimé \" + s.chemin;' '}' >> harnais6.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 harnais, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function toutLancer(mode, approbations) {' ' const sortie = [];' ' for (let i = 0; i < script.length; i++) {' ' const s = script[i];' ' if (s.outil === \"fini\") break;' ' const v = decider(mode, RISQUE[s.outil], s.chemin);' ' let issue = v === \"autoriser\" ? \"ok\" : \"interdit\";' ' if (v === \"demander\") issue = approbations.shift() === true ? \"approuvé\" : \"refusé\";' ' const bon = issue === \"ok\" || issue === \"approuvé\";' ' sortie.push({ etape: i + 1, outil: s.outil, chemin: s.chemin, issue: issue, resultat: bon ? agir(s, i + 1) : issue });' ' }' ' return sortie;' '}' >> harnais6.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 harnais, partie 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function annulerJusqua(etape) {' ' for (const e of journal.slice().reverse()) {' ' if (e.etape <= etape) continue;' ' if (e.avant === null) delete fichiers[e.chemin];' ' else fichiers[e.chemin] = e.avant;' ' }' '}' >> harnais6.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 harnais, partie 6
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const sortie = toutLancer(\"auto\", [true, false]);' ' for (const o of sortie) console.log(o.etape + \" \" + o.outil + \" \" + o.chemin + \" \" + o.issue + \" -> \" + o.resultat);' ' console.log(\"fenêtre \" + JSON.stringify(sortie.slice(-3).map(function (o) { return o.resultat; })));' ' console.log(\"fichiers \" + JSON.stringify(fichiers));' ' annulerJusqua(1);' ' console.log(\"après annulation jusqu’à 1 \" + JSON.stringify(fichiers));' ' const texte = sortie.map(function (o) { return o.outil + \" \" + o.issue; }).join(\", \");' ' const reponse = await demander([{ role: \"user\", content: \"Écrivez un message de commit d’une ligne pour une exécution qui a fait ceci : \" + texte }], \"Corriger le total du panier dans app.py\", 30);' ' console.log(\"message : \" + reponse.texte);' '}' 'main();' >> harnais6.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 harnais et rembobiner
docker exec -w /home/node/project agent node harnais6.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.
Le modèle est un script
Les étapes viennent d’une liste fixe, donc chaque étudiant affiche la même exécution. Un vrai harnais demanderait chaque étape au modèle. Le court appel au modèle ne fait qu’écrire un message de commit d’après ce que l’exécution a fait.
Sans modèle, il affiche un échantillon enregistré pour cette ligne.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| 1 lire_fichier app.py ok -> x = 1 | Une lecture passe en mode auto |
| 2 ecrire_fichier app.py ok -> écrit app.py | Une écriture passe en mode auto, après son entrée de journal |
| 3 executer pytest approuvé -> exécuté pytest | La première approbation était oui |
| 4 supprimer_fichier notes.txt refusé -> refusé | La seconde approbation était non |
| 5 ecrire_fichier .env refusé -> refusé | Plus aucune approbation, donc non |
| fenêtre ["exécuté pytest","refusé","refusé"] | Les trois derniers résultats, refus compris |
| fichiers {"app.py":"x = 2","notes.txt":"a faire"} | Seule l’écriture a changé quelque chose |
| après annulation jusqu’à 1 {"app.py":"x = 1",...} | Le retour a remis l’écriture comme avant |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.