Documentation

Documentation

Mise en cache des prompts

Marquez une fois un préfixe de prompt stable avec cache_control — ou appelez un modèle Claude au format OpenAI, qui ne dispose pas d’un tel champ, et Kunavo placera les points de rupture pour vous. Chaque appel ultérieur qui réutilise le préfixe le facture à une fraction du prix d’entrée — 10% du prix d’entrée sur la plupart des modèles conversationnels, avec un minimum de 2.5%. La couche de routage de Kunavo maintient le cache actif en épinglant votre conversation à la même clé en amont.

Avec une invite système de 30 k tokens et Claude Sonnet 5, la lecture du cache coûte $0.14 / 1M au lieu de $1.4 / 1M. Pour un agent qui renvoie le même contexte 50 fois au cours d’une session, le cache permet d’économiser ~90 % de la facture d’entrée.

Comment l’activer

Ajoutez un point de rupture cache_control: { type: "ephemeral" } à n’importe quel bloc système, message ou définition d’outil. Lors du premier appel, le modèle met en cache la portion allant jusqu’à ce point ; les appels ultérieurs partageant les mêmes octets de préfixe utiliseront le cache.

cache_example.py
from openai import OpenAI

client = OpenAI(
    api_key="sk-kn-...",
    base_url="https://api.kunavo.com/v1",
)

# Mark the large, stable prefix once with cache_control.
# Every subsequent call that sends the same prefix reads it back
# from cache at 0.10× the input rate on Claude Sonnet 5.
# Per-model rates are in the pricing table below.
SYSTEM = [
    {
        "type": "text",
        "text": open("long_system_prompt.txt").read(),
        "cache_control": {"type": "ephemeral"},
    },
]

resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[
        {"role": "system", "content": SYSTEM},
        {"role": "user", "content": "Summarize the doc above in 3 bullets."},
    ],
)

u = resp.usage
# OpenAI-compat fields populated by Kunavo:
# - prompt_tokens_details.cached_tokens — read from cache
# - cache_creation_input_tokens          — written to cache this call
print(u.prompt_tokens_details.cached_tokens,
      getattr(u, "cache_creation_input_tokens", 0))

Format filaire identique sur l’API Messages native — cache_control est transmis à Claude sans traduction :

messages_api.sh
# Native Anthropic Messages API — cache_control passes through untranslated.
curl https://api.kunavo.com/v1/messages \
  -H "Authorization: Bearer $KUNAVO_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "claude-sonnet-5",
    "system": [
      {
        "type": "text",
        "text": "<your large stable system prompt>",
        "cache_control": { "type": "ephemeral" }
      }
    ],
    "messages": [
      { "role": "user", "content": "Summarize the doc above in 3 bullets." }
    ],
    "max_tokens": 1024
  }'
La stabilité des octets est essentielle. La clé de cache est basée sur le contenu. Un seul caractère différent, où que ce soit dans le préfixe, invalide le cache pour cette portion. Placez le contenu dynamique (horodatages, noms d’utilisateur, identifiants propres à chaque appel) après le bloc mis en cache, et non à l’intérieur.

Points de rupture automatiques sur les points de terminaison au format OpenAI

Les formats OpenAI Chat Completions et Responses ne comportent aucun champ cache_control — OpenAI met implicitement en cache sur ses propres serveurs. Ce n’est pas le cas de Claude : il ne met en cache que ce qu’un point de rupture marque. Un client utilisant OpenAI ne peut donc pas demander la mise en cache, et une traduction directe de sa requête arrive à Claude sans aucun point de rupture : chaque tour d’une longue conversation refacture tout l’historique au tarif d’entrée complet.

Ainsi, lorsque vous appelez un modèle Claude via /v1/chat/completions ou /v1/responses, Kunavo définit les points de rupture que le protocole ne peut pas transmettre. Vous n’avez rien à activer. Cela s’applique uniquement aux modèles Claude — toutes les autres familles utilisent un protocole dont le fournisseur met implicitement en cache, sans qu’il soit nécessaire d’ajouter des points de rupture.

