Image de couverture : Architecture RAG-less dans Laravel : le cache long-contexte avec Gemini 2.5
tech

Architecture RAG-less dans Laravel : le cache long-contexte avec Gemini 2.5

24 April 2026
6 min de lecture
10 vues
Sébastien Muler

Le RAG n'est plus la seule réponse

Depuis quelques années, le RAG (Retrieval-Augmented Generation) s'est imposé comme la solution de référence pour ancrer un modèle de langage sur un corpus privé : vectorisation des documents, découpage en chunks, recherche sémantique, injection dans le prompt. L'approche fonctionne — mais elle reste, dans certains cas, un contournement coûteux d'un problème de fenêtre de contexte que les modèles récents ont en grande partie résolu.

Google Gemini 2.5 Flash et 2.5 Pro embarquent désormais une fenêtre de 1 million de tokens — soit environ 750 000 mots, l'équivalent d'un monolithe Laravel de taille moyenne. Et surtout, l'API Gemini propose désormais l'Explicit Context Caching : vous uploadez votre corpus une seule fois, vous payez le prix d'entrée une seule fois, puis vous référencez ce contexte mis en cache sur chaque requête suivante à jusqu'à 75 % de réduction sur le tarif d'entrée standard.

Pour des workloads impliquant de nombreuses questions sur la même base de code, le même jeu de documents ou la même base de connaissance, c'est un modèle de coût — et une architecture — fondamentalement différents. Cet article, inspiré d'un excellent guide publié sur dev.to par Dewald Hugo, vous montre comment implémenter cette approche dans Laravel 11/12.


Implémenter le GeminiContextCacheService dans Laravel

L'idée centrale est d'encapsuler tout le cycle de vie du cache (upload, référencement, invalidation) dans un service dédié, puis de le lier au Service Container de Laravel pour une injection propre.

Structure du service

namespace App\Services;

use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Cache;

class GeminiContextCacheService
{
    private string $baseUrl = 'https://generativelanguage.googleapis.com/v1beta';
    private string $apiKey;
    private string $model;

    public function __construct()
    {
        $this->apiKey = config('services.gemini.api_key');
        $this->model  = config('services.gemini.model', 'models/gemini-2.5-flash-preview-04-17');
    }

    public function createCache(string $corpus, int $ttlSeconds = 3600): string
    {
        $response = Http::withQueryParameters(['key' => $this->apiKey])
            ->post("{$this->baseUrl}/cachedContents", [
                'model'    => $this->model,
                'contents' => [[
                    'role'  => 'user',
                    'parts' => [['text' => $corpus]],
                ]],
                'ttl' => "{$ttlSeconds}s",
            ]);

        $response->throw();

        return $response->json('name'); // ex: cachedContents/abc123
    }

    public function query(string $cacheName, string $userQuestion): string
    {
        $response = Http::withQueryParameters(['key' => $this->apiKey])
            ->post("{$this->baseUrl}/models/{$this->model}:generateContent", [
                'cachedContent' => $cacheName,
                'contents'      => [[
                    'role'  => 'user',
                    'parts' => [['text' => $userQuestion]],
                ]],
            ]);

        $response->throw();

        return $response->json('candidates.0.content.parts.0.text', '');
    }

    public function deleteCache(string $cacheName): void
    {
        Http::withQueryParameters(['key' => $this->apiKey])
            ->delete("{$this->baseUrl}/{$cacheName}")
            ->throw();
    }
}

Binding dans le Service Container

Dans AppServiceProvider :

public function register(): void
{
    $this->app->singleton(GeminiContextCacheService::class);
}

Vous pouvez ensuite l'injecter directement dans vos controllers ou vos jobs :

public function __construct(private GeminiContextCacheService $gemini) {}

Cycle de vie du cache : les pièges à éviter

L'Explicit Context Caching est puissant, mais il introduit des subtilités de coût que la plupart des tutoriels passent sous silence.

Le piège des coûts de stockage

