Donner une fenêtre à une tâche annexe
Un sous-agent est un second appel au modèle qui prend une tâche annexe, travaille dans une fenêtre neuve et rend une courte réponse. Le parent ne voit jamais le travail, seulement le résultat.
C’est tout l’intérêt. Une recherche qui lit quarante fichiers remplirait la fenêtre du parent. Faite dans un sous-agent, elle ne lui coûte que quelques lignes.
À la fin de cette leçon
Remarque
Je peux donner à une tâche annexe son propre contexte et n’en reprendre que la réponse.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Sous-agent | un appel au modèle qui fait seul une tâche annexe |
| Isolation | la tâche annexe a sa propre fenêtre |
| Consigne | le court message qui lance un sous-agent |
| Contrat | les champs et la taille que doit avoir un résultat |
| Résultat | ce que le sous-agent rend |
| Parent | l’agent qui a demandé la tâche annexe |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un sous-agent n’est pas un transfert
Dans le module « Construire des agents », un transfert passe tout le travail à un autre agent, qui continue. Un sous-agent est différent. Le parent reste responsable et pose une seule question.
Le sous-agent démarre vide. Il ne voit pas l’historique du parent. Il voit la consigne, et rien d’autre.
Écrire une consigne, puis un contrat
Une bonne consigne donne l’objectif, les faits qui comptent, les fichiers à regarder et la taille de la réponse. Tout ce dont le sous-agent a besoin y figure, car il n’a aucune autre source.
Un contrat dit ce qui revient : des champs nommés et une limite de mots. Le harnais coupe le résultat au contrat avant que le parent le lise. Une longue réponse n’est pas une meilleure réponse.
La forme d’une consigne et d’un résultat
| Partie | Contient | Pourquoi |
|---|---|---|
| Objectif | Une question | Le sous-agent sait quand il a fini |
| Faits | Ce qui est déjà connu | Il ne refait pas le travail |
| Fichiers | Où chercher | Il ne s’égare pas |
| Taille | Une limite de mots | Le résultat reste court |
| Contrat | Des champs et une limite | Le parent lit une forme fixe |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une erreur courante
Attention
Beaucoup passent tout l’historique du parent au sous-agent, par prudence. Plus rien n’est alors isolé et le coût grimpe. Écrivez une consigne avec seulement ce dont la tâche annexe a besoin.
É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 l’exécution du sous-agent, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '// Des mots tiennent lieu de jetons, comme dans la leçon 1.' 'function mots(texte) { return texte.split(\" \").length; }' 'const parent = \"La personne veut corriger le test qui échoue. Nous avons lu app.py, lancé deux fois les tests, vérifié le lisezmoi et parlé de style un moment. Elle demande de garder de petits changements.\";' 'const travail = [' ' \"ls : app.py lisezmoi tests notes.txt ancien.py\",' ' \"grep total : app.py ligne 4 def total articles return sum articles\",' ' \"grep total : tests ligne 9 assert total de 3 articles egale 12\",' ' \"lire tests : 40 lignes de code de test pour le panier\",' ' \"lire app.py : 22 lignes qui calculent un panier et ajoutent la taxe\",' '];' > sous4.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’exécution du sous-agent, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function faireConsigne(objectif, faits, fichiers, max) {' ' const lignes = [\"Objectif : \" + objectif];' ' for (const f of faits) lignes.push(\"Fait : \" + f);' ' for (const f of fichiers) lignes.push(\"Fichier : \" + f);' ' lignes.push(\"Répondez en \" + max + \" mots au plus.\");' ' return lignes.join(\" | \");' '}' 'function couper(texte, max) { return texte.split(\" \").slice(0, max).join(\" \"); }' 'function somme(liste) { return liste.reduce(function (a, b) { return a + b; }, 0); }' >> sous4.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’exécution du sous-agent, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const consigne = faireConsigne(\"Trouver où le total du panier est calculé\", [\"Le test pour 3 articles est faux\"], [\"app.py\", \"tests\"], 20);' ' const echantillon = \"Le total est calculé dans app.py à la ligne 4. Il additionne les articles sans taxe. Voir tests ligne 9.\";' ' const propre = consigne + \" | \" + travail.join(\" ; \");' ' const reponse = await demander([{ role: \"user\", content: propre }], echantillon, 60);' ' const resultat = couper(reponse.texte, 20);' ' console.log(\"fenêtre du parent : \" + mots(parent) + \" mots\");' ' console.log(\"fenêtre de la tâche annexe : \" + mots(propre) + \" mots, dont \" + somme(travail.map(mots)) + \" de résultats d’outils\");' ' console.log(\"résultat gardé : \" + resultat);' ' console.log(\"le parent paie \" + (mots(consigne) + mots(resultat)) + \" mots\");' '}' 'main();' >> sous4.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 sous-agent
docker exec -w /home/node/project agent node sous4.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.
Les résultats d’outils sont enregistrés
Les cinq résultats d’outils sont inventés et enregistrés, à la place de ce qu’un vrai sous-agent lirait. Le court appel au modèle reçoit la consigne et ces résultats, et le Run coupe la réponse à vingt mots.
Sans modèle, il affiche un échantillon enregistré et se sert d’une réponse enregistrée.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| fenêtre du parent : 33 mots | Ce que le parent contient déjà |
| fenêtre de la tâche annexe : 97 mots, dont 57 de résultats d’outils | Où le travail a eu lieu |
| résultat gardé : Le total est calculé dans ... | Vingt mots au plus |
| le parent paie 55 mots | La consigne et le résultat, pas les 57 mots d’outils |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.