no_cache_control.py
# Plain OpenAI Chat Completions — no cache_control anywhere in the body.
# The format has no field for one; Kunavo inserts the breakpoints instead.
resp = client.chat.completions.create(
    model="claude-sonnet-5",
    messages=[
        {"role": "system", "content": LONG_SYSTEM_PROMPT},
        {"role": "user", "content": "first question"},
        {"role": "assistant", "content": "...previous answer..."},
        {"role": "user", "content": "follow-up"},
    ],
)

u = resp.usage
# Turn that writes the cache:
#   u.cache_creation_input_tokens            -> 18,000
#   u.prompt_tokens_details.cached_tokens    ->      0
# Next turn, same prefix, inside the 5-minute window:
#   u.cache_creation_input_tokens            ->    240   (only the new turn)
#   u.prompt_tokens_details.cached_tokens    -> 18,000
#
# Both stuck at 0 on a conversation well over the threshold? The prefix is
# changing between calls — a timestamp or request id inside the cached span.

Leur emplacement et l’ordre dans lequel le quota est utilisé :

  • Un point de rupture glissant sur le dernier message — sur le dernier bloc de contenu du dernier message, afin de mettre en cache tout ce qui le précède ; si la requête se termine par un message système qui reste dans la conversation (voir plus bas), il se place plutôt sur le bloc situé juste avant ce message. Ajouté seulement lorsque messages contient déjà un tour de l’assistant : une écriture dans le cache est facturée à 1.25× du tarif d’entrée ; marquer un prompt qui ne sera jamais renvoyé coûte donc un quart de plus sans rien économiser. Un tour précédent de l’assistant prouve que ce préfixe précis sera bientôt réutilisé.
  • Un point sur system, un sur tools — le début stable du prompt. Cela vaut la peine de le marquer même lors d’un appel unique, car un appelant qui envoie un prompt système une fois en enverra presque toujours un identique lors de l’appel suivant.
  • Devant un message système qui reste dans la conversation — ce que le point glissant et les points sur system et tools laissent du quota, sur le bloc situé juste avant le dernier message de ce type suivi d’autre chose, puis devant le précédent. Rempli en dernier, car seul un client qui déplace un tel message d’un tour à l’autre en a besoin ; une conversation sans message de ce type, c’est-à-dire presque toutes, n’a pas cette position.

Deux limites s’appliquent. Si le prompt — messages, system et schémas d’outils réunis — fait moins de 8,192 caractères, rien n’est inséré : Anthropic ignore silencieusement un point de rupture placé sur un préfixe inférieur à la longueur minimale pouvant être mise en cache (1,024 jetons, 2,048 sur Haiku) et ne facture ni écriture ni lecture ; en ajouter un ne servirait donc à rien. Anthropic accepte également au maximum 4 points de rupture par requête — un cinquième entraîne une erreur 400 bloquante, et non une absence d’effet silencieuse.

Un message système ou developer envoyé après la première réponse du modèle reste dans la conversation, dans le tour où il a été envoyé, au lieu d’être remonté dans system : le remonter changerait le début du prompt au tour où il est ajouté et réécrirait tout l’historique dans le cache. Claude le reçoit comme message système lorsque le fournisseur en amont en accepte un, et sinon comme texte dans ce tour utilisateur, entre des balises <system-reminder>. Un message système envoyé avant la première réponse du modèle, ou dont le propre point de rupture demande le cache d’une heure, va toujours dans system.

Vos propres points de rupture ne sont jamais déplacés ni supprimés. Ils comptent dans la même limite de 4, et Kunavo ne remplit que les positions que vous avez laissées libres. Si vous en envoyez 4, aucun n’est ajouté ; si vous en envoyez 1, jusqu’à 3 peuvent être ajoutés autour. Lorsque le quota est limité, la position la plus profonde est remplie en premier — un point de rupture placé tard dans le prompt met en cache tout ce qui le précède ; le point glissant est donc le plus utile. Sur le point de terminaison natif /v1/messages, rien n’est inséré : votre corps de requête est transmis tel quel et chaque point de rupture qu’il contient est le vôtre.
Un point de rupture ne signifie pas qu’il y a un accès au cache. Il marque un préfixe comme pouvant être mis en cache ; pour qu’il y ait un accès, les mêmes octets de préfixe doivent revenir pendant la fenêtre éphémère d’Anthropic — cinq minutes, renouvelées à chaque lecture — et parvenir à la même clé en amont, ce que permet le routage par affinité décrit ci-dessous. Le premier appel avec un préfixe vierge est toujours une écriture, jamais une lecture, et un prompt qui change à chaque tour ne donnera aucun accès, quel que soit l’emplacement des points de rupture.

