Deux agents qui se renvoyaient un ticket
Un système multi-agents répartit le travail entre des agents qui ont chacun un rôle. Ils se passent le travail par des messages de transfert. C’est important, car chaque transfert coûte un appel et peut faire perdre du contexte.
Un agent de facturation envoie un ticket à un agent de stock. L’agent de stock le renvoie aussitôt. Le ticket a rebondi toute la nuit.
À la fin de cette leçon
Remarque
Je peux répartir le travail entre des agents et dire quand un seul agent suffit.
Mots-clés
| Mot | Ce que ça veut dire | Exemple |
|---|---|---|
| Rôle | l’unique tâche d’un agent | prix |
| Transfert | un message qui passe le travail à un autre agent | |
| Aiguilleur | la partie qui choisit le rôle d’une tâche | |
| Registre partagé | un journal que tous les agents lisent et complètent | |
| Garde-fou | un contrôle qui empêche le travail de rebondir sans fin |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Quand un seul agent suffit
Commencez avec un agent et une consigne. N’en ajoutez un deuxième que si vous savez nommer le gain. Ce peut être une consigne plus courte, moins d’outils en vue, un autre modèle ou une étape qu’une personne doit valider.
Chaque agent de plus ajoute des appels, du délai et un endroit où le contexte fuit. Si un agent avec trois outils fait le travail, arrêtez-vous là.
Exemple travaillé : un agent ou plusieurs
| Situation | Choix | Raison |
|---|---|---|
| Chercher un prix et le dire | Un agent | Un outil, une consigne |
| Règles de prix, de stock et de livraison, 20 outils | Des rôles | Chaque rôle ne voit que ses outils |
| Un remboursement de plus de 100 euros | Une étape humaine | Valider n’est pas un travail d’agent |
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 la démo de transferts, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const PRIX = { baguette: 110, croissant: 130 };' 'const STOCK = { baguette: 12, croissant: 0 };' 'function chercher(table, q, unite) {' ' const cle = Object.keys(table).find(function (w) { return q.indexOf(w) >= 0; });' ' return cle === undefined ? \"rien trouvé\" : cle + \" : \" + table[cle] + unite;' '}' 'const roles = {' ' prix: function (q) { return chercher(PRIX, q, \" centimes\"); },' ' stock: function (q) { return chercher(STOCK, q, \" en stock\"); },' '};' 'function orienter(q) {' ' if (q.indexOf(\"combien\") >= 0) return \"prix\";' ' if (q.indexOf(\"en stock\") >= 0) return \"stock\";' ' return null;' '}' > transfert1.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 démo de transferts, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function choisir(q) {' ' const regle = orienter(q);' ' if (regle) return regle;' ' const texte = \"Quel rôle convient à cette tâche : prix, stock ou aucun ? Répondez par un seul mot. Tâche : \" + q;' ' const mot = (await demander([{ role: \"user\", content: texte }], \"aucun\", 10)).trim().toLowerCase();' ' return mot === \"prix\" || mot === \"stock\" ? mot : \"aucun\";' '}' 'const piste = [];' 'function permis(de, vers) {' ' piste.push(de + \">\" + vers);' ' return piste.filter(function (t) { return t === de + \">\" + vers; }).length < 3;' '}' >> transfert1.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 démo de transferts, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const registre = [];' ' for (const q of [\"combien coûte une baguette\", \"reste-t-il des croissants en stock\", \"quel temps fait-il\"]) {' ' const role = await choisir(q);' ' const resultat = role === \"aucun\" ? \"aucun rôle ne convient, demandez à une personne\" : roles[role](q);' ' registre.push({ vers: role, tache: q, resultat: resultat });' ' }' ' registre.forEach(function (r) { console.log(r.vers + \" | \" + r.tache + \" | \" + r.resultat); });' ' let qui = \"prix\";' ' for (let i = 0; i < 6; i++) {' ' const suivant = qui === \"prix\" ? \"stock\" : \"prix\";' ' if (permis(qui, suivant) === false) { console.log(\"Boucle : \" + qui + \" vers \" + suivant + \" une troisième fois, arrêt\"); break; }' ' qui = suivant;' ' }' '}' 'main();' >> transfert1.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 et lire le registre partagé
docker exec -w /home/node/project agent node transfert1.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 |
|---|---|
| prix | combien coûte une baguette | baguette : 110 centimes |
| stock | reste-t-il des croissants | croissant : 0 en stock |
| La tâche sur la météo | aucun, ou un rôle choisi par le modèle, avec un résultat faible |
| Boucle | une ligne disant qu’une paire est apparue une troisième fois |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un transfert est un message structuré
Un message de transfert nomme l’expéditeur, le destinataire, la tâche et le contexte dont le destinataire a besoin. Il ne doit pas avoir à relire tout l’historique de l’expéditeur.
Les agents notent ce qu’ils ont fait dans un registre partagé. C’est l’endroit unique où se trouve la vérité sur le travail.
Empêcher le travail de rebondir entre agents
Deux agents peuvent chacun décider que l’autre doit traiter une tâche. Rien n’est faux dans l’un ni dans l’autre, et pourtant le travail ne finit jamais.
Comptez chaque paire de l’expéditeur au destinataire. Quand une paire atteint une limite, arrêtez et confiez le travail à une personne. Gardez la piste des transferts dans le registre pour voir la boucle.
Une erreur fréquente
Attention
Beaucoup pensent qu’un aiguilleur doit toujours interroger un modèle. Un appel de modèle est plus lent, coûte de l’argent et peut nommer un rôle qui n’existe pas. Utilisez des règles quand elles sont exactes, interrogez le modèle pour le reste et vérifiez ce qu’il nomme.