Envoyer chaque requête au moins cher qui suffit
Aiguiller les modèles, c’est choisir, pour chaque requête, le modèle le moins cher qui suffit. C’est utile parce que le plus cher coûte plusieurs fois le plus petit, et que la plupart des requêtes n’en ont pas besoin.
Un outil d’assistance classe des tickets, répond à des questions courtes et rédige de longues réponses. Classer n’exige pas le modèle qui rédige. Tout envoyer à un grand modèle paie une puissance que presque aucune requête n’emploie.
À la fin de cette leçon
Remarque
Je peux aiguiller chaque requête vers le modèle le moins cher qui suffit et vérifier ce choix sur des cas enregistrés.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Aiguilleur | la règle qui choisit un modèle pour une requête |
| Niveau | un groupe de modèles par taille et par prix |
| Suffisant | juste assez souvent pour cette tâche |
| Cas enregistré | une requête avec le niveau choisi par une personne |
| Désaccord | un cas où l’aiguilleur et la personne diffèrent |
| Enveloppe | le code autour d’un appel qui y ajoute les règles |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Un aiguilleur est une règle simple
Commencez par une règle lisible. Un texte court ou une tâche de classement va au niveau petit. Un texte long ou une tâche de raisonnement va au niveau grand. Le reste va au niveau moyen.
Une règle sur la taille et la tâche est bon marché, instantanée et facile à expliquer. On peut aussi demander à un petit modèle de choisir, mais cela ajoute un appel à chaque requête : essayez d’abord la règle.
Exemple détaillé : la règle sur quatre requêtes
| Requête | Tâche | Taille | Niveau |
|---|---|---|---|
| Classer un ticket | classer | 150 signes | petit |
| Résumer une page | résumer | 1500 signes | moyen |
| Planifier une migration | raisonner | 900 signes | grand |
| Résumer un long rapport | résumer | 5000 signes | grand |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Vérifier la règle sur des cas
Un aiguilleur fait économiser de l’argent et peut coûter de la qualité : testez-le. Gardez des cas enregistrés : une requête, sa taille et sa tâche, et le niveau qu’une personne juge nécessaire. Passez l’aiguilleur sur ces cas. Listez les désaccords et totalisez le coût.
Un désaccord n’est pas toujours un défaut de la règle. Lisez chacun. Puis changez la règle, ou changez le cas.
Une erreur courante
Attention
Beaucoup pensent que le modèle le moins cher est toujours la meilleure économie. Un modèle bon marché qui répond mal coûte un nouvel essai, une plainte ou un utilisateur perdu. Descendez de niveau seulement là où les cas enregistrés montrent que la réponse reste suffisante.
É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 vérification, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const { demander } = require(\"./model\");' '' '// Prix inventés : des euros par million de jetons, donc jetons fois prix = micro-euros.' 'const PRIX = { petit: { entree: 1, sortie: 4 }, moyen: { entree: 3, sortie: 12 }, grand: { entree: 10, sortie: 40 } };' 'function aiguiller(requete) {' ' if (requete.tache === \"classer\" || requete.signes <= 200) return \"petit\";' ' if (requete.tache === \"raisonner\" || requete.signes > 2000) return \"grand\";' ' return \"moyen\";' '}' 'function cout(niveau, c) { return c.entree * PRIX[niveau].entree + c.sortie * PRIX[niveau].sortie; }' > route6.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 vérification, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// Des cas enregistrés : ce qu’est chaque requête, et le niveau qu’un relecteur juge nécessaire.' 'const CAS = [' ' { id: 1, tache: \"classer\", signes: 150, entree: 60, sortie: 5, attendu: \"petit\" },' ' { id: 2, tache: \"question\", signes: 180, entree: 70, sortie: 40, attendu: \"petit\" },' ' { id: 3, tache: \"resumer\", signes: 1500, entree: 500, sortie: 150, attendu: \"moyen\" },' ' { id: 4, tache: \"raisonner\", signes: 900, entree: 400, sortie: 600, attendu: \"grand\" },' '];' >> route6.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 vérification, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const SUITE = [' ' { id: 5, tache: \"resumer\", signes: 5000, entree: 1400, sortie: 300, attendu: \"grand\" },' ' { id: 6, tache: \"classer\", signes: 3000, entree: 800, sortie: 5, attendu: \"petit\" },' ' { id: 7, tache: \"question\", signes: 700, entree: 250, sortie: 120, attendu: \"moyen\" },' ' { id: 8, tache: \"resumer\", signes: 190, entree: 80, sortie: 30, attendu: \"moyen\" },' '];' >> route6.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 vérification, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const tous = CAS.concat(SUITE);' ' let total = 0, cher = 0, ecarts = [];' ' for (const c of tous) {' ' const niveau = aiguiller(c);' ' total = total + cout(niveau, c);' ' cher = cher + cout(\"grand\", c);' ' if (niveau === c.attendu) console.log(\"#\" + c.id + \" \" + c.tache + \" : \" + niveau + \" ok\");' ' else { ecarts.push(c.id); console.log(\"#\" + c.id + \" \" + c.tache + \" : \" + niveau + \", attendu \" + c.attendu); }' ' }' ' console.log(\"Désaccords : \" + ecarts.length + \" sur \" + tous.length + \", cas \" + ecarts.join(\", \"));' ' console.log(\"Coût aiguillé : \" + total + \". Tout sur grand : \" + cher + \". Économisé : \" + (cher - total));' >> route6.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 vérification, partie 5
docker exec -w /home/node/project agent sh -c "printf '%s\n' ' const r = await demander([{ role: \"user\", content: \"Classe en facturation, bogue ou autre : j’ai été débité deux fois.\" }], \"facturation\", 10);' ' console.log(\"Aiguillé vers \" + aiguiller(tous[0]) + \", réponse : \" + r.texte.slice(0, 40));' '}' 'main();' >> route6.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 vérification
docker exec -w /home/node/project agent node route6.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.
Le labo n’a qu’un modèle
Dans le labo, un seul modèle répond quel que soit le niveau choisi par la règle, et la plateforme le choisit. Le Run montre donc le choix et le coût, et l’unique appel réel montre seulement qu’une requête aiguillée reçoit une réponse.
Les prix et les huit cas sont inventés et enregistrés, donc chaque étudiant affiche les mêmes lignes.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| #1 classer : petit ok | L’aiguilleur et la personne sont d’accord |
| #8 resumer : petit, attendu moyen | Un désaccord à lire et à trancher |
| Désaccords : 1 sur 8, cas 8 | Le nombre et les numéros de cas |
| Coût aiguillé : 60820. Tout sur grand : 85600. Économisé : 24780 | Le total, en micro-euros |
| Aiguillé vers petit, réponse : ... | Un appel réel. Les mots changent à chaque Run |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.