Maîtriser le TCO d’un RAG : calcul du coût marginal par requête et caching sémantique en production

Guide pratique : calculer le coût marginal par requête d’un RAG et l’optimiser via le caching sémantique
Ce guide s’adresse aux équipes techniques qui exploitent déjà (ou s’apprêtent à exploiter) un pipeline de génération augmentée par récupération (RAG) en production et veulent arrêter de « deviner » le coût d’une requête. En 1 à 2 itérations de sprint, vous pouvez mettre en place :
- un modèle de calcul du coût marginal par requête (tokens + retrieval + overhead),
- une instrumentation exploitable dans vos dashboards (P95, coût moyen, coût par client),
- un cache sémantique et une récupération hybride pour réduire la latence P95 (typ. de 350 ms vers ~220 ms) et viser ~0,015 $/requête au lieu de dérives incontrôlées.
1. Objectif
L’objectif est double :
- Mesurer précisément le coût marginal d’une requête RAG : coût des tokens en entrée/sortie plus coût de la récupération (ANN, BM25, reranking).
- Réduire ce coût (et la latence) en implémentant une récupération hybride et un cache sémantique sans dégrader la qualité de réponse.
À la fin, vous saurez répondre en chiffres à des questions du type : « Combien coûte une requête P95 pour ce client ? Que gagne-t-on (en $ et en ms) si on augmente le taux de hit du cache de 20 % ? ».
2. Prérequis
- Un service RAG existant (microservice HTTP, worker ou fonction serverless) qui :
- appelle un LLM géré (ex. GPT-4o / GPT-4o-mini ou équivalent),
- use un moteur de recherche vectorielle (FAISS, pgvector, Elasticsearch/OpenSearch, etc.) et/ou BM25.
- Accès aux métriques de consommation du LLM (tokens
prompt/completion) via l’API ou les logs. - Une base de métriques (Prometheus, ClickHouse, PostgreSQL analytique…) ou, a minima, un système de logs centralisés.
- Un Redis ou équivalent (Memcached, KeyDB…) disponible pour le caching.
- Niveau de code : à l’aise avec au moins un backend (Python, Node, Go, Java). Les exemples ci-dessous sont donnés en pseudo-python, facilement transposables.
3. Implémentation pas à pas
Étape 1 – Modéliser le coût marginal par requête
On modélise le coût marginal d’une requête comme :
coût_total = coût_tokens_entrée + coût_tokens_sortie + coût_recherche + coût_overhead_infra
Avec, pour un modèle typique du marché :
- Coût tokens entrée : environ
0,0008 $/100 tokens. - Coût tokens sortie : environ
0,0012 $/100 tokens. - Coût recherche (ANN + BM25 + reranker) : souvent <
0,0001 $par requête à volume modéré, mais dépend de votre infra. - Overhead infra (réseau, orchestrateur, logs) : souvent intégré au coût global (~x2 à x4 du seul coût LLM pour arriver autour de 0,015 $/requête en prod sur GPT‑4o ou modèles similaires).
Point clé : ne cherchez pas une précision au centime près dès le début. L’important est d’obtenir un ordre de grandeur cohérent et comparable dans le temps par environnement, client et type de requête.
Étape 2 – Instrumenter la consommation de tokens
Commencez par loguer systématiquement, pour chaque appel LLM :
prompt_tokens,completion_tokens,total_tokens,- identifiants de corrélation (
request_id,user_id,tenant,use_case).
Exemple simplifié en Python avec un client type OpenAI :
from time import time
from openai import OpenAI
client = OpenAI()
def rag_answer(query, context_chunks, meta):
t0 = time()
response = client.chat.completions.create(
model="gpt-4o",
messages=[
{"role": "system", "content": "Tu es un assistant RAG."},
{"role": "user", "content": build_prompt(query, context_chunks)},
],
)
t1 = time()
usage = response.usage
log_llm_call(
request_id=meta["request_id"],
tenant=meta["tenant"],
prompt_tokens=usage.prompt_tokens,
completion_tokens=usage.completion_tokens,
total_tokens=usage.total_tokens,
latency_ms=(t1 - t0) * 1000,
)
return response.choices[0].message.content
Bonnes pratiques :
- Ajoutez ces métriques à votre pipeline d’observabilité (Prometheus, OpenTelemetry, logs structurés JSON).
- Ne loguez jamais de données sensibles (contenu de prompt) en clair en production : limitez-vous aux métriques et aux métadonnées.
Étape 3 – Mesurer la latence P95 et les coûts unitaires
Pour chaque requête RAG (pas seulement l’appel LLM) loguez :
latency_ms_totaldu pipeline (HTTP in → HTTP out),latency_ms_retrieval(ANN + BM25 + rerank),latency_ms_llm.
Exportez ensuite des métriques de type histogramme pour obtenir vos P95 et P99 :
# Exemple conceptuel Prometheus (pseudo-code)
rag_request_latency_ms{stage="total"}
rag_request_latency_ms{stage="retrieval"}
rag_request_latency_ms{stage="llm"}
# Dans vos dashboards, visualisez p95(rag_request_latency_ms{stage="total"})

