L’agent qui ne s’arrêtait jamais
Un agent IA est une boucle dans laquelle un modèle choisit lui-même sa prochaine étape. Comme rien ne lui dit quand renoncer, il lui faut un plan et une règle d’arrêt.
Un agent de support relance toute la nuit une recherche qui échoue. Chaque essai coûte de l’argent et aucun ne peut réussir. La boucle était juste. La règle d’arrêt manquait.
À la fin de cette leçon
Remarque
Je peux faire planifier ses étapes à un agent et l’arrêter selon une règle.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Agent | une boucle où un modèle choisit la prochaine étape | |
| Plan | les étapes qu’un agent prévoit, sous forme de données | |
| Budget d’étapes | le nombre maximal d’étapes d’un run | 3 |
| Règle d’arrêt | un test qui termine la boucle | |
| Issue | la raison pour laquelle un run s’est terminé |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un plan est une donnée, pas une impression
Demandez au modèle un plan sous forme de liste JSON, puis traitez-le comme une donnée. Votre programme peut le compter, vérifier chaque étape et le montrer à une personne avant tout lancement.
Un plan est une proposition. Le modèle peut rendre une liste de mauvaise forme ou une étape qu’aucun outil ne sait faire. Vérifiez le plan avant de le suivre.
Exemple travaillé : un plan et son run
| Étape | Le plan dit | Résultat de l’outil | État du run |
|---|---|---|---|
| 1 | chercher baguette | baguette 110 centimes | ok |
| 2 | chercher croissant | croissant 130 centimes | ok |
| 3 | chercher kiwi | aucun outil ne convient | echec |
| 4 | annoncer les prix | pas atteinte | le budget ou le blocage décide |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
É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’agent planificateur, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const PRIX = { baguette: 110, croissant: 130, eclair: 280 };' 'const messages = [' ' { role: \"system\", content: \"Vous répondez uniquement par du JSON.\" },' ' { role: \"user\", content: \"Planifiez la recherche du prix d’une baguette, d’un croissant et d’un kiwi. Répondez par un tableau JSON de 3 courtes chaînes, un produit à chercher chacune. Répondez uniquement par le JSON.\" },' '];' '// Échantillon enregistré, utilisé seulement si aucun modèle ne répond.' 'const echantillon = JSON.stringify([\"baguette\", \"croissant\", \"kiwi\"]);' > plan1.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’agent planificateur, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function etape(texte) {' ' const cle = Object.keys(PRIX).find(function (k) { return texte.toLowerCase().indexOf(k) >= 0; });' ' if (cle === undefined) return { ok: false, note: \"aucun outil ne convient : \" + texte };' ' return { ok: true, note: cle + \" coûte \" + PRIX[cle] + \" centimes\" };' '}' >> plan1.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’agent planificateur, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const reponse = await demander(messages, echantillon, 100);' ' let plan = null;' ' try { plan = JSON.parse(reponse.slice(reponse.indexOf(\"[\"), reponse.lastIndexOf(\"]\") + 1)); } catch (erreur) { }' ' if (Array.isArray(plan) === false) { console.log(\"Ce n’est pas une liste, plan fixe utilisé.\"); plan = [\"baguette\", \"croissant\"]; }' ' const budget = 3, echecsMax = 2;' ' let utilisees = 0, echecs = 0, issue = \"termine\";' ' for (const texte of plan) {' ' if (utilisees === budget) { issue = \"budget\"; break; }' ' utilisees += 1;' ' const r = etape(String(texte));' ' echecs = r.ok ? 0 : echecs + 1;' ' console.log(\"Étape \" + utilisees + \" : \" + r.note);' ' if (echecs === echecsMax) { issue = \"bloque\"; break; }' ' }' ' console.log(\"Issue : \" + issue + \" après \" + utilisees + \" étapes\");' '}' 'main();' >> plan1.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’agent et lire l’issue
docker exec -w /home/node/project agent node plan1.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 |
|---|---|
| Étapes 1 et 2 | Les prix de la baguette et du croissant |
| Étape 3 | aucun outil ne convient : kiwi |
| Issue | termine après 3 étapes, car un échec n’en fait pas deux de suite |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Trois façons de finir un run
Un run peut finir parce que le travail est terminé, parce que l’agent est bloqué ou parce que le budget est épuisé. Chaque issue donne un autre message à l’utilisateur et une autre ligne dans le journal.
Vérifiez-les dans cet ordre. Un run qui finit à sa dernière étape permise est terminé, pas hors budget. Bloqué veut dire des échecs répétés sans progrès.
Une erreur fréquente
Attention
Beaucoup pensent qu’un budget suffit comme règle d’arrêt. Un budget ne fait que plafonner le coût. Il ne distingue pas un run fini d’un run bloqué. Gardez le budget en dernier recours et ajoutez des règles pour terminé et bloqué.