Le cache Gemini n'est pas gratuit à conserver. Vous êtes facturé par token stocké par heure. Si votre corpus fait 500 000 tokens et que vous le mettez en cache 24 h, vous paierez le stockage de ces tokens pendant toute la durée — même sans une seule requête. La règle d'or : n'ouvrez le TTL qu'à hauteur de votre fenêtre d'utilisation réelle.

Concrètement :

  • Workload batch nocturne → TTL de 2-3 h maximum
  • Corpus interrogé en continu en journée → TTL de 8-12 h avec renouvellement automatique
  • Corpus interrogé sporadiquement → recréer le cache à la demande est souvent moins cher

Les limites d'upload

L'API impose une taille minimale de contenu caché (environ 32 768 tokens selon les modèles). En dessous, le cache est refusé — et une injection directe dans le prompt reste plus économique. Vérifiez également la taille maximale de votre corpus avant de tenter l'upload.

Invalidation propre

Stockez le cacheName retourné par l'API dans votre propre cache Laravel (Redis ou base de données) en associant un hash de votre corpus :

$corpusHash = md5($corpus);
$cacheKey   = "gemini_cache_{$corpusHash}";

$cacheName = Cache::remember($cacheKey, $ttlSeconds, function () use ($corpus, $ttlSeconds) {
    return $this->gemini->createCache($corpus, $ttlSeconds);
});

Si le corpus change, le hash change, le cache Laravel expira, et un nouveau cache Gemini sera créé automatiquement. Pensez à appeler deleteCache() sur l'ancien pour éviter des frais de stockage inutiles.


RAG vs cache explicite : quand choisir quoi ?

Les deux approches ne s'excluent pas mutuellement, mais leurs cas d'usage sont distincts.

Critère RAG classique Cache explicite Gemini
Corpus très volumineux (> 1M tokens) ❌ (limite contexte)
Requêtes variées sur corpus stable ✅ (mais cache préférable)
Nombreuses requêtes, même corpus ⚠️ coût élevé ✅ économique
Fraîcheur des données (temps réel) ✅ avec re-indexation ⚠️ TTL à gérer
Latence à froid Faible (retrieval ciblé) Élevée (upload initial)
Complexité d'infrastructure Haute (vector DB, embeddings) Faible (API Gemini seule)

Pour une base de code à auditer régulièrement, un jeu de documentation technique stable ou des rapports de conformité récurrents, le cache explicite est souvent l'option la plus rapide à mettre en œuvre et la moins coûteuse à l'usage.


Checklist de mise en production

  • Clé API Gemini stockée dans .env, jamais en dur
  • GeminiContextCacheService bindé en singleton dans AppServiceProvider
  • Hash du corpus utilisé comme clé de cache Laravel pour détecter les changements
  • TTL aligné sur la fenêtre d'utilisation réelle (pas 24 h par défaut)
  • Suppression explicite des anciens caches Gemini en cas de rotation
  • Gestion des exceptions HTTP (quota, token limit) avec fallback ou retry
  • Monitoring des coûts via Google Cloud Console ou l'API de métadonnées du cache
  • Tests d'intégration mockant l'API Gemini pour éviter des frais en CI/CD

Conclusion

L'Explicit Context Caching de Gemini 2.5 ne remplace pas le RAG dans tous les contextes, mais il rend l'approche RAG-less viable et économiquement attractive pour une large classe de workloads en production. Dans un projet Laravel, l'encapsulation dans un service singleton suffit à rendre l'intégration propre, testable et maintenable.

La vraie discipline reste la gestion du cycle de vie du cache : un TTL mal calibré ou des caches orphelins peuvent rapidement transformer une économie en surcoût. Avec les bonnes pratiques, cette architecture simplifie considérablement l'infrastructure — plus de vector database, plus de pipeline d'embeddings — tout en conservant une qualité de réponse optimale sur des corpus stables.

Source originale : RAG-less Architecture in Laravel: Long-Context Caching with Gemini par Dewald Hugo sur dev.to.

Partager cet article