La boucle derrière chaque assistant
Un appel d’outil n’est que la moitié d’une conversation. Le modèle demande un appel, votre programme l’exécute, et le modèle doit voir le résultat avant de pouvoir répondre. L’échange est donc une boucle.
Tout assistant qui utilise des outils fait tourner cette boucle, et elle doit toujours pouvoir s’arrêter.
À la fin de cette leçon
Remarque
Je peux faire tourner la boucle : demander, appeler, répondre, puis redemander.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Boucle | des étapes que le programme répète jusqu’à une condition d’arrêt |
| Historique | la liste des messages envoyée à chaque demande |
| Résultat d’outil | le message qui rapporte au modèle la réponse d’un appel |
| Étape | une demande et sa réponse |
| Limite d’étapes | le nombre maximal d’étapes d’une boucle |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
La boucle redemande après chaque appel
Envoyez les messages et les outils au modèle. Si la réponse contient tool_calls, exécutez chaque appel, ajoutez les résultats aux messages et redemandez. Si la réponse est faite de mots, c’est la réponse finale et la boucle s’arrête.
Le modèle ne garde rien entre deux demandes. Chaque demande porte tout l’historique.
Exemple travaillé : l’historique après chaque étape
| Étape | Ce qui se passe | Messages envoyés ensuite |
|---|---|---|
| Début | L’utilisateur demande le prix d’une poire | user |
| 1 | Le modèle demande prix_de | user, assistant |
| 1 | Votre programme ajoute le résultat | user, assistant, tool |
| 2 | Le modèle répond avec des mots | user, assistant, tool, assistant |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Enregistrer un assistant d’appel du modèle
docker exec -w /home/node/project agent node -e "const poser=async(m,t,c)=>{const r=await fetch((process.env.AI_URL||'http://ai:4000')+'/v1/chat/completions',{method:'POST',headers:{'content-type':'application/json',authorization:'Bearer lab'},body:JSON.stringify({model:'lab',messages:m,tools:t,tool_choice:c,max_tokens:120})});const j=await r.json();if(r.ok)return j.choices[0].message;throw Error(r.status+' '+j.error.message)};require('fs').writeFileSync('ia.cjs','exports.poser='+poser+';');console.log('ia.cjs enregistré : poser(messages, outils) renvoie le message du modèle.')"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.
Un résultat d’outil porte l’id de l’appel
Après un appel, vous ajoutez un message de rôle tool. Il contient le tool_call_id de la demande à laquelle il répond, et le résultat sous forme de texte.
Le modèle se sert de l’id pour relier chaque résultat à sa demande. Sans lui, une réponse à deux appels ne pourrait pas recevoir ses deux résultats.
Faire tourner la boucle, trois étapes au plus
docker exec -w /home/node/project agent node -e "const{poser}=require('./ia.cjs');const P={pomme:40,poire:45};const T=[{type:'function',function:{name:'prix_de',description:'Prix d’un article.',parameters:{type:'object',properties:{article:{type:'string'}}}}}];let rec=0;const faux=m=>m.at(-1).role=='tool'?{role:'assistant',content:'Elle coûte 45 centimes.'}:{role:'assistant',tool_calls:[{id:'c1',function:{name:'prix_de',arguments:JSON.stringify({article:'poire'})}}]};(async()=>{const m=[{role:'user',content:'Prix d’une poire ?'}];for(let e=1;e<4;e++){let a;try{if(rec)throw 0;a=await poser(m,T)}catch(x){rec||console.log('Refus ('+x.message+'). Exemple enregistré :');rec=1;a=faux(m)}m.push(a);if(a.tool_calls==null){console.log('Réponse : '+a.content);return}for(const c of a.tool_calls){const v=P[JSON.parse(c.function.arguments).article];console.log('Étape '+e+' : '+c.function.name+' -> '+v);m.push({role:'tool',tool_call_id:c.id,content:String(v)})}}console.log('Limite atteinte')})()"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.
Une limite d’étapes arrête une boucle folle
Un modèle peut redemander un appel encore et encore. Chaque étape coûte du temps et du crédit : vous comptez donc les étapes et vous vous arrêtez à une limite. Trois est un bon début.
Quand la limite arrive d’abord, votre programme le dit. Il ne fait pas semblant d’avoir une réponse.
Une erreur courante
Attention
Beaucoup pensent que le modèle se souvient des demandes précédentes. Ce n’est pas le cas. Il ne voit que les messages que vous envoyez. Si vous oubliez le résultat de l’outil, le modèle ne peut pas savoir ce que l’appel a renvoyé : gardez tous les messages.
Pour aller plus loin : changer la question
Changez la question dans l’exécution en Prix d’une pomme et d’une poire ?. Lancez-la trois fois et comptez les étapes à chaque fois.
Ensuite, changez e<4 en e<2. Écrivez ce que le programme affiche quand la limite arrive d’abord.