Les rapports restent inchangés : les mêmes champs cached_tokens et cache_creation_input_tokens décrits dans Champs de réponse sont renseignés, que les points de rupture viennent de vous ou de la passerelle ; l’objet d’utilisation vous permet donc de vérifier qu’un point de rupture inséré a effectivement généré un accès au cache.

Tarification

Les tarifs du cache sont calculés à partir du prix d’entrée de chaque modèle, selon les ratios propres au fournisseur — les tableaux ci-dessous font foi et chaque tarif indiqué est calculé à partir du prix d’entrée du modèle concerné, sans saisie manuelle. Les lectures du cache sont facturées à 0.10× du tarif d’entrée, chez Anthropic comme chez OpenAI, avec 2 exceptions : 0.025× on Claude Fable 5.1, 0.05× on Claude Opus 5.5. Les écritures du cache sont facturées à 1.25× du tarif d’entrée pour chaque modèle Claude et sur GPT-6 Astra, GPT-6 Sol, GPT-6 Luna, GPT-5.6 Sol et GPT-5.6 Terra. Kunavo facture les écritures Claude avec une TTL d’une heure au même tarif de 1.25×, inférieur aux 2× d’Anthropic. Les autres modèles n’ont pas de tarif d’écriture distinct : leurs écritures sont facturées au tarif d’entrée standard. Dans les deux cas, un préfixe réutilisé ne serait-ce qu’une fois coûte moins cher en cache que s’il était envoyé sans cache. Chaque catégorie bénéficie de la propre remise du modèle, de sorte que ses tarifs de cache affichent la même réduction par rapport au tarif catalogue du fournisseur que ses tarifs d’entrée et de sortie.

Anthropic

ModelInputCache readCache write
Claude Fable 5.1$7$0.175 0.025×$8.75 1.25×
Claude Fable 5$7$0.7 0.10×$8.75 1.25×
Claude Opus 5.5$2.8$0.14 0.050×$3.5 1.25×
Claude Opus 5$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.8$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.7$3.5$0.35 0.10×$4.375 1.25×
Claude Opus 4.6$3.5$0.35 0.10×$4.375 1.25×
Claude Sonnet 5$1.4$0.14 0.10×$1.75 1.25×
Claude Sonnet 4.6$2.1$0.21 0.10×$2.625 1.25×
Claude Haiku 4.5$0.7$0.07 0.10×$0.875 1.25×

OpenAI

ModelInputCache readCache write
GPT-6 Astra$4$0.4 0.10×$5 1.25×
GPT-6 Sol$0.8$0.08 0.10×$1 1.25×
GPT-6 Luna$0.04$0.004 0.10×$0.05 1.25×
GPT-5.6 Sol$2$0.2 0.10×$2.5 1.25×
GPT-5.6 Terra$0.7$0.07 0.10×$0.875 1.25×
GPT-5.5$2$0.2 0.10×$2 1×
Les prompts longs sont facturés selon le palier de contexte long du fournisseur. Au-delà du seuil, l’ensemble de la requête est facturé selon un multiple des prix d’entrée et de sortie du modèle, y compris les lectures et les écritures du cache : sur plus de 272K tokens d’entrée sur GPT-6 Astra, GPT-6 Sol, GPT-6 Luna, GPT-5.6 Sol, GPT-5.6 Terra et GPT-5.5 (2× pour l’entrée, 1.5× pour la sortie). Claude n’applique aucun supplément pour les longs contextes : son contexte de 1 M est facturé au tarif standard.
Le tarif plancher du catalogue tient compte du cache. Kunavo facture max(catalog_cost, upstream × markup) par appel — le plancher est calculé selon le tarif des entrées mises en cache, et non celui des entrées non mises en cache ; les économies vous reviennent donc au lieu d’être annulées.
Les tarifs ci-dessus sont ceux du catalogue. La facture réelle de chaque appel est calculée à partir des crédits du fournisseur en amont effectivement consommés par le modèle (avec une majoration), puis comparée au tarif plancher du catalogue. En cas d’accès au cache, le fournisseur en amont facture moins de crédits ; le calcul selon le coût en amont multiplié par la majoration passe donc généralement sous le tarif catalogue, qui l’emporte alors. En bref : le tarif catalogue est votre prix maximal ; les appels avec cache peuvent être facturés moins cher, mais jamais plus cher.

