Aller au contenu
astorlm
Langue: Français
← Carte

Niveau 8

Cache de prompts

À chaque tour, la boucle renvoie toute la requête au modèle. Presque tout est identique à la fois précédente, et le fournisseur peut s'en souvenir, tant que vous ne changez pas la façon dont elle commence.
1/35 Plis du bandonéon :
  • user
  • assistant
  • tool_result
Soirée jeux. À chaque tour, la boucle renvoie toute la requête à l'Oracle, alors Astor la joue sur le Simon : une lumière par bloc, d'abord le system prompt et les outils, puis chaque message.

EventBus

Le problème

Un modèle ne se souvient de rien entre deux appels, alors la boucle renvoie tout à chaque tour : le system prompt, la liste des outils et tout l'historique. Dans un agent qui fait dix tours, les longues instructions du début voyagent dix fois, et se paient dix fois.

Et elles ralentissent tout : avant d'écrire un seul mot, le modèle doit relire toute la requête, depuis le premier token.

La solution

Les fournisseurs gardent une mémoire de courte durée des requêtes qu'ils viennent de lire. Quand une nouvelle requête commence exactement comme une requête récente, ils réutilisent la partie qui correspond et ne traitent que le reste. Cette partie est facturée une fraction du prix (souvent un dixième, selon le fournisseur et le modèle), et la réponse démarre plus tôt.

La règle tient dans le mot commence : la correspondance se compte depuis le premier token et s'arrête au premier qui diffère. Un agent s'y prête naturellement, car chaque requête est la précédente plus quelques messages à la fin. Il suffit de ne pas la casser :

  • Quelque chose qui change, tout en haut

    l'heure, un id de requête, le nom de l'utilisateur

    Gardez le system prompt fixe. Ce qui change va dans le dernier message, à la fin.

  • Des outils qui bougent

    un autre ordre, un outil ajouté en cours de route

    Construisez la liste d'outils une fois, dans un ordre fixe, et envoyez la même à chaque tour.

  • Réécrire le passé

    modifier, couper ou résumer d'anciens messages

    N'ajoutez qu'à la fin. Quand il faut réécrire, tout ce qui suit ce point est repayé.

  • Changer de modèle

    un modèle moins cher pour un tour

    Chaque modèle a son propre cache. Un changement le fait repartir de zéro.

Certains fournisseurs mettent en cache d'eux-mêmes dès qu'une requête est assez longue : OpenAI le fait à partir de 1 024 tokens. D'autres, comme Anthropic, ne le font que là où vous le marquez. Dans tous les cas, l'usage qu'ils renvoient indique combien de tokens d'entrée viennent du cache : vous pouvez vérifier.

Les personnages

Les mêmes personnages que d'habitude, un soir de jeux.

Le Simon la requête
Chaque manche répète toute la séquence et y ajoute quelque chose à la fin, comme chaque tour renvoie la requête.
Les lumières ses blocs
Jaunes pour le system prompt et les outils, puis une par message, aux couleurs de l'historique.
Astor la boucle
Joue toute la séquence à l'Oracle à chaque tour, puis exécute les outils comme d'habitude.
L'Oracle le modèle
Sa bulle est le cache du fournisseur : les lumières qu'il connaît déjà, à partir de la première.
Le tableau l'usage
Tokens d'entrée envoyés, lus depuis le cache et payés, ceux du cache à un dixième.
Le salon les outils
L'étagère de jeux et le téléphone : game_shelf et order_pizza.

Le code

Avec astorlm : l'agent construit son system prompt une fois et le renvoie tel quel à chaque tour, avec les mêmes outils dans le même ordre, et l'historique ne grandit qu'à la fin. Votre part : garder ce qui change hors de systemPrompt. Chaque turn_end porte cacheReadTokens, que OpenAIProvider lit dans la réponse.

À partir de zéro : la boucle du niveau 2, avec son début fixe construit une fois hors de la boucle, et une ligne qui journalise cached_tokens depuis l'usage de chaque réponse.

import { OpenAIProvider, createLocalAgent, tool } from 'astorlm'
import { z } from 'zod'

const gameShelf = tool({
  name: 'game_shelf',
  description: 'List the board games on the living-room shelf.',
  schema: z.object({}),
  execute: async () => home.shelf(), // your code
})

const orderPizza = tool({
  name: 'order_pizza',
  description: 'Order pizza for delivery. Returns how long it will take.',
  schema: z.object({ size: z.enum(['medium', 'large']), count: z.number().int().min(1) }),
  execute: async ({ size, count }) => pizzeria.order(size, count), // your code
})

const agent = await createLocalAgent({
  // Any OpenAI-compatible endpoint: OpenAI, Ollama, LM Studio, vLLM, a proxy…
  provider: new OpenAIProvider({
    baseURL: 'http://localhost:11434/v1', // e.g. Ollama's default address
    model: 'your-model', // e.g. 'llama3.1', 'gpt-4o-mini'
    apiKey: 'YOUR_API_KEY', // local servers usually ignore it
  }),
  // The start of every request. astorlm builds it once and resends it unchanged every turn.
  // Nothing that changes goes here: no clock, no request id, no user name.
  systemPrompt: HOUSE_RULES, // a long, fixed text: the more of it, the more the cache saves
  tools: [gameShelf, orderPizza], // same tools, same order, every turn
  maxTurns: 10,
})

// OpenAI caches long prompts on its own (from 1,024 tokens), and says how much it reused.
agent.on('event', (event) => {
  if (event.type !== 'turn_end' || !event.usage) return
  const { inputTokens, cacheReadTokens = 0 } = event.usage
  console.log(`turn ${event.turn}: ${cacheReadTokens} of ${inputTokens} input tokens from cache`)
})

// Need the time? Put it at the end, in the message, where it only changes what comes after it.
const now = new Date().toLocaleTimeString()
await agent.run(`Game night for four: see what games we have, and order pizza. (It is ${now}.)`)

Points de vigilance

  • Le cache ne dure pas. Il vit quelques minutes sans usage. Un agent qui attend une personne pendant une heure, ou un heartbeat qui bat toutes les deux heures, repaie sa première requête en entier.
  • La compaction et le cache tirent dans des sens opposés. Réduire les anciens messages, le niveau suivant, réécrit le passé : chaque token après le premier changement est payé en entier au tour suivant. Compactez rarement, et par grandes étapes.
  • Les prompts courts ne sont pas mis en cache. Sous le minimum du fournisseur, il n'y a rien à économiser. Le cache rapporte avec de longs system prompts, beaucoup d'outils ou de longs historiques : exactement ce qu'ont les agents.
  • Mesurez-le. Si cacheReadTokens reste à zéro dès le deuxième tour, quelque chose change au début. Comparez deux requêtes côte à côte et trouvez la première différence.
  • Il n'économise que sur l'entrée. Les tokens que le modèle écrit coûtent autant. Dans les agents, l'entrée fait généralement l'essentiel de la facture : ça reste la plus grosse économie possible.