Les systèmes RAG gaspillent des millions en tokens inutiles. Une couche de contrôle des coûts, combinant cache sémantique et routage intelligent, réduit la facture de 85% sans sacrifier la qualité des réponses.

Les systèmes RAG (Retrieval-Augmented Generation) sont conçus pour améliorer la qualité des réponses, pas pour optimiser les coûts. Pourtant, cette négligence peut coûter cher. Très cher. Chaque token inutile dans la requête ou chaque appel répété au même modèle alourdit la facture. Et quand on multiplie par des milliers de requêtes par jour, ça devient explosif.

À 10 000 requêtes par jour, un système RAG classique gaspille jusqu'à 1 575 dollars par mois en tokens inutiles.

LE PROBLÈME : UN RAG QUI COÛTE PLUS CHER QUE L'UTILITÉ

Dans un système RAG standard, chaque requête est traitée comme si elle était unique. Même si la même question est posée deux fois à quelques minutes d'intervalle, le système recalcule tout : les embeddings, les chunks récupérés, l'appel au modèle. Résultat ? On paie deux fois pour la même réponse. Pire : on récupère souvent 10 chunks de contexte alors que 2 ou 3 suffisent. Les 7 ou 8 autres sont du bruit, des tokens payants pour rien.

Prenons un exemple concret. Avec un coût de 0,015 dollar pour 1 000 tokens :

Tokens inutiles par requête : ~350
À 10 000 requêtes/jour : 3 500 000 tokens inutiles/jour
Coût journalier : 52,50 dollars
Coût mensuel : 1 575 dollars

Ce chiffre n'est pas une estimation fantaisiste. Il est calculé à partir des paramètres de base d'un système RAG classique. Et ce n'est que le début. Car si vous utilisez un modèle haut de gamme pour des requêtes simples comme « Qu'est-ce que le RAG ? », vous payez 90 fois plus cher que nécessaire.

QUATRE COUCHES POUR UN SYSTÈME RAG ÉCONOME

Pour éviter ce gaspillage, j'ai construit une couche de contrôle des coûts composée de quatre éléments, chacun ciblant une faille différente du système RAG classique. Ensemble, ils rendent le système conscient des coûts à chaque étape.

1. LE CACHE SÉMANTIQUE : NE PLUS PAYER POUR LES MÊMES QUESTIONS

Le principe est simple : si une question a déjà été posée et répondue, pourquoi la traiter à nouveau ? Le cache sémantique stocke les requêtes et leurs réponses sous forme d'embeddings. Quand une nouvelle requête arrive, on compare son embedding à ceux du cache. Si la similarité dépasse un seuil, on renvoie la réponse en cache au lieu d'appeler le modèle.

Voici comment ça fonctionne en code :

class SemanticCache:
    def get(self, query: str) -> Optional[str]:
        query = self._validate(query)
        if query is None:
            return None

        with self._lock:
            self.stats.total_requests += 1
            if not self._entries:
                self.stats.cache_misses += 1
                return None

            qvec = self.embedder.embed(query)
            best, bestsim = self.findbest(qvec)

            if best is not None and best_sim >= self.threshold:
                best.hit_count += 1
                self.stats.cache_hits += 1
                self.stats.totalcostsavedusd += self.costperllmcall_usd
                return best.response

            self.stats.cache_misses += 1
            return None

Le cache utilise un verrou (RLock) pour éviter les conflits en cas d'accès simultané. L'embedding de la requête n'est calculé qu'une seule fois, ce qui garantit des temps de réponse stables même avec un cache volumineux.

Avec un cache sémantique bien réglé, 98,5% des requêtes peuvent être servies sans appel au modèle, avec un temps de réponse moyen de 4 ms.

Le seuil de similarité (par défaut 0,75) est crucial. Un seuil bas permet plus de hits, mais risque de renvoyer des réponses incorrectes pour des cas limites. Un seuil élevé réduit les faux positifs, mais augmente le nombre de misses. Pour un système spécialisé (comme un bot de support interne), un seuil de 0,70 à 0,75 est souvent suffisant. Pour un système plus large, il faut monter à 0,90 ou plus.

Avec des embeddings TF-IDF, le seuil de 0,75 est optimal. Avec des modèles comme OpenAI text-embedding-3-small, les scores de similarité sont plus élevés, donc il faut ajuster le seuil à 0,92-0,95.

2. LE ROUTAGE INTELLIGENT : ENVOYER LA BONNE QUESTION AU BON MODÈLE

Tous les modèles ne se valent pas. Un modèle haut de gamme comme GPT-4.5 est parfait pour des requêtes complexes nécessitant du raisonnement multi-étapes. Mais pour une simple question factuelle comme « Qu'est-ce que le RAG ? », il est surdimensionné. Et surtout, il coûte 90 fois plus cher.

Le routeur classe chaque requête en trois niveaux de complexité : simple, standard, complexe. Il utilise trois critères pondérés :

  • Score de longueur (poids 0,20) : nombre de tokens normalisé. Une requête de 5 mots n'a pas le même niveau de complexité qu'une requête de 50 mots.
  • Densité d'entités (poids 0,30) : ratio de mots capitalisés, de nombres ou de ponctuations techniques. Une requête comme « Compare les coûts de RAG hybride et de Recherche vectorielle pure » a une densité d'entités élevée et nécessite un modèle plus puissant.
  • Profondeur de raisonnement (poids 0,50) : présence de mots-clés comme « comparer », « analyser », « pourquoi », « trade-off ». Deux mots-clés suffisent pour maximiser le score.

Voici comment ces scores sont calculés :

def lengthscore(self, query: str) -> float:
    return min(len(query.split()) / 80.0, 1.0)

def entityscore(self, query: str) -> float:
    tokens = query.split()
    if not tokens:
        return 0.0
    hits = sum(
        1 for t in tokens
        if (t[0].isupper() and len(t) > 1)
        or re.search(r"\d", t)
        or re.search(r"[:>/%]", t)
    )
    return min(hits / len(tokens), 1.0)

REASONING_KEYWORDS: frozenset[str] = frozenset({
    "compare", "contrast", "analyze", "why", "trade-off",
    "design", "architecture", "failure mode", "evaluate",
    "relationship between", "when should", "how should", .
})

def reasoningscore(self, query: str) -> float:
    q_lower = query.lower()
    hits = sum(1 for kw in REASONINGKEYWORDS if kw in qlower)
    return min(hits / 2.0, 1.0)

Avant de calculer ces scores, le routeur détecte les requêtes factuelles comme « Qu'est-ce que X ? », « Définir X », ou « Lister X ». Ces requêtes sont directement classées comme « simple » avec un score fixe de 0,10.

FACTOID_PATTERNS = [
    re.compile(r"^(what is|what are|who is|where is)\b", re.I),
    re.compile(r"^(define|definition of|meaning of)\b", re.I),
    re.compile(r"^(list|name|give me)\b.{0,40}$", re.I),
]

Exemple de routage :

[Query 01] What is RAG?
  Tier: simple  (score: 0.10)  → gpt-4o-mini

[Query 04] How does hybrid retrieval differ from pure vector search?
  Tier: standard  (score: 0.306)  → gpt-4o

[Query 06] Compare the cost and latency trade-offs of agentic RAG versus standard
  Tier: standard  (score: 0.611)  → gpt-4o
Avec un routage intelligent, 81% des requêtes peuvent être traitées par un modèle économique comme gpt-4o-mini, réduisant considérablement la facture.

3. LE BUDGET DE TOKENS : FIXER DES LIMITES PRÉCISES

Même avec un cache et un routage optimaux, il faut s'assurer que chaque requête ne dépasse pas un budget tokens raisonnable. Le budget token est divisé en quatre slots prioritaires :

  • Prompt système (200 tokens) : jamais négociable, car il contient les instructions de base.
  • Historique (variable) : permet de maintenir la cohérence des conversations multi-tours.
  • Documents récupérés (variable) : ce qui reste après les coûts fixes.
  • Sortie (max 512 tokens) : l'espace réservé pour la génération de la réponse.

Voici comment le budget est réservé :

# Réserve dans l'ordre de priorité : fixe → historique → docs → sortie
ctx.budget.reserve("system_prompt", 200)        # 1. Jamais négociable
ctx.budget.reserve_text("history", history)     # 2. Cohérence multi-tours
ctx.budget.reservetext("retrieveddocs", docs) # 3. Ce qui reste après les coûts fixes
ctx.budget.reserve("output", min(512, ctx.budget.remaining()))  # 4. Espace de génération

Chaque slot est associé à un coût en dollars. Si le budget est dépassé, la requête est bloquée ou dégradée. Voici comment un slot est enregistré :

self.slots[slotname] = SlotUsage(
    name=slot_name,
    reserved_tokens=granted,
    costusd=granted * self.costpertoken,
)

Et voici comment les coûts réels sont enregistrés après l'appel au modèle :

ctx.recordactual(actualtokens=620, cost_usd=0.0031)

Le système vérifie aussi que les réservations sont valides :

def reserve(self, slot_name: str, tokens: int) -> int:
    if tokens <= 0:
        logger.debug("reserve(%s, %d) — tokens non positifs rejetés", slot_name, tokens)
        return 0

4. LE DISJONCTEUR : ÉVITER LES DÉPASSEMENTS CATASTROPHIQUES

Le disjoncteur (circuit breaker) est un mécanisme de sécurité qui bloque les requêtes si le budget est dépassé, ou si le système détecte une anomalie. Il existe trois états :

  • FERMÉ : fonctionnement normal. Toutes les requêtes passent.
  • OUVERT : seuil dépassé. Les requêtes sont bloquées ou dégradées.
  • DEMI-OUVERT : après un délai de refroidissement, une requête de test est autorisée pour vérifier si le système est revenu à la normale.

Le disjoncteur surveille en permanence les dépenses horaires et quotidiennes :

class CostLedger:
    def record(self, costusd, tokens, modeltier, request_id=""):
        event = SpendEvent(timestamp=time.time(), costusd=costusd, .)
        with self._lock:
            self._events.append(event)
            self.totallifetimeusd += costusd
            self._prune()  # supprime les événements de plus de 24h

    def hourly_spend(self) -> float:
        return self.windowspend(3600)

    def daily_spend(self) -> float:
        return self.windowspend(86400)

def checkand_trip(self) -> None:
    if self.ledger.hourlybreach() or self.ledger.dailybreach():
        self.breaker.trip()

On peut configurer le disjoncteur pour qu'il degrade les requêtes au lieu de les bloquer en cas de dépassement :

enforcer = BudgetEnforcer(
    hourlylimitusd=5.0,
    dailylimitusd=50.0,
    downgradeonbreach=True,   # dégradation progressive
)

Voici ce qui se passe avec différents seuils :

Seuil strict (limite horaire = 0,001 $) :
  → 10 requêtes légitimes bloquées sur 10

Seuil raisonnable (limite horaire = 5,00 $) :
  → 10 requêtes légitimes servies sur 10 (avec dégradation si nécessaire)

PERFORMANCES : 85% D'ÉCONOMIES EN PRODUCTION

J'ai testé ce système de contrôle des coûts sur 200 requêtes avec un mélange réaliste : 60% simple, 30% standard, 10% complexe, 20% répétées. Voici les résultats :

Requêtes traitées : 200
Taux de hits cache : 98,5%
Latence moyenne hit : ~4 ms
Latence moyenne miss : ~4-5 ms
Latence p95 hit : ~5-7 ms
Coût économisé : 0,788 $

Sur 500 requêtes, avec 81% de requêtes simples routées vers gpt-4o-mini, 16,4% de requêtes standard vers gpt-4o, et 2,6% de requêtes complexes vers gpt-4.5, les économies sont encore plus impressionnantes :

Coût total économisé : 3,41 $
Latence moyenne de routage : <0,025 ms

À l'échelle, les économies deviennent monumentales. Voici une comparaison entre un système RAG classique et le système optimisé :

Échelle         Coût naïf/jour  Coût optimisé/jour  Économies
100 req/jour      1,20 $           0,18 $            84,6%
1 000 req/jour    12,00 $          1,71 $            85,7%
10 000 req/jour  120,00 $         17,00 $           85,8%

Économies mensuelles à 10 000 req/jour : 3 090 $ (3 600 $ vs 510 $)

Ces chiffres ne sont pas des promesses marketing. Ils sont calculés à partir des paramètres de base du système et vérifiés par des benchmarks locaux.

LE CODE COMPLET : À VOUS DE JOUER

Si vous voulez reproduire ce système, tout le code est disponible en open source. Vous trouverez l'implémentation complète, les benchmarks, et des exemples d'utilisation ici :

<https://github.com/Emmimal/rag-cost-control-layer/>

Le code est écrit en Python 3.12.6, fonctionne sur Windows 11 sans GPU, et utilise des embeddings TF-IDF pour le cache sémantique. Pour utiliser OpenAI text-embedding-3-small, il suffit de remplacer l'embedding par l'appel à l'API d'OpenAI.

POURQUOI CE SYSTÈME FONCTIONNE (ET COMMENT L'ADAPTER)

Ce système de contrôle des coûts ne sacrifie pas la qualité des réponses. Il optimise simplement les ressources. Voici pourquoi il fonctionne :

  • Cache sémantique : élimine les appels inutiles au modèle pour les requêtes répétées.
  • Routage intelligent : envoie chaque requête au modèle le plus adapté, réduisant les coûts sans perte de qualité.
  • Budget token : limite les tokens inutiles et évite les dépassements de budget.
  • Disjoncteur : protège le système contre les dépenses excessives en cas d'anomalie.

Pour adapter ce système à votre propre projet, voici quelques conseils :

  • Commencez par mesurer les coûts réels de votre système RAG actuel. Combien de tokens inutiles récupérez-vous ? Quel modèle utilisez-vous pour chaque type de requête ?
  • Testez le cache sémantique avec un seuil bas (0,70-0,75) pour voir combien de requêtes peuvent être servies sans appel au modèle.
  • Ajustez le routage en fonction de votre trafic. Si vous avez beaucoup de requêtes factuelles, optimisez la détection des patterns factuels.
  • Configurez le budget token en fonction de vos besoins. Si vos requêtes sont courtes, vous pouvez réduire la taille du prompt système.
  • Activez le disjoncteur avec un seuil raisonnable. Un seuil trop strict bloquera des requêtes légitimes, un seuil trop large ne protègera pas contre les dépassements.
En production, ce système permet d'économiser jusqu'à 3 090 dollars par mois à 10 000 requêtes par jour, tout en maintenant la qualité des réponses.

CONCLUSION : RAG PEUT ÊTRE ÉCONOME SANS ÊTRE MOINS PERFORMANT

Les systèmes RAG ne sont pas conçus pour être économes. Ils sont conçus pour améliorer la qualité des réponses en combinant recherche et génération. Mais en production, cette qualité a un prix. Un prix qui peut devenir prohibitif si on ne prend pas en compte les coûts dès la conception.

La couche de contrôle des coûts que j'ai développée montre qu'il est possible de réduire les dépenses de 85% sans sacrifier la qualité. Grâce au cache sémantique, au routage intelligent, au budget token et au disjoncteur, chaque requête est traitée de la manière la plus efficace possible.

Et le meilleur ? Tout ce code est open source. Vous pouvez l'adapter, l'améliorer, et l'intégrer à votre propre système RAG. Parce que l'IA ne devrait pas coûter une fortune pour être utile.

Sources :
  • Towards Data Science

L'indépendance de CLODCO est votre garantie.

Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.

Soutenir CLODCO