Le routage par affinité maintient le cache actif

Les caches de prompts des fournisseurs en amont sont propres à chaque clé API. Si chaque requête arrive sur une clé en amont aléatoire, le taux d’accès au cache est d’environ 1/N. Kunavo calcule un hachage stable à partir de votre prompt système et du premier message utilisateur, puis achemine tous les appels partageant le même préfixe vers la même clé en amont, au moyen d’un hachage de rendez-vous pondéré ; ainsi, seul ~1/N des clés est réaffecté lorsque le pool change.

Vous n’avez rien à configurer. Le routage par affinité s’applique automatiquement à tous les appels de chat. (Les images, vidéos et synthèses vocales utilisent un routage aléatoire : ces modalités ne disposent pas de cache par clé.)

Métriques du tableau de bord

Une fois que vos appels commencent à utiliser le cache, /app/usage affiche deux métriques agrégées pour la période sélectionnée :

  • Taux d’accès — jetons d’entrée mis en cache ÷ nombre total de jetons d’entrée. Valeur exacte calculée à partir du nombre de jetons de chaque appel.
  • Économies — montant en USD économisé sur cette période, calculé par modèle selon son véritable tarif de lecture du cache (sans approximation par moyenne générale). Il s’agit d’une estimation aux tarifs catalogue : comme les factures réelles sont plafonnées au tarif catalogue et que les appels avec cache peuvent être facturés sous ce plancher, ce montant constitue une limite supérieure précise des économies réelles. Pour les charges de travail de chat habituelles, les deux valeurs diffèrent de quelques pour cent au maximum.

La page détaillée de chaque appel (/app/usage/<id>) affiche le nombre de jetons mis en cache et écrits dans le cache lorsqu’il est non nul, ainsi que le montant réellement facturé.

Champs de réponse

Kunavo expose les jetons du cache dans les deux formats de réponse. Sur le point de terminaison compatible OpenAI /v1/chat/completions :

ChampSignification
usage.prompt_tokensNombre total de jetons d’entrée (y compris ceux mis en cache et écrits dans le cache).
usage.prompt_tokens_details.cached_tokensSous-ensemble des jetons d’entrée provenant du cache.
usage.cache_creation_input_tokensJetons écrits dans le cache lors de cet appel (facturés au tarif d’écriture du cache du modèle indiqué dans les tableaux ci-dessus).

Sur l’API Messages native à /v1/messages, les champs d’origine d’Anthropic sont transmis tels quels :

ChampSignification
input_tokensEntrées fraîches (non mises en cache).
cache_read_input_tokensProvenant du cache, facturées à 0.10× du tarif d’entrée (0,025× pour Claude Fable 5.1, 0,05× pour Claude Opus 5.5).
cache_creation_input_tokensÉcrits dans le cache lors de cet appel (facturés à 1.25× le tarif d’entrée — également pour les écritures avec une TTL d’une heure, inférieures aux 2× d’Anthropic).

Étapes suivantes

  • Référence de l’API Messages — format natif d’Anthropic avec des exemples détaillés de mise en cache.
  • Mise en cache des prompts Claude — les valeurs de l’objet d’utilisation qui permettent de diagnostiquer le cache et les trois façons dont le routage via une passerelle peut réduire le taux d’accès.
  • Facturation et registre — interaction entre le plancher max() tenant compte du cache et le coût en amont.
  • Tableau complet des tarifs — tarifs des entrées et sorties sans cache pour chaque modèle de chat, avec le sous-tableau du cache en bas.