Une cliente qui change d’avis
Un utilisateur simulé est un script qui joue le client dans une conversation avec votre assistant. C’est utile, car un dialogue n’a pas de réponse unique et saisir des conversations de test à la main ne passe pas à l’échelle.
Camille demande un remboursement, nomme une commande, puis change d’avis. L’assistant suit-il ?
À la fin de cette leçon
Remarque
Je peux tester un dialogue avec un utilisateur scénarisé et noter la conversation.
Mots-clés
| Mot | Ce que ça veut dire |
|---|---|
| Utilisateur simulé | un script qui joue le client |
| Personnage | qui est l’utilisateur et comment il écrit |
| Objectif | ce que l’utilisateur veut à la fin |
| Condition d’arrêt | la règle qui arrête le dialogue |
| Transcription | toute la conversation, ligne par ligne |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Écrire l’utilisateur comme un script
Donnez à l’utilisateur un personnage, un objectif et une liste de lignes. Ajoutez une condition d’arrêt, comme un au revoir ou un nombre de tours maximal, pour que le dialogue s’arrête toujours.
Un script donne le même utilisateur à chaque essai. Un modèle qui jouerait l’utilisateur varierait, coûterait du crédit à chaque ligne et rendrait un échec difficile à reproduire. Commencez par un script.
Exemple travaillé : les quatre lignes de Camille
| Ligne | Ce que dit Camille | Ce que cela teste |
|---|---|---|
| 1 | bonjour, un remboursement svp | l’assistant demande-t-il quelle commande |
| 2 | commande A17 | lance-t-il le remboursement |
| 3 | en fait, plutôt la commande B02 | gère-t-il le revirement |
| 4 | merci, au revoir | termine-t-il le dialogue |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Noter la transcription, pas chaque réponse
Contrôlez trois choses sur toute la transcription. L’objectif est-il atteint ? Les tours restent-ils sous la limite ? L’action a-t-elle eu lieu le bon nombre de fois ?
Le dernier contrôle trouve un défaut discret. Un assistant qui lance un remboursement pour A17 puis un autre pour B02 a atteint l’objectif et remboursé deux fois.
Écrire l’aide au 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 au 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 le pilote du dialogue, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '// Un utilisateur scénarisé : un personnage, un objectif, des lignes, et un revirement à la ligne 3.' 'const utilisateur = {' ' personnage: \"Camille, pressée, écrit des messages courts\",' ' objectif: \"un remboursement pour la commande B02\",' ' lignes: [\"bonjour, un remboursement svp\", \"commande A17\", \"en fait, plutôt la commande B02\", \"merci, au revoir\"],' '};' '// Un assistant de substitution fait de règles. Un vrai serait l’agent à tester.' 'function assistant(texte) {' ' const commande = texte.match(/[AB][0-9][0-9]/);' ' if (commande) return \"Remboursement lancé pour \" + commande[0] + \".\";' ' if (texte.includes(\"revoir\")) return \"Au revoir.\";' ' return \"Quel est le numéro de commande ?\";' '}' > simulation.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 le pilote du dialogue, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function mener(toursMax) {' ' const transcription = [];' ' for (let i = 0; i < utilisateur.lignes.length && i < toursMax; i = i + 1) {' ' transcription.push(\"utilisateur : \" + utilisateur.lignes[i]);' ' const reponse = assistant(utilisateur.lignes[i]);' ' transcription.push(\"assistant : \" + reponse);' ' if (reponse.includes(\"Au revoir\")) break;' ' }' ' return transcription;' '}' 'const transcription = mener(6);' 'transcription.forEach(function (ligne) { console.log(ligne); });' 'const remboursements = transcription.filter(function (ligne) { return ligne.includes(\"Remboursement lancé\"); });' >> simulation.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 le pilote du dialogue, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'console.log(\"Objectif atteint : \" + transcription.join(\" \").includes(\"Remboursement lancé pour B02\"));' 'console.log(\"Tours utilisés : \" + transcription.filter(function (ligne) { return ligne.startsWith(\"utilisateur\"); }).length);' 'console.log(\"Remboursements lancés : \" + remboursements.length + \" (l’utilisateur en voulait un)\");' 'async function main() {' ' const systeme = \"Vous êtes un assistant de support d’une boutique. Demandez le numéro de commande avant tout remboursement. Répondez brièvement.\";' ' const echantillon = \"Bien sûr. Quel est le numéro de commande ?\";' ' const texte = await demander([{ role: \"system\", content: systeme }, { role: \"user\", content: utilisateur.lignes[0] }], echantillon, 60);' ' console.log(\"Réponse en direct : \" + texte.trim());' ' console.log(\"Pose une question sur la commande : \" + texte.toLowerCase().includes(\"commande\"));' '}' 'main();' >> simulation.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.
Piloter l’assistant avec Camille
docker exec -w /home/node/project agent node simulation.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.
Ce que le pilote affiche
| Ligne | Ce qu’elle montre |
|---|---|
| Transcription | Quatre lignes de l’utilisateur et quatre de l’assistant |
| Objectif atteint | true |
| Tours utilisés | 4 |
| Remboursements lancés | 2, l’utilisateur en voulait un |
| Réponse en direct | Une sortie possible : une question sur la commande |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
À vous
Astuce
Dans simulation.js, retirez la troisième ligne, « en fait, plutôt la commande B02 », puis relancez. Les remboursements lancés tombent à 1, mais l’objectif n’est plus atteint. Dites pourquoi.
Une erreur fréquente
Attention
Beaucoup croient qu’un dialogue réussit quand sa dernière réponse sonne juste. Un au revoir poli peut suivre deux remboursements. Notez ce que l’assistant a fait sur toute la transcription : l’objectif, les actions et les tours.