Pourquoi vos factures LLM explosent (et comment y remédier)
Votre prototype fonctionnait nickel avec quelques centaines de requêtes par jour. Puis le trafic a décollé — et la facture a suivi. Si ça vous parle, vous n'êtes pas seul. En 2026, l'inférence représente 80 à 90 % du coût total d'un système LLM en production, bien plus que l'entraînement initial. Et d'après une étude récente portant sur 86 000 développeurs, 40 à 60 % des budgets LLM partent en inefficiences opérationnelles. Pas en utilisation réelle du modèle.
La bonne nouvelle ? Avec les bonnes stratégies, on peut réduire la facture de 60 à 80 % sans sacrifier la qualité des réponses. Ce guide vous montre exactement comment, avec du code Python fonctionnel pour chaque technique.
Anatomie d'un coût API LLM
Avant d'optimiser quoi que ce soit, il faut comprendre où part l'argent. Chaque appel API est facturé sur deux axes :
- Tokens d'entrée : votre system prompt, le contexte, l'historique de conversation, les documents injectés via RAG
- Tokens de sortie : la réponse générée par le modèle
En avril 2026, voici les tarifs des principaux modèles (par million de tokens) :
| Modèle | Entrée | Sortie | Cas d'usage typique |
|---|---|---|---|
| Claude Haiku 4.5 | 0,80 $ | 4 $ | Classification, modération, FAQ |
| Claude Sonnet 4.5 | 3 $ | 15 $ | Production générale |
| Claude Opus 4.6 | 5 $ | 25 $ | Raisonnement complexe |
| GPT-4o | 2,50 $ | 10 $ | Production générale |
| GPT-4o mini | 0,15 $ | 0,60 $ | Tâches simples, haut débit |
Faisons un calcul rapide. Un chatbot d'entreprise qui traite 10 000 requêtes/jour avec un contexte de 4 000 tokens et des réponses de 500 tokens sur Claude Sonnet, ça donne environ 2 700 $ par mois rien qu'en appels API. Avec les stratégies qu'on va voir, on peut descendre sous les 800 $.
Stratégie 1 : Prompt Caching natif (jusqu'à −90 % sur les tokens d'entrée)
C'est le quick win par excellence, et honnêtement, c'est par là que tout le monde devrait commencer.
L'idée est simple : au lieu de retraiter votre system prompt et vos documents de référence à chaque requête, le fournisseur les met en cache côté serveur. Les requêtes suivantes ne paient que 10 % du prix normal pour ces tokens. Oui, vous avez bien lu — 90 % d'économie.
Prompt Caching avec Claude (Anthropic)
Depuis fin 2025, le prompt caching est en disponibilité générale sur l'API Claude. Le principe : vous marquez les blocs de contenu statique avec cache_control, et Anthropic se charge du reste.
import anthropic
client = anthropic.Anthropic()
# System prompt volumineux (documentation produit, règles métier, etc.)
SYSTEM_PROMPT = """Vous êtes un assistant spécialisé dans notre plateforme SaaS.
Voici la documentation complète de nos 47 endpoints API...
[... 50 000 tokens de documentation ...]
"""
response = client.messages.create(
model="claude-sonnet-4-5-20250514",
max_tokens=1024,
system=[
{
"type": "text",
"text": SYSTEM_PROMPT,
"cache_control": {"type": "ephemeral"}
}
],
messages=[
{"role": "user", "content": "Comment créer un webhook ?"}
]
)
# Vérifier l'utilisation du cache
print(f"Tokens cache write : {response.usage.cache_creation_input_tokens}")
print(f"Tokens cache read : {response.usage.cache_read_input_tokens}")
print(f"Tokens standard : {response.usage.input_tokens}")
Les tarifs du prompt caching Claude :
- Écriture en cache (première requête) : 125 % du prix standard — un petit surcoût de 25 %
- Lecture du cache (requêtes suivantes) : 10 % du prix standard (économie de 90 %)
- TTL : 5 minutes (standard) ou 1 heure (étendu, avec surcoût)
Pour un bot de support avec un system prompt de 50 000 tokens qui traite des milliers de requêtes par jour, on parle d'une économie de plus de 4 000 $/mois. Le cache se renouvelle automatiquement à chaque requête qui le touche — tant que votre trafic est régulier, il reste actif.
Prompt Caching avec OpenAI
OpenAI propose aussi le prompt caching, mais avec une approche automatique — pas besoin de marquer manuellement les blocs. Le système détecte et met en cache les préfixes longs répétés. La remise est de 50 % sur les tokens cachés (moins agressif que les 90 % de Claude, mais zéro effort de configuration).
Optimiser la structure de vos prompts pour le cache
Pour maximiser les cache hits, structurez vos prompts avec les éléments statiques en premier :
- System prompt (instructions générales, documentation) → caché
- Exemples few-shot (si constants) → cachés
- Historique de conversation (conversations multi-tours) → partiellement caché
- Message utilisateur (dynamique) → jamais caché
L'ordre est crucial — et c'est un piège classique. Le cache fonctionne sur les préfixes. Si vous changez un seul caractère au milieu du prompt, tout ce qui suit est invalidé. On a vu des équipes perdre 80 % de leurs cache hits à cause d'un timestamp injecté au mauvais endroit dans le prompt.
Stratégie 2 : Cache sémantique avec LiteLLM et Redis (−40 à −90 %)
Le prompt caching natif, c'est très bien, mais il ne gère que les préfixes identiques. Or, en pratique, vos utilisateurs posent souvent la même question avec des formulations différentes. "Comment annuler mon abonnement ?", "Je veux résilier", "Procédure de désabonnement" — même intention, zéro cache hit avec un cache exact.
C'est là qu'intervient le cache sémantique.
Le principe : on compare les embeddings vectoriels des requêtes. Deux questions sémantiquement proches (similarité cosinus au-dessus d'un certain seuil) retournent la même réponse en cache, sans appeler le LLM.
Architecture du cache sémantique
Le flux est assez direct :
- Une requête arrive
- Son embedding est calculé (modèle d'embedding rapide et peu coûteux)
- Recherche de similarité dans la base vectorielle (Redis, Qdrant…)
- Si similarité > seuil → retour de la réponse en cache (0 appel LLM)
- Si similarité < seuil → appel LLM normal, puis stockage du résultat
Implémentation avec LiteLLM + Redis
LiteLLM est un proxy Python qui unifie l'accès à plus de 100 fournisseurs LLM. Son système de cache sémantique repose sur Redis avec le module RediSearch pour la recherche vectorielle.
import os
import litellm
from litellm import completion
from litellm.caching.caching import Cache
# Configuration du cache sémantique Redis
litellm.cache = Cache(
type="redis-semantic",
host=os.environ["REDIS_HOST"],
port=os.environ["REDIS_PORT"],
password=os.environ["REDIS_PASSWORD"],
similarity_threshold=0.85, # 0 = tout matche, 1 = exact uniquement
ttl=3600, # TTL de 1 heure
redis_semantic_cache_embedding_model="text-embedding-3-small",
)
# Première requête → appel LLM (cache miss)
response1 = completion(
model="claude-sonnet-4-5-20250514",
messages=[
{"role": "user", "content": "Comment annuler mon abonnement ?"}
],
max_tokens=500,
)
print(f"Réponse 1 (LLM) : {response1.choices[0].message.content[:100]}...")
# Requête similaire → réponse en cache (cache hit)
response2 = completion(
model="claude-sonnet-4-5-20250514",
messages=[
{"role": "user", "content": "Je voudrais résilier mon offre"}
],
max_tokens=500,
)
print(f"Réponse 2 (cache) : {response2.choices[0].message.content[:100]}...")
# → Même réponse, zéro appel API, temps de réponse en millisecondes
Alternative : cache sémantique avec Qdrant
Si vous utilisez déjà Qdrant pour vos pipelines RAG, autant mutualiser l'infrastructure :
import litellm
from litellm.caching.caching import Cache
litellm.cache = Cache(
type="qdrant-semantic",
qdrant_api_base="http://localhost:6333",
qdrant_api_key=os.environ.get("QDRANT_API_KEY", ""),
qdrant_collection_name="llm_cache",
similarity_threshold=0.85,
qdrant_semantic_cache_embedding_model="text-embedding-3-small",
qdrant_semantic_cache_vector_size=1536,
)
Réglage du seuil de similarité
Le seuil est le paramètre le plus critique de toute cette architecture — et aussi celui qui fait le plus de dégâts quand il est mal calibré. Trop bas, vous servez des réponses incorrectes pour des questions différentes. Trop haut, le cache ne sert presque jamais.
| Domaine | Seuil recommandé | Taux de hit typique |
|---|---|---|
| FAQ / Support client | 0,82 – 0,88 | 40 – 60 % |
| Recherche documentaire | 0,88 – 0,92 | 20 – 40 % |
| Génération de code | 0,92 – 0,96 | 10 – 20 % |
| Rédaction créative | 0,95+ | 5 – 15 % |
Mon conseil : commencez à 0,85 et ajustez selon vos métriques. Surveillez le ratio réponses incorrectes / économies réalisées — c'est le vrai indicateur de succès.
Stratégie 3 : Routage intelligent des modèles (−30 à −70 %)
Pourquoi envoyer toutes vos requêtes à Claude Opus à 5 $/M tokens quand 80 % d'entre elles pourraient être traitées par Haiku à 0,80 $ ? La question se pose, et la réponse est évidente : elles ne devraient pas toutes aller vers le même modèle.
Le routage intelligent analyse chaque requête et la dirige vers le modèle le moins cher capable de la traiter correctement.
Approche 1 : Routage par règles (simple et efficace)
Pour beaucoup d'applications, des règles simples suffisent. Pas besoin de sortir l'artillerie lourde d'un classificateur ML — quelques conditions bien choisies font largement le travail :
from anthropic import Anthropic
client = Anthropic()
def router_par_regles(message: str, outils_requis: bool = False) -> str:
"""Sélectionne le modèle optimal selon la complexité de la requête."""
message_lower = message.lower()
# Requêtes simples → Haiku (0,80 $/M tokens entrée)
mots_simples = ["bonjour", "merci", "oui", "non", "horaires",
"prix", "adresse", "contact", "statut"]
if any(mot in message_lower for mot in mots_simples):
return "claude-haiku-4-5-20251001"
# Requêtes nécessitant du raisonnement → Opus (5 $/M tokens entrée)
mots_complexes = ["compare", "analyse", "stratégie", "architecture",
"optimise", "explique pourquoi", "débug"]
if any(mot in message_lower for mot in mots_complexes):
return "claude-opus-4-6-20260320"
# Tout le reste → Sonnet (meilleur rapport qualité/prix)
return "claude-sonnet-4-5-20250514"
# Utilisation
question = "Quels sont vos horaires d'ouverture ?"
modele = router_par_regles(question)
print(f"Modèle sélectionné : {modele}")
response = client.messages.create(
model=modele,
max_tokens=512,
messages=[{"role": "user", "content": question}]
)
Approche 2 : Routage ML avec RouteLLM
Quand les règles ne suffisent plus, RouteLLM entre en jeu. Développé par LMSYS (les créateurs de Chatbot Arena), il utilise un classificateur ML entraîné sur des données de préférences humaines pour router chaque requête vers un modèle fort ou faible. En pratique, il réduit les coûts jusqu'à 85 % tout en maintenant 95 % de la qualité de GPT-4.
# Installation
# pip install routellm
from routellm.controller import Controller
# Initialisation avec un modèle fort et un modèle faible
client = Controller(
routers=["mf"], # matrix factorization — recommandé
strong_model="claude-sonnet-4-5-20250514",
weak_model="claude-haiku-4-5-20251001",
)
# Le threshold contrôle le compromis coût/qualité
# Plus bas = plus de requêtes vers le modèle fort (meilleure qualité)
# Plus haut = plus de requêtes vers le modèle faible (moins cher)
response = client.chat.completions.create(
model="router-mf-0.11593", # format : router-{type}-{threshold}
messages=[
{"role": "user", "content": "Résume les avantages du serverless"}
]
)
print(response.choices[0].message.content)
Pour calibrer le seuil optimal, RouteLLM fournit un utilitaire qui simule différents ratios sur des données réelles :
# Calibrer pour envoyer ~50 % des requêtes vers le modèle fort
# python -m routellm.calibrate_threshold # --routers mf # --strong-model-pct 0.5 # --config config.yaml
# → Sortie : threshold = 0.11593
Approche 3 : Routage multi-modèles avec LiteLLM Router
LiteLLM Router va encore plus loin en gérant le load balancing, les fallbacks et les retries entre plusieurs déploiements et fournisseurs. C'est particulièrement utile si vous voulez de la résilience en plus de l'optimisation des coûts.
from litellm import Router
# Configuration multi-modèle avec fallback
router = Router(
model_list=[
{
"model_name": "modele-rapide",
"litellm_params": {
"model": "claude-haiku-4-5-20251001",
"api_key": os.environ["ANTHROPIC_API_KEY"],
},
},
{
"model_name": "modele-standard",
"litellm_params": {
"model": "claude-sonnet-4-5-20250514",
"api_key": os.environ["ANTHROPIC_API_KEY"],
},
},
{
"model_name": "modele-standard", # fallback OpenAI
"litellm_params": {
"model": "gpt-4o",
"api_key": os.environ["OPENAI_API_KEY"],
},
},
],
redis_host=os.environ.get("REDIS_HOST"),
redis_password=os.environ.get("REDIS_PASSWORD"),
redis_port=os.environ.get("REDIS_PORT"),
cache_responses=True,
routing_strategy="least-busy",
)
# LiteLLM gère automatiquement le failover entre fournisseurs
response = await router.acompletion(
model="modele-standard",
messages=[{"role": "user", "content": "Explique le pattern CQRS"}]
)
Stratégie 4 : API Batch pour le traitement asynchrone (−50 %)
Celle-ci est souvent oubliée, et c'est dommage. Si une partie de vos workloads n'a pas besoin de réponses en temps réel — rapports générés la nuit, classification de documents, enrichissement de données — l'API Batch est un levier massif. Anthropic et OpenAI offrent tous les deux une réduction de 50 % sur tous les tokens pour les requêtes batch.
Cinquante pour cent. Juste en acceptant un délai de livraison.
import anthropic
client = anthropic.Anthropic()
# Créer un batch de requêtes
batch = client.messages.batches.create(
requests=[
{
"custom_id": f"doc-{i}",
"params": {
"model": "claude-haiku-4-5-20251001",
"max_tokens": 200,
"messages": [
{
"role": "user",
"content": f"Classifie ce ticket support : {ticket}"
}
]
}
}
for i, ticket in enumerate(tickets_a_classifier)
]
)
print(f"Batch ID : {batch.id}")
print(f"Statut : {batch.processing_status}")
# Les résultats sont disponibles sous 24h, à 50 % du prix normal
Cas d'usage idéaux pour le batch :
- Classification et labellisation de datasets
- Génération de résumés en masse
- Extraction d'entités sur des corpus documentaires
- Évaluation de qualité (scoring) de contenus existants
- Traductions par lots
Stratégie 5 : Optimisation des prompts (−20 à −40 %)
La stratégie la plus sous-estimée, et pourtant la plus rapide à mettre en œuvre. Chaque token superflu dans votre prompt vous coûte de l'argent — multiplié par des milliers de requêtes quotidiennes, ça chiffre vite.
Techniques concrètes
- Contraindre la longueur de sortie : ajoutez "Réponds en 50 mots maximum" ou utilisez
max_tokens. Sans contrainte, les modèles produisent des réponses 3 à 5 fois plus longues que nécessaire - Compresser le system prompt : un prompt de 500 tokens peut souvent être réduit à 200 sans perte de qualité. Éliminez les redondances, les formules de politesse, les exemples superflus
- Utiliser des formats structurés : au lieu de paragraphes descriptifs, utilisez des listes et des schémas JSON pour vos instructions — plus concis, plus précis
- Élaguer l'historique : dans les conversations multi-tours, ne gardez que les N derniers échanges pertinents. Résumez les échanges anciens en quelques tokens au lieu de tout envoyer
# AVANT : system prompt verbeux (487 tokens)
prompt_avant = """
Vous êtes un assistant virtuel intelligent et bienveillant. Votre rôle est
d'aider les utilisateurs de notre plateforme à résoudre leurs problèmes
techniques. Vous devez toujours être poli, professionnel et fournir des
réponses détaillées et complètes. Si vous ne connaissez pas la réponse,
dites-le honnêtement. Vous pouvez utiliser le format markdown pour
structurer vos réponses. Voici les catégories de problèmes que vous
pouvez traiter : facturation, technique, compte, fonctionnalités...
"""
# APRÈS : system prompt optimisé (127 tokens)
prompt_apres = """Assistant support technique. Règles :
- Réponses concises, format markdown
- Catégories : facturation | technique | compte | fonctionnalités
- Si incertain, le signaler
"""
# Économie : 74 % de tokens en moins sur le system prompt
Architecture complète : l'AI Gateway multi-couches
Bon, on a vu les stratégies une par une. Mais la vraie puissance vient de leur combinaison. En production, ces techniques se composent dans une architecture en couches — du plus rapide au plus coûteux :
Requête utilisateur
│
▼
┌─────────────────────────┐
│ 1. Cache exact (hash) │ ← Coût : 0 $ | Latence : ~1 ms
│ Hit ? → Réponse │
└─────────┬───────────────┘
│ Miss
▼
┌─────────────────────────┐
│ 2. Cache sémantique │ ← Coût : ~0,001 $ (embedding)
│ Similarité > 0.85 ? │ Latence : ~5 ms
│ Hit ? → Réponse │
└─────────┬───────────────┘
│ Miss
▼
┌─────────────────────────┐
│ 3. Routage du modèle │ ← Classifie la complexité
│ Simple → Haiku │ Latence : ~0 ms (règles)
│ Moyen → Sonnet │ ou ~200 ms (classificateur)
│ Expert → Opus │
└─────────┬───────────────┘
│
▼
┌─────────────────────────┐
│ 4. Prompt caching │ ← −90 % sur tokens statiques
│ (natif fournisseur) │
└─────────┬───────────────┘
│
▼
┌─────────────────────────┐
│ 5. Appel LLM optimisé │ ← max_tokens contraint
│ + stockage en cache │ prompt compressé
└─────────────────────────┘
Avec cette architecture, voici ce qu'on observe en conditions réelles :
| Couche | % de requêtes traitées | Économie sur ces requêtes |
|---|---|---|
| Cache exact | 10 – 15 % | 100 % |
| Cache sémantique | 25 – 40 % | ~100 % |
| Routage vers modèle léger | 30 – 50 % | 60 – 90 % |
| Prompt caching natif | 100 % (résiduel) | 50 – 90 % (tokens entrée) |
| Total combiné | 60 – 80 % |
Mesurer avant d'optimiser : les métriques essentielles
Optimiser sans mesurer, c'est conduire à l'aveugle. Voici les métriques à suivre dans votre dashboard (et oui, il vous faut un dashboard pour ça) :
- Coût par requête : ventilé par modèle, par endpoint, par type de cache
- Taux de cache hit : exact et sémantique, séparément. Un taux sémantique < 20 % sur du support client indique un seuil mal calibré
- Latence P50 / P99 : le cache sémantique doit être < 10 ms. Si c'est plus, votre infrastructure vectorielle est sous-dimensionnée
- Ratio de routage : quel pourcentage part vers chaque modèle ? Si Opus traite plus de 15 % des requêtes, vos règles de routage sont probablement trop laxistes
- Qualité perçue : suivez les thumbs up/down ou un score CSAT par modèle — l'économie n'a aucun sens si la qualité chute
Des outils comme Langfuse et LiteLLM fournissent ces métriques nativement. Si vous partez de zéro, Langfuse est un excellent point de départ (open source, auto-hébergeable).
FAQ
Quel est le moyen le plus rapide de réduire ses coûts API LLM ?
Le prompt caching natif (Claude ou OpenAI) est le quick win par excellence. Il ne nécessite que quelques lignes de code, aucune infrastructure supplémentaire, et réduit immédiatement les coûts de tokens d'entrée de 50 à 90 %. Commencez par là si votre system prompt dépasse 1 000 tokens.
Le cache sémantique ne risque-t-il pas de retourner des réponses incorrectes ?
C'est le risque principal, oui. Deux questions qui semblent proches peuvent avoir des réponses très différentes selon le contexte. La clé est de calibrer le seuil de similarité correctement (commencez à 0,85) et de monitorer activement le taux de faux positifs. Pour les applications critiques (médical, juridique), privilégiez un seuil > 0,92 ou désactivez le cache sémantique entièrement.
Combien coûte l'infrastructure de cache sémantique ?
Un serveur Redis avec le module RediSearch (8 Go de RAM) coûte environ 30 à 50 $/mois en auto-hébergé, ou 100 à 300 $/mois en managed (Redis Cloud). À comparer avec les milliers de dollars économisés sur les appels LLM — le ROI est généralement atteint dès la première semaine pour les applications à fort trafic.
Peut-on combiner routage intelligent et cache sémantique ?
Absolument, et c'est même recommandé. L'architecture optimale place le cache sémantique avant le routeur : si la réponse est en cache, on ne fait ni appel LLM ni classification de complexité. Pour les requêtes non cachées, le routeur dirige vers le modèle le moins cher adapté. Les deux stratégies sont complémentaires — leurs économies se multiplient.
L'API Batch Anthropic est-elle adaptée à tous les cas d'usage ?
Non, clairement pas. L'API Batch est conçue pour les workloads non interactifs — les résultats peuvent prendre jusqu'à 24 heures. Elle est parfaite pour le traitement de documents en masse, la classification de datasets ou la génération de rapports planifiés. Pour tout ce qui nécessite une réponse en temps réel (chatbot, autocomplétion, agents), restez sur l'API standard combinée aux autres stratégies d'optimisation.