Calculez le coût unitaire côté application, afin de pouvoir l’agréger facilement par requête ou client.
PRICE_PROMPT = 0.0008 # $ / 100 tokens (exemple)
PRICE_COMPLETION = 0.0012 # $ / 100 tokens (exemple)
PRICE_RETRIEVAL = 0.0001 # $ / requête (approximation)
def estimate_cost_usd(prompt_tokens, completion_tokens):
cost_in = (prompt_tokens / 100.0) * PRICE_PROMPT
cost_out = (completion_tokens / 100.0) * PRICE_COMPLETION
return cost_in + cost_out + PRICE_RETRIEVAL
Stockez ce estimated_cost_usd avec vos logs de requêtes. Cela vous permettra ensuite d’agréger :
- coût moyen par requête,
- coût par client / équipe / use-case,
- impact du cache (avant/après) sur le coût moyen.
Étape 4 – Mettre en place la récupération hybride (vecteur + BM25)
L’objectif de la récupération hybride est de réduire le nombre de documents passés au LLM tout en améliorant le rappel.

- Recherche dense : ANN sur embeddings (FAISS, pgvector…).
- Recherche lexicale : BM25 (PostgreSQL full-text, OpenSearch, Elasticsearch…).
Pattern courant :
def hybrid_retrieval(query, k_dense=50, k_bm25=50, k_final=10):
dense_candidates = vector_store.search(query, k=k_dense)
lexical_candidates = bm25_search(query, k=k_bm25)
merged = merge_and_score(dense_candidates, lexical_candidates)
topk = rerank_cross_encoder(query, merged, k=k_final)
return topk
Dans la pratique, ce pattern permet souvent de passer de ~40–50 chunks envoyés au LLM à ~10–15 pertinents, ce qui :
- réduit le nombre de tokens d’entrée (coût),
- réduit la latence P95 (moins de contexte à sérialiser / désérialiser),
- améliore la qualité (moins de bruit dans le contexte).
Étape 5 – Implémenter un cache sémantique
Le cache sémantique vise à éviter d’appeler le LLM pour des requêtes identiques ou très proches. En pratique, un taux de hit de 30–50 % sur certains use-cases est atteignable et peut réduire les coûts LLM de 30–50 %.

Stratégie minimale (cache textuel) :
- Normaliser la requête (
lower(), strip, suppression de ponctuation mineure). - Clé :
hash(normalized_query + tenant_id). - Valeur : réponse finale (et éventuellement documents sources).
- TTL : 1–24 heures selon la criticité et la fraîcheur attendue.
import hashlib
import redis
r = redis.Redis(...)
def cache_key(query, tenant):
normalized = " ".join(query.lower().split())
raw = f"{tenant}:{normalized}"
return "rag_cache:" + hashlib.sha256(raw.encode()).hexdigest()
def get_cached_answer(query, tenant):
key = cache_key(query, tenant)
data = r.get(key)
if not data:
return None
return deserialize_answer(data)
def set_cached_answer(query, tenant, answer, ttl=3600):
key = cache_key(query, tenant)
r.setex(key, ttl, serialize_answer(answer))
Cache sémantique (niveau avancé) :
- Stocker les embeddings de requêtes dans un store vectoriel rapide.
- Pour une nouvelle requête, chercher les requêtes passées les plus proches (cosine similarity > seuil).
- Si un match est trouvé et jugé suffisamment proche, renvoyer la réponse en cache sans appel LLM.
Dans ce cas, la clé de cache devient un vecteur plutôt qu’une simple string normalisée. Attention à la complexité : commencez par le cache textuel, mesurez le gain, puis introduisez la similarité sémantique si la diversité des requêtes est faible à modérée.
Étape 6 – Calculer l’impact du cache et de l’optimisation sur le coût
Une fois le cache en place, loguez pour chaque requête :
cache_hit(booléen),cache_source("exact","semantic","miss"),estimated_cost_usd,latency_ms_total.
Puis agréguez par fenêtre temporelle (ex. 1 jour) :
-- Exemple SQL conceptuel
SELECT
date_trunc('day', ts) AS day,
count(*) AS total_requests,
avg(estimated_cost_usd) AS avg_cost,
avg(latency_ms_total) AS avg_latency_ms,
avg(CASE WHEN cache_hit THEN 1 ELSE 0 END) AS cache_hit_rate
FROM rag_requests
GROUP BY 1
ORDER BY 1;
Vous devez voir, après activation du cache et de la récupération hybride :
- une baisse nette du coût moyen par requête,
- une réduction significative de la latence moyenne et P95,
- une corrélation claire entre
cache_hit_rateet la baisse de coût.
4. Vérification de la mise en œuvre
- Coût marginal : prenez un échantillon de 100–500 requêtes, exportez
prompt_tokens,completion_tokens, appliquez la formule de coût côté notebook / tableur et comparez àestimated_cost_usd. L’écart doit être <5 %. - Latence P95 : comparez les P95 avant/après récupération hybride + cache pour le même type de trafic (même mix de requêtes).
- Cache : vérifiez que
cache_hit_rateest non nul, et inspectez quelques requêteshitpour confirmer que la réponse reste valide et fraîche. - Qualité : utilisez un golden set de requêtes métier pour comparer la qualité perçue avant/après. Un gain de coût ne doit pas se faire au prix d’une chute de précision.
5. Dépannage : erreurs fréquentes et solutions
Quelques pièges classiques observés en production :
- Coût moyen qui explose (>0,02 $/requête)
Cause : prompts verbeux, trop de chunks envoyés, absence de plafonds.
Solution :- limiter le nombre de documents (
k_final) envoyés au LLM, - imposer un
max_tokensde sortie raisonnable, - réécrire les prompts pour supprimer le bruit, utiliser des schémas de sortie stricts (JSON, listes courtes).
- limiter le nombre de documents (
- Latence P95 > 350 ms malgré le cache
Cause : retrieval lent (index mal configuré), reranker trop lourd, cache placé trop tard dans le pipeline.
Solution :- placer le cache au plus tôt (juste après la réception de la requête),
- vérifier les index (ANN & BM25) et les paramètres de recherche,
- profilage fin des étapes (traces distribuées) pour isoler le goulot.
- Taux de hit du cache faible (<10 %)
Cause : requêtes très variées, normalisation insuffisante, TTL trop court.
Solution :- améliorer la normalisation (synonymes, lemmatisation simple),
- augmenter le TTL sur les cas d’usage stables (FAQ, docs internes),
- introduire progressivement un cache sémantique (similarité sur embeddings).
- Perte de qualité après compression / chunking
Cause : chunks trop petits ou compression agressive (type LLMLingua mal paramétré).
Solution :- augmenter légèrement la taille des chunks et inclure des overlaps,
- diminuer le taux de compression de contexte,
- valider chaque changement sur un golden set avec scoring automatique + revue humaine.
6. Prochaines étapes et considérations opérationnelles
Une fois cette première boucle en place, vous pouvez aller plus loin sur plusieurs axes :
- Optimisation des prompts et du chunking :
réduire systématiquement les tokens de contexte (chunking pertinent, prompts compacts, instructions réutilisables) peut facilement diviser les coûts de 20–30 % supplémentaires. - Compression de contexte :
tester des outils de compression (ex. LLMLingua ou équivalents) pour réduire de ~50 % le nombre de tokens envoyés, en surveillant de près la qualité. - Embeddings locaux & open source :
remplacer un service d’embeddings payant par un modèle open source déployé en interne (ex. all-MiniLM-L6-v2 ou modèles plus récents) fait quasiment tomber à zéro la composante embedding de votre TCO. - Gouvernance des coûts :
exposer les métriques de coût et de latence par client / produit, définir des SLO (coût max par requête, P95 max), mettre en place des garde-fous (budget alerts, quotas dynamiques). - Multi-modèle :
router dynamiquement les requêtes vers un modèle « léger » (type GPT‑4o‑mini) ou un modèle plus puissant selon la complexité détectée (longueur de la question, criticité, besoin de raisonnement). Cela permet souvent de rester autour de 0,015 $/requête tout en gardant le haut de gamme quand nécessaire.
En traitant le coût marginal par requête comme une métrique de première classe au même titre que la latence et la disponibilité, vous transformez votre RAG d’un « proof‑of‑concept cher » en un service industriel maîtrisé, aligné sur les contraintes financières de l’entreprise.
Damien Larquey
Author at Codolie
Passionate about technology, innovation, and sharing knowledge with the developer community.