Deux agents, une fiche, une écriture perdue
L’état partagé est une fiche que plusieurs agents lisent et écrivent. Cela compte, car deux agents qui lisent tous deux, puis écrivent tous deux, peuvent effacer le travail l’un de l’autre sans bruit.
Rien ne plante. La fiche garde simplement la dernière écriture, et une bonne trouvaille disparaît.
À la fin de cette leçon
Remarque
Je peux laisser des agents partager une fiche sans s’écraser, et signaler les conflits.
Mots-clés
| Mot | Ce que ça veut dire |
|---|---|
| Fiche partagée | une seule copie des faits que tous les agents lisent et écrivent |
| Message | un texte envoyé d’un agent à un autre |
| Version | un nombre qui monte à chaque écriture acceptée |
| Version de base | la version qu’un auteur a lue avant d’écrire |
| Conflit | une écriture fondée sur un champ qui a changé depuis |
| Mise à jour perdue | une écriture qui en efface une autre sans qu’on la voie |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Des messages ou une fiche partagée
Les agents peuvent s’envoyer des messages. Chacun garde sa copie de ce qu’il a entendu et rien n’entre en conflit, mais les copies divergent.
Ou bien ils partagent une fiche. Il y a une seule vérité, mais deux auteurs peuvent se heurter. La plupart des équipes ont besoin des deux : des messages pour dire quoi faire, une fiche pour ce qui est su.
Exemple travaillé : messages ou fiche
| Messages | Fiche partagée | |
|---|---|---|
| Qui détient les faits | Chaque agent, séparément | Un seul endroit |
| Deux agents peuvent-ils diverger | Oui, sans que personne le sache | Non, il y a une valeur |
| Une écriture peut-elle se perdre | Non | Oui, si les écritures ne sont pas vérifiées |
| Le mieux pour | Dire à un exécutant quoi faire | Ce que l’équipe a trouvé jusqu’ici |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une écriture nomme la version lue
Donnez une version à chaque champ. Quand un agent lit, il garde la version. Quand il écrit, il renvoie cette version de base.
La fiche n’accepte l’écriture que si le champ n’a pas changé depuis. Sinon elle répond par un conflit, et l’agent relit puis décide. Un champ que personne n’a écrit ne peut pas entrer en conflit.
Une erreur fréquente
Attention
Beaucoup règlent un conflit en laissant gagner la dernière écriture. C’est la mise à jour perdue, avec une ligne de journal en plus. Un conflit est une question posée à l’auteur : relire la nouvelle valeur, puis décider.
É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 la fiche partagée, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' 'const fiche = { version: 0, champs: {} };' 'function lire() {' ' return { version: fiche.version, champs: JSON.parse(JSON.stringify(fiche.champs)) };' '}' > etat.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 fiche partagée, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Une écriture nomme la version lue. Elle passe seulement si personne n’a changé ce champ depuis.' 'function ecrire(agent, champ, valeur, base) {' ' const f = fiche.champs[champ];' ' const mots = \" écrit \" + champ + \" depuis la version \" + base + \" : \";' ' if (f && f.version > base) {' ' console.log(agent + mots + \"CONFLIT, tenu à la version \" + f.version + \" par \" + f.par);' ' return false;' ' }' ' fiche.version += 1;' ' fiche.champs[champ] = { valeur: valeur, version: fiche.version, par: agent };' ' console.log(agent + mots + \"appliquée, fiche à la version \" + fiche.version);' ' return true;' '}' >> etat.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 fiche partagée, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const a = lire();' ' const b = lire();' ' ecrire(\"chiffreur\", \"cout\", \"108 euros\", a.version);' ' if (ecrire(\"lecteur\", \"cout\", \"120 euros\", b.version) === false) {' ' const encore = lire();' ' console.log(\"lecteur relit : cout vaut \" + encore.champs.cout.valeur + \" à la version \" + encore.version);' ' ecrire(\"lecteur\", \"cout_note\", \"le second chiffre était 120\", encore.version);' ' }' ' ecrire(\"lecteur\", \"veulent\", \"212 sur 400\", b.version);' ' await dernier();' '}' >> etat.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 fiche partagée, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function dernier() {' ' const q = [{ role: \"user\", content: \"Écrivez une note de moins de 12 mots disant que deux chiffres d’un même coût diffèrent.\" }];' ' const note = (await demander(q, \"Deux chiffres d’un même coût diffèrent : vérifiez la source.\", 30)).texte.trim();' ' ecrire(\"relecteur\", \"note\", note, fiche.version);' ' const noms = Object.keys(fiche.champs);' ' console.log(\"Champs : \" + noms.map(function (k) { return k + \"@\" + fiche.champs[k].version; }).join(\", \"));' '}' 'main();' >> etat.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 la fiche partagée
docker exec -w /home/node/project agent node etat.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.
C’est un substitut
Le labo n’a pas de base de données : la fiche est un objet du script. Une vraie équipe utilise une base qui fait cette vérification en une seule étape. Vérifier puis écrire en deux étapes laisse un trou à un autre auteur.
Le seul appel en direct formule une note. Ses mots changent à chaque exécution.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| chiffreur écrit cout depuis la version 0 : appliquée, fiche à la version 1 | Le premier auteur gagne |
| lecteur écrit cout depuis la version 0 : CONFLIT, tenu à la version 1 par chiffreur | Le second avait lu une ancienne version |
| lecteur relit : cout vaut 108 euros à la version 1 | Il relit la fiche avant de décider |
| lecteur écrit cout_note depuis la version 1 : appliquée, fiche à la version 2 | Après relecture, il écrit une note |
| lecteur écrit veulent depuis la version 0 : appliquée, fiche à la version 3 | Un nouveau champ ne peut pas entrer en conflit |
| relecteur écrit note depuis la version 3 : appliquée, fiche à la version 4 | La note du modèle s’ajoute, ses mots changent |
| Champs : cout@1, cout_note@2, veulent@3, note@4 | La version à laquelle chaque champ a été écrit |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.