Un petit assistant qui utilise vos fonctions
Vous avez maintenant toutes les pièces d’un assistant qui utilise des outils. Il lui faut des outils que le modèle sait nommer et une boucle qui les exécute. Il lui faut une limite d’étapes, des erreurs renvoyées comme résultats et une personne qui approuve les appels risqués.
Cette leçon réunit ces pièces dans un seul petit programme.
À la fin de cette leçon
Remarque
Je peux construire un petit assistant qui utilise mes propres fonctions.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Assistant | un programme qui utilise un modèle et des outils pour un travail |
| Boucle | des étapes répétées jusqu’à une réponse ou une limite |
| Aiguilleur | du code qui envoie chaque demande à sa fonction |
| Accord | le oui d’une personne avant un outil d’écriture |
| Garde-fou | une vérification qui empêche un appel de s’exécuter |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un assistant : cinq pièces ensemble
Chaque pièce, vous l’avez déjà construite. Les outils sont vos fonctions et leurs descriptions. La boucle demande, exécute et redemande. L’aiguilleur envoie chaque demande à sa fonction.
Les garde-fous sont la limite d’étapes, les résultats d’erreur et l’accord pour les écritures. Retirez-en un seul, et un échec devient un problème.
Exemple travaillé : contre quoi chaque pièce protège
| Pièce | Ce qu’elle fait | Ce qui va mal sans elle |
|---|---|---|
| Limite d’étapes | S’arrête après trois étapes | Une boucle qui ne finit pas |
| Table d’aiguillage | Refuse un outil inconnu | Une demande exécute autre chose |
| Résultats d’erreur | Renvoie un échec | Un plantage au lieu d’un rattrapage |
| Accord | Demande avant une écriture | Une erreur change vos données |
| Journal | Note chaque appel | Personne ne sait ce qui s’est passé |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Enregistrer les aides pour le modèle et l’accord
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)};const outil=(n,d,k)=>({type:'function',function:{name:n,description:d,parameters:{type:'object',properties:{[k]:{type:'string'}}}}});const ecoute=()=>{const rl=require('readline').createInterface({input:process.stdin});const it=rl[Symbol.asyncIterator]();return{oui:async q=>{process.stdout.write(q+' (o/n) ');const l=await it.next();const ok=l.value=='o';console.log(ok?'o':'n');return ok},fin:()=>rl.close()}};require('fs').writeFileSync('agent.cjs','exports.poser='+poser+';exports.outil='+outil+';exports.ecoute='+ecoute+';');console.log('agent.cjs ok')"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.
Enregistrer les outils de la boutique
docker exec -w /home/node/project agent node -e "const fabriquer=()=>{const{outil}=require('./agent.cjs');const N=[];const k=(i,n,o)=>({role:'assistant',tool_calls:[{id:i,function:{name:n,arguments:JSON.stringify(o)}}]});return{N,T:[outil('prix_de','Prix d’un article.','article'),outil('ajouter_note','Enregistre une note.','texte')],F:{prix_de:a=>({pomme:40,poire:45})[a.article]||'Erreur : introuvable',ajouter_note:a=>N.push(a.texte)},FK:[k('c1','prix_de',{article:'poire'}),k('c2','ajouter_note',{texte:'poire 45 c'}),{role:'assistant',content:'Enregistré.'}]}};require('fs').writeFileSync('outils.cjs','exports.fabriquer='+fabriquer+';');console.log('outils.cjs enregistré')"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.
Vérifiez un appel dans un ordre fixe
Pour chaque demande, posez les mêmes questions dans le même ordre. Le nom est-il dans la table ? Faut-il un accord ? La personne a-t-elle dit o ? Exécutez, puis transformez toute erreur en résultat.
Un appel qui échoue à une question n’atteint jamais la suivante. Chaque issue, exécutée ou non, devient un message d’outil et une ligne du journal.
Faire tourner tout l’assistant
printf 'o\n' | docker exec -i -w /home/node/project agent node -e "const{poser,ecoute}=require('./agent.cjs');const{T,F,N,FK}=require('./outils.cjs').fabriquer();const{oui,fin}=ecoute();let rec=0;(async()=>{const m=[{role:'user',content:'Trouvez le prix d’une poire, puis notez-le.'}];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=FK[e-1]}m.push(a);if(a.tool_calls==null){console.log('Réponse : '+a.content);break}for(const c of a.tool_calls){const n=c.function.name;let r='Erreur : outil inconnu';if(Object.hasOwn(F,n))r=n=='ajouter_note'&&(await oui('Autoriser '+n+' '+c.function.arguments+' ?'))===false?'Refusé par la personne':String(F[n](JSON.parse(c.function.arguments)));console.log('Étape '+e+' : '+n+' -> '+r);m.push({role:'tool',tool_call_id:c.id,content:r})}}console.log('Notes : '+N.join(', '));fin()})()"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 erreur courante
Attention
Beaucoup pensent qu’un assistant est sûr dès que le modèle est bon. Un bon modèle se trompe encore. La sécurité est dans votre code : la table d’outils, la limite d’étapes, l’accord et le journal. Construisez-les d’abord, et faites confiance au modèle ensuite.
Pour aller plus loin : changer le travail
Changez la question dans l’exécution de l’assistant en Notez : acheter du pain. Lancez-la deux fois, une avec o et une avec n fournis d’avance.
Comparez les deux lignes de notes. Écrivez quelle pièce du programme a fait la différence.