Un exécutant ne connaît que sa consigne
Le schéma orchestrateur-exécutants est un programme où un agent découpe une tâche en consignes, confie chacune à un exécutant et rassemble les résultats. Cela compte, car un exécutant ne sait rien d’autre que sa consigne.
Une consigne vague donne une réponse assurée à la mauvaise question. Une consigne autonome donne une réponse vérifiable.
À la fin de cette leçon
Remarque
Je peux découper une tâche en consignes autonomes et vérifier chaque résultat avant de les rassembler.
Mots-clés
| Mot | Ce que ça veut dire |
|---|---|
| Consigne | tout ce qu’un exécutant reçoit : question, matériel et forme de la réponse |
| Forme du résultat | les champs qu’un exécutant doit renvoyer |
| Matériel | le texte qu’un exécutant peut utiliser pour répondre |
| Validation | la vérification d’un résultat avant son emploi |
| Jointure | l’assemblage des résultats acceptés |
| Source inconnue | une source citée par un exécutant mais jamais donnée |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Ce que contient une consigne
Un exécutant ne voit ni la tâche, ni les autres consignes, ni la conversation. Ce qui n’est pas dans la consigne n’existe pas.
Une consigne contient donc la question, le matériel autorisé, la forme de la réponse et une limite de taille. Le matériel est le texte lui-même, pas la promesse que l’exécutant ira le chercher.
Exemple travaillé : une consigne
| Champ | Exemple | Pourquoi il est là |
|---|---|---|
| question | Que coûterait un dimanche en temps de personnel ? | Une question, pas toute la tâche |
| matériel | La fiche de coût du personnel, en texte | L’exécutant ne peut rien utiliser d’autre |
| sources | s2 | Pour que la réponse dise d’où elle vient |
| réponse | Un objet avec réponse et sources | Pour que l’orchestrateur la vérifie |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Validez un résultat avant de le joindre
Un résultat est une donnée venue d’une partie du système que vous ne contrôlez pas. Vérifiez d’abord la forme : y a-t-il une réponse, une liste de sources ? Puis vérifiez que chaque source figure dans la consigne.
Ne joignez que ce qui passe. Le module précédent faisait passer un message d’un agent à l’autre. Ici, on décide s’il faut croire ce qui revient.
Une erreur fréquente
Attention
Beaucoup joignent tous les résultats et réparent le désordre ensuite. Un mauvais résultat pèse alors autant qu’un bon dans la réponse finale. Refusez à l’entrée et gardez la raison.
É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’orchestrateur, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const SOURCES = {' ' s1: \"Sondage : 212 visiteurs sur 400 ont demandé l’ouverture le dimanche.\",' ' s2: \"Personnel : six heures à 18 euros de l’heure font 108 euros chaque dimanche.\",' ' s3: \"Visites : le samedi a compté 310 visites le mois dernier, un jour de semaine 120.\",' '};' '// Une consigne porte tout ce que son exécutant peut utiliser : la question et le texte.' 'function consigne(id, question, ids) {' ' const materiel = ids.map(function (i) { return SOURCES[i]; });' ' return { id: id, question: question, sources: ids, materiel: materiel, reponse: \"un objet avec reponse et sources\" };' '}' > equipe.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’orchestrateur, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Des exécutants scriptés. Chacun ne voit que sa consigne.' 'const EXECUTANTS = {' ' b1: function (c) { return { reponse: \"212 visiteurs sur 400 la veulent\", sources: [\"s1\"] }; },' ' b2: function (c) { return { reponse: \"108 euros chaque dimanche\", sources: [\"s2\"] }; },' ' b3: function (c) { return { reponse: \"Les week-ends sont chargés\", sources: [\"s9\"] }; },' '};' 'function problemes(c, r) {' ' const sortie = [];' ' const plein = typeof r.reponse === \"string\" && r.reponse.trim().length > 0;' ' if (plein === false) sortie.push(\"réponse absente\");' ' if (Array.isArray(r.sources) === false || r.sources.length === 0) sortie.push(\"sources absentes\");' ' else r.sources.forEach(function (s) { if (c.sources.includes(s) === false) sortie.push(\"source inconnue \" + s); });' ' return sortie;' '}' >> equipe.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’orchestrateur, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const propre = \"Quelle est l’affluence de la bibliothèque un jour de week-end ?\";' ' const q = [{ role: \"user\", content: \"Écrivez une courte question de recherche sur l’affluence d’une bibliothèque le week-end.\" }];' ' const texte = (await demander(q, propre, 30)).texte.trim();' ' const consignes = [' ' consigne(\"b1\", \"Combien de visiteurs veulent l’ouverture le dimanche ?\", [\"s1\"]),' ' consigne(\"b2\", \"Que coûterait un dimanche en temps de personnel ?\", [\"s2\"]),' ' consigne(\"b3\", texte.endsWith(\"?\") ? texte : propre, [\"s3\"]),' ' ];' ' for (const c of consignes) {' ' const r = EXECUTANTS[c.id](c);' ' const p = problemes(c, r);' ' console.log(c.id + \" [\" + c.materiel.length + \" source] \" + c.question);' ' console.log(\" \" + (p.length ? \"REFUSÉE : \" + p.join(\", \") : \"acceptée : \" + r.reponse));' ' }' '}' 'main();' >> equipe.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’orchestrateur
docker exec -w /home/node/project agent node equipe.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 exécutants sont scriptés
Le labo n’a qu’un modèle, donc une vraie équipe serait un seul modèle jouant tous les rôles. Les trois exécutants ici sont de simples fonctions aux réponses enregistrées. Le seul appel en direct formule une question de recherche.
Un vrai système envoie chaque consigne à un modèle, et la vérification reste la même.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| b1 [1 source] Combien de visiteurs veulent l’ouverture le dimanche ? | Une consigne avec son matériel |
| acceptée : 212 visiteurs sur 400 la veulent | La forme et les sources passent |
| acceptée : 108 euros chaque dimanche | Le deuxième exécutant passe aussi |
| REFUSÉE : source inconnue s9 | Le troisième cite une source qu’il n’a jamais reçue |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.