Le modèle demande un fichier
Limiter les outils d’un modèle, c’est faire décider votre code, et non le modèle, de ce que chaque outil peut faire. Cela compte, car la demande d’un modèle est du texte, et un texte peut être faux, trompé ou inventé.
Un assistant de notes a un outil qui lit une note par son nom. Une réponse demande la note « ../prive ». L’outil essaierait volontiers. Une politique l’arrête avant qu’il parte.
À la fin de cette leçon
Remarque
Je peux vérifier chaque demande d’outil contre une politique de liste autorisée avant que rien ne s’exécute.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Liste autorisée | les seuls outils et valeurs permis |
| Politique | des données qui disent ce que chaque outil peut recevoir |
| Contrôle d’argument | un test des entrées d’un outil avant qu’il s’exécute |
| Moindre privilège | ne donner à un outil que le pouvoir dont il a besoin |
| Délai maximal | une limite à la durée d’un appel |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Une liste autorisée dit oui, jamais non
Une liste d’interdits nomme les mauvaises choses et laisse passer le reste. Elle en oublie toujours une. Une liste autorisée nomme les bonnes choses et refuse le reste.
Écrivez une politique en données : chaque outil, chaque paramètre, son type et ses limites. Une fonction lit la politique et juge chaque demande. Le modèle n’exécute jamais rien lui-même.
Exemple : une politique et quatre demandes
| Demande | La politique dit | Décision |
|---|---|---|
| lire_note, nom menu | nom doit être courses, menu ou horaire | Autorisée |
| lire_note, nom ../prive | nom doit être courses, menu ou horaire | Refusée : non autorisé |
| lancer_commande, un texte | Cet outil n’existe pas | Refusée : outil inconnu |
| additionner, b 99999 | b vaut au plus 1000 | Refusée : trop grand |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Pas de commande bâtie sur un texte
Certains outils tentent : lancer une commande, évaluer une expression, écrire un fichier. N’en construisez aucun à partir d’un texte écrit par un modèle. Un refus vaut mieux qu’un filtre astucieux.
Si une fonction doit faire tourner du vrai code, faites-le dans un bac à sable séparé. Pas de secrets, et une limite de temps. Ce labo n’en a pas, donc la leçon ne fait que refuser.
Écrire l’aide 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\";' '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 du 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 script de politique, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '' '// Des notes inventées, et une politique : chaque outil, ses arguments et leurs limites.' 'const notes = { courses: \"acheter de la farine\", menu: \"pain, gâteau\", horaire: \"ouvert à 8 h\" };' 'const politique = {' ' lire_note: { nom: { type: \"string\", autorises: Object.keys(notes) } },' ' additionner: { a: { type: \"number\", max: 1000 }, b: { type: \"number\", max: 1000 } },' '};' > politique.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 script de politique, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function verifier(demande) {' ' const regle = politique[demande.outil];' ' if (regle === undefined) return \"refusé : outil inconnu\";' ' const parametres = demande.parametres || {};' ' for (const cle of Object.keys(parametres)) {' ' if (regle[cle] === undefined) return \"refusé : argument inattendu \" + cle;' ' }' ' for (const cle of Object.keys(regle)) {' ' const modele = regle[cle];' ' const valeur = parametres[cle];' ' if (typeof valeur === modele.type === false) return \"refusé : \" + cle + \" incorrect\";' ' if (modele.autorises && modele.autorises.includes(valeur) === false) return \"refusé : \" + cle + \" non autorisé\";' ' if (typeof modele.max === \"number\" && valeur > modele.max) return \"refusé : \" + cle + \" trop grand\";' ' }' ' return \"ok\";' '}' >> politique.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 script de politique, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function executer(demande) {' ' if (verifier(demande) === \"ok\") {' ' return demande.outil === \"lire_note\" ? notes[demande.parametres.nom] : demande.parametres.a + demande.parametres.b;' ' }' ' return verifier(demande);' '}' 'const scenario = [' ' { outil: \"lire_note\", parametres: { nom: \"menu\" } },' ' { outil: \"lire_note\", parametres: { nom: \"../prive\" } },' ' { outil: \"lancer_commande\", parametres: { texte: \"lister le dossier\" } },' ' { outil: \"additionner\", parametres: { a: 5, b: 99999 } },' ' { outil: \"additionner\", parametres: { a: 2, b: 3, c: 4 } },' '];' 'for (const demande of scenario) console.log(demande.outil + \" -> \" + executer(demande));' >> politique.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 script de politique, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'function extraireJson(texte) {' ' const debut = texte.indexOf(\"{\");' ' const fin = texte.lastIndexOf(\"}\");' ' try { return JSON.parse(texte.slice(debut, fin + 1)); } catch (erreur) { return null; }' '}' 'async function main() {' ' const question = [{ role: \"user\", content: \"Réponds uniquement par un objet JSON avec les clés outil et parametres, pour lire la note nommée horaire. Outils : lire_note(nom), additionner(a, b).\" }];' ' const echantillon = JSON.stringify({ outil: \"lire_note\", parametres: { nom: \"horaire\" } });' ' const demande = extraireJson(await demander(question, echantillon, 80));' ' console.log(\"Demande du modèle : \" + JSON.stringify(demande));' ' console.log(\"Résultat : \" + (demande === null ? \"refusé : pas du JSON\" : executer(demande)));' '}' 'main();' >> politique.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 le script de politique
docker exec -w /home/node/project agent node politique.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.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| lire_note -> pain, gâteau | La première demande a passé tous les contrôles |
| lire_note -> refusé : nom non autorisé | L’astuce de chemin a échoué devant la liste autorisée |
| lancer_commande -> refusé : outil inconnu | L’outil n’est pas dans la politique |
| Demande du modèle, puis Résultat | Change à chaque exécution. Une demande d’outil du modèle passe par le même contrôle |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Limitez le temps et le pouvoir
Un délai maximal empêche un outil lent de retenir une requête pour toujours. L’aide que vous avez écrite abandonne déjà le modèle après 30 secondes.
Appliquez aussi le moindre privilège. Un outil qui lit des notes ne doit pas pouvoir en écrire, et un outil qui écrit ne doit atteindre que son propre dossier.
Une erreur fréquente
Attention
Beaucoup pensent que, si on a dit les règles au modèle, l’outil n’a pas besoin de vérifier. Le modèle peut se tromper, être trompé ou inventer un outil. Vérifiez toujours dans le code qui lance l’outil, car c’est la seule partie qui fait la même chose à chaque fois.