Ne pas payer deux fois la même réponse
Mettre en cache une réponse de modèle, c’est la ranger puis la rendre pour la même requête plus tard. C’est utile parce qu’un appel coûte de l’argent et des secondes, et que beaucoup de requêtes se répètent.
Une page d’aide demande au modèle de résumer la même politique de retour pour chaque visiteur. Mille appels paient mille fois la même réponse. Un appel et neuf cent quatre-vingt-dix-neuf réponses trouvées dans le cache, non.
À la fin de cette leçon
Remarque
Je peux mettre des réponses de modèle en cache sans risque et dire exactement ce qui fait qu’une entrée est trouvée.
Mots clés
| Mot | Ce que ça veut dire |
|---|---|
| Cache | un stock de réponses déjà payées |
| Clé de cache | la valeur qui décide si deux requêtes se ressemblent |
| Entrée trouvée | une requête servie depuis le cache |
| Entrée absente | une requête qui doit appeler le modèle |
| Expiration | le moment où une entrée est retirée |
| Donnée personnelle | tout ce qui identifie une personne |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
La clé couvre toute la requête
Deux requêtes ne partagent une réponse que si le modèle voit la même chose. La clé de cache couvre donc tout ce qui change la réponse. C’est le modèle, chaque message avec son rôle, et les paramètres comme la température et la limite de réponse.
Oubliez un élément, et vous servez la mauvaise réponse. Un hachage de ce texte garde la clé courte.
Exemple détaillé : ce qui change la clé
| Changement | Même clé ? | Pourquoi |
|---|---|---|
| Même modèle, mêmes messages et paramètres | Oui | Le modèle voit la même requête |
| Un mot de différence dans un message | Non | Les messages font partie de la clé |
| Température 0, puis 1 | Non | Les réponses peuvent différer |
| Un autre modèle | Non | Un autre modèle répond autrement |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.
Les entrées expirent, le personnel reste dehors
Une réponse vieillit. Donnez à chaque entrée une expiration : une durée de vie en secondes. Ensuite l’entrée est retirée et la requête suivante est absente du cache.
Ne mettez jamais en cache ce qui est personnel. Une réponse construite avec l’adresse ou la commande d’un utilisateur pourrait être donnée au suivant qui a la même clé. Examinez la requête avant la recherche, et n’utilisez pas le cache pour elle.
Une erreur courante
Attention
Beaucoup pensent que le cache est toujours sans risque, puisque la réponse n’est que du texte. Une réponse en cache qui contient les détails d’une personne peut être montrée à une autre. Décidez ce qui peut être mis en cache avant d’en ranger une seule.
É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 le cache, partie 1
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'const crypto = require(\"crypto\");' 'const { demander } = require(\"./model\");' '' 'let maintenant = 0; // des secondes, sur une horloge que nous avançons nous-mêmes' 'const DUREE_DE_VIE = 60; // une entrée vit 60 secondes' 'const cache = new Map();' > cache2.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 cache, partie 2
docker exec -w /home/node/project agent sh -c "printf '%s\n' '// La clé est construite sur toute la requête : modèle, messages et paramètres.' 'function cleDe(requete) {' ' const tout = JSON.stringify([requete.modele, requete.messages, requete.temperature, requete.max_tokens]);' ' return crypto.createHash(\"sha256\").update(tout).digest(\"hex\").slice(0, 12);' '}' '// Une donnée personnelle n’est jamais mise en cache. Ici : une adresse ou un long nombre.' 'function personnelle(requete) {' ' return /@|[0-9]{7,}/.test(JSON.stringify(requete.messages));' '}' >> cache2.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 cache, partie 3
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function repondre(requete, echantillon) {' ' if (personnelle(requete)) return \"ignorée, personnelle\";' ' const cle = cleDe(requete);' ' const trouve = cache.get(cle);' ' if (trouve && trouve.expire > maintenant) return \"trouvé\";' ' const reponse = await demander(requete.messages, echantillon, requete.max_tokens);' ' cache.set(cle, { texte: reponse.texte, expire: maintenant + DUREE_DE_VIE });' ' return trouve ? \"périmé, absent\" : \"absent\";' '}' >> cache2.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 cache, partie 4
docker exec -w /home/node/project agent sh -c "printf '%s\n' 'async function main() {' ' const conseil = [{ role: \"user\", content: \"Donne un conseil sur le cache, en une phrase.\" }];' ' const base = { modele: \"lab\", temperature: 0, max_tokens: 40, messages: conseil };' ' const autre = { ...base, temperature: 1 };' ' const secret = { ...base, messages: [{ role: \"user\", content: \"Écris à [email protected] un conseil.\" }] };' ' const echantillon = \"Ne mettez en cache que ce qui est identique pour tous.\";' ' for (const a of [0, 30, 90]) {' ' maintenant = a;' ' console.log(\"t=\" + a + \" s : \" + (await repondre(base, echantillon)));' ' }' ' console.log(\"même clé avec une autre température : \" + (cleDe(base) === cleDe(autre)));' ' console.log(\"requête personnelle : \" + (await repondre(secret, echantillon)));' '}' 'main();' >> cache2.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 cache
docker exec -w /home/node/project agent node cache2.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.
Ceci est une maquette
Le labo n’a pas de serveur de cache. Le cache est donc une Map dans le script, et l’horloge est un nombre que le script fixe. Un vrai système utilise un vrai service de cache, partagé par tous les serveurs.
Les règles restent les mêmes : une clé sur toute la requête, une expiration, et un contrôle des données personnelles.
Une sortie possible
| Ligne | Ce qu’elle montre |
|---|---|
| t=0 s : absent | La première requête appelle le modèle |
| t=30 s : trouvé | La même requête, servie depuis le cache |
| t=90 s : périmé, absent | L’entrée avait 60 secondes, le modèle est rappelé |
| même clé avec une autre température : false | Un nouveau paramètre est une nouvelle clé |
| requête personnelle : ignorée, personnelle | Le cache n’a pas été touché |
Les cellules que les étudiants remplissent restent vides dans cet aperçu.