0Pricing
AI Engineering Academy · Leçon

Mise en cache sémantique avec des plongements

Créez un cache sémantique qui récupère les réponses enregistrées pour des requêtes sémantiquement similaires mais non identiques, en comparant les plongements des requêtes à un cache de plongements de requêtes précédentes.

Mise en cache sémantique avec des plongements est une leçon AI Engineering Academy gratuite sur CoddyKit. Ceci est la leçon 2 sur 4. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage AI Engineering Academy, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours AI Engineering Academy comprend 4 leçons au total.

La limite de la mise en cache exacte

La mise en cache exacte n’est utile que lorsque les utilisateurs envoient des requêtes identiques octet par octet. En réalité, les utilisateurs formulent différemment une même question : « Comment résilier mon abonnement ? », « Quelle est la procédure pour me désabonner ? » et « Puis-je arrêter mon forfait ? » expriment toutes la même intention, mais produisent des clés de cache différentes. La mise en cache exacte ne reconnaît aucune de ces variantes. La mise en cache sémantique résout ce problème en recherchant des requêtes similaires plutôt qu’identiques, ce qui augmente considérablement les taux de succès du cache.

Fonctionnement de la mise en cache sémantique

Un cache sémantique stocke l’encodage de chaque requête mise en cache avec la réponse correspondante. Lorsqu’une nouvelle requête arrive, encodez-la et recherchez dans le cache une requête déjà vue présentant une forte similarité cosinus. Si la requête en cache la plus proche dépasse un seuil de similarité (généralement 0,95 ou plus), renvoyez sa réponse mise en cache. Si aucune correspondance n’est trouvée, appelez le LLM, stockez la nouvelle réponse et ajoutez l’encodage de la nouvelle requête à l’index du cache pour les recherches futures.

# Semantic cache flow
# 1. New query arrives: 'How do I cancel my subscription?'
# 2. Embed it: embed_query = embed('How do I cancel my subscription?')
# 3. Search cache index for nearest cached query embedding
# 4. Find cached: 'What is the process to unsubscribe?' (similarity=0.97)
# 5. 0.97 >= threshold (0.95) → cache HIT, return cached response
# 6. If 0.82 < threshold → cache MISS, call LLM, cache result, add embedding to index

Cache sémantique en mémoire avec NumPy

Pour les petites applications ou les prototypes, mettez en œuvre la mise en cache sémantique en mémoire en utilisant NumPy pour calculer la similarité cosinus. Stockez les encodages des requêtes mises en cache dans un tableau à deux dimensions et les réponses dans une liste parallèle. Pour chaque nouvelle requête, calculez la similarité cosinus entre le nouvel encodage et tous les encodages mis en cache, puis renvoyez la correspondance la plus proche si elle dépasse le seuil.

import numpy as np
from openai import OpenAI

client = OpenAI()

class InMemorySemanticCache:
    def __init__(self, threshold: float = 0.95):
        self.threshold = threshold
        self.embeddings = []    # list of np.ndarray
        self.responses = []     # list of str
        self.queries = []       # list of str (for inspection)

    def _embed(self, text: str) -> np.ndarray:
        resp = client.embeddings.create(model='text-embedding-3-small', input=text)
        return np.array(resp.data[0].embedding)

    def get(self, query: str) -> str | None:
        if not self.embeddings:
            return None
        q_emb = self._embed(query)
        cache_matrix = np.array(self.embeddings)
        # Cosine similarity: dot product of normalized vectors
        norms = np.linalg.norm(cache_matrix, axis=1)
        q_norm = np.linalg.norm(q_emb)
        sims = (cache_matrix @ q_emb) / (norms * q_norm + 1e-8)
        best_idx = int(np.argmax(sims))
        if sims[best_idx] >= self.threshold:
            print(f'[SEMANTIC HIT] sim={sims[best_idx]:.3f} matched: {self.queries[best_idx]!r}')
            return self.responses[best_idx]
        return None

    def set(self, query: str, response: str):
        emb = self._embed(query)
        self.embeddings.append(emb)
        self.responses.append(response)
        self.queries.append(query)

Cache sémantique avec Redis et Pinecone

Pour mettre en place une mise en cache sémantique en production, stockez les représentations vectorielles des requêtes dans une base de données vectorielle afin d’effectuer rapidement une recherche approximative des plus proches voisins, et stockez les réponses dans Redis avec une clé correspondant à un ID unique. Lorsqu’une nouvelle requête arrive, recherchez la requête mise en cache la plus proche dans la base de données vectorielle, récupérez la réponse dans Redis à l’aide de l’ID présent dans les métadonnées du vecteur, puis renvoyez-la — le tout sans appeler le LLM.

import redis
from pinecone import Pinecone
import hashlib

r = redis.Redis(decode_responses=True)
pc = Pinecone(api_key='YOUR_KEY')
index = pc.Index('semantic-cache')

SIMILARITY_THRESHOLD = 0.95

def semantic_cache_get(query: str) -> str | None:
    q_emb = embed(query)  # from earlier lesson
    results = index.query(vector=q_emb, top_k=1, include_metadata=True)
    if not results.matches:
        return None
    best = results.matches[0]
    if best.score >= SIMILARITY_THRESHOLD:
        response_key = best.metadata.get('response_key')
        return r.get(response_key)
    return None

def semantic_cache_set(query: str, response: str):
    q_emb = embed(query)
    entry_id = hashlib.sha256(query.encode()).hexdigest()[:16]
    response_key = f'sem_cache_resp:{entry_id}'
    r.setex(response_key, 86400, response)  # 24h TTL
    index.upsert(vectors=[{
        'id': entry_id,
        'values': q_emb,
        'metadata': {'query': query[:200], 'response_key': response_key},
    }])

GPTCache : un cache sémantique prêt à l’emploi

GPTCache est une bibliothèque open source qui implémente la mise en cache sémantique pour les applications utilisant des LLM. Elle encapsule le client OpenAI, génère automatiquement les représentations vectorielles des requêtes, vérifie un cache de similarité vectorielle et utilise l’API réelle en solution de repli en cas d’absence dans le cache. Elle prend en charge plusieurs systèmes de stockage vectoriel (FAISS, Milvus, Redis) ainsi que plusieurs modèles de représentation vectorielle, ce qui en fait un moyen rapide d’ajouter la mise en cache sémantique à une application existante.

# pip install gptcache
from gptcache import cache
from gptcache.adapter import openai
from gptcache.embedding import OpenAI as EmbeddingOpenAI
from gptcache.manager import CacheBase, VectorBase, get_data_manager
from gptcache.similarity_evaluation.distance import SearchDistanceEvaluation

# Configure GPTCache with FAISS vector store
cache.init(
    embedding_func=EmbeddingOpenAI().to_embeddings,
    data_manager=get_data_manager(
        CacheBase('sqlite'),
        VectorBase('faiss', dimension=1536),
    ),
    similarity_evaluation=SearchDistanceEvaluation(),
)

# Now use the wrapped openai client — caching is transparent
response = openai.ChatCompletion.create(
    model='gpt-4o-mini',
    messages=[{'role': 'user', 'content': 'What is RAG?'}],
)

Choisir le seuil de similarité

Le seuil de similarité est l’hyperparamètre le plus important pour la mise en cache sémantique. S’il est trop élevé (0.99 ou plus), vous ignorez la plupart des variantes paraphrasées. S’il est trop bas (0.85 ou moins), vous renvoyez des réponses mises en cache incorrectes pour des requêtes différentes, mais superficiellement similaires. Validez votre seuil de manière empirique en échantillonnant des paires de requêtes et en vérifiant que les requêtes au-dessus du seuil ont réellement la même réponse attendue. Valeurs courantes : 0.92 à 0.97 pour les questions-réponses factuelles, et 0.98 ou plus pour la génération de code, où de petites différences peuvent avoir beaucoup d’importance.

def validate_threshold(cache, query_pairs_with_labels):
    '''
    query_pairs_with_labels: list of (query1, query2, should_match: bool)
    '''
    true_pos = true_neg = false_pos = false_neg = 0
    for q1, q2, should_match in query_pairs_with_labels:
        e1, e2 = embed(q1), embed(q2)
        sim = cosine_similarity(e1, e2)
        matched = sim >= cache.threshold
        if should_match and matched: true_pos += 1
        elif not should_match and not matched: true_neg += 1
        elif not should_match and matched: false_pos += 1
        else: false_neg += 1
    precision = true_pos / (true_pos + false_pos) if (true_pos + false_pos) else 0
    recall = true_pos / (true_pos + false_neg) if (true_pos + false_neg) else 0
    print(f'Precision: {precision:.3f}, Recall: {recall:.3f}')

Portée du cache sémantique : le message système est important

Un point essentiel : la mise en cache sémantique doit tenir compte du message système. Deux requêtes utilisateur identiques produisent des réponses différentes si le message système diffère (personas différents, bases de connaissances différentes, formats de réponse différents). Incluez toujours le message système dans l’entrée utilisée pour la représentation vectorielle, ou créez des espaces de noms de cache distincts pour chaque message système. Une approche claire consiste à hacher le message système et à l’utiliser comme préfixe de l’espace de noms du cache.

import hashlib

def make_semantic_cache_namespace(system_prompt: str) -> str:
    return 'sc:' + hashlib.md5(system_prompt.encode()).hexdigest()[:8]

def semantic_cache_get_namespaced(system_prompt: str, user_query: str) -> str | None:
    namespace = make_semantic_cache_namespace(system_prompt)
    q_emb = embed(user_query)
    # Search only within this namespace
    results = index.query(
        vector=q_emb,
        top_k=1,
        filter={'namespace': namespace},
        include_metadata=True,
    )
    if results.matches and results.matches[0].score >= SIMILARITY_THRESHOLD:
        return r.get(results.matches[0].metadata['response_key'])
    return None

Analyse du taux de succès du cache sémantique

Après avoir déployé la mise en cache sémantique, analysez les taux de succès par groupe de requêtes. Utilisez directement les représentations vectorielles des requêtes mises en cache : regroupez-les avec K-means et calculez le taux de succès pour chaque groupe. Les groupes présentant un taux de succès élevé correspondent aux thèmes de questions fréquents pour lesquels la mise en cache est la plus avantageuse. Les groupes présentant un faible taux de succès et composés de requêtes uniques et variées peuvent ne tirer aucun bénéfice de la mise en cache ; vous pouvez donc les exclure afin de réduire la taille de l’index et le coût des représentations vectorielles.

from sklearn.cluster import KMeans
import numpy as np

def analyze_cache_clusters(cache, n_clusters=10):
    if len(cache.embeddings) < n_clusters:
        print('Not enough cache entries to cluster')
        return

    matrix = np.array(cache.embeddings)
    kmeans = KMeans(n_clusters=n_clusters, n_init=10, random_state=42)
    labels = kmeans.fit_predict(matrix)

    from collections import Counter
    cluster_sizes = Counter(labels)
    print('Query clusters by size:')
    for cluster_id, count in cluster_sizes.most_common():
        representative = cache.queries[labels.tolist().index(cluster_id)]
        print(f'  Cluster {cluster_id}: {count} queries, e.g. {representative!r}')

Considérations de sécurité du cache sémantique

La mise en cache sémantique introduit un risque pour la confidentialité : si l’utilisateur A pose une question sensible, sa réponse peut être renvoyée à l’utilisateur B, qui pose une question similaire. Cela peut être acceptable pour les bases de connaissances publiques, mais pas pour les applications contenant des données propres aux utilisateurs ou des contenus sensibles. Appliquez une isolation stricte des espaces de noms par utilisateur ou par organisation, et envisagez d’exclure entièrement du cache les requêtes correspondant à des motifs tels que des informations personnelles.

import re

PII_PATTERNS = [
    r'\b\d{3}[-.]?\d{3}[-.]?\d{4}\b',   # phone numbers
    r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b',  # emails
    r'\b\d{9}\b',                           # SSN-like
]

def should_cache(query: str) -> bool:
    for pattern in PII_PATTERNS:
        if re.search(pattern, query, re.IGNORECASE):
            return False  # do not cache queries with PII
    return True

def secure_semantic_completion(user_id: str, query: str) -> str:
    if should_cache(query):
        cached = semantic_cache_get_namespaced(f'user:{user_id}', query)
        if cached:
            return cached
    result = call_llm_api(query)  # actual API call
    if should_cache(query):
        semantic_cache_set_namespaced(f'user:{user_id}', query, result)
    return result

Combiner mise en cache exacte et sémantique

La stratégie de mise en cache la plus efficace utilise à la fois la mise en cache exacte et la mise en cache sémantique dans une hiérarchie à deux niveaux. Vérifiez d’abord le cache exact (le plus rapide, sans coût de représentation vectorielle) et renvoyez immédiatement la réponse en cas de succès. En cas d’échec, vérifiez le cache sémantique (ce qui nécessite un appel à l’API de représentation vectorielle). Si le cache sémantique ne contient pas la requête, appelez le LLM. Cet ordre réduit à la fois la latence et le coût par requête.

async def two_tier_cached_completion(messages: list[dict], model: str = 'gpt-4o-mini') -> str:
    user_query = messages[-1].get('content', '')
    system_prompt = messages[0].get('content', '') if messages and messages[0]['role'] == 'system' else ''

    # Tier 1: exact cache (instant, free)
    exact_key = make_cache_key(messages, model, temperature=0.0)
    exact_cached = await async_r.get(exact_key)
    if exact_cached:
        return json.loads(exact_cached)

    # Tier 2: semantic cache (one embedding call ~5ms)
    sem_result = semantic_cache_get_namespaced(system_prompt, user_query)
    if sem_result:
        # Backfill exact cache to avoid embedding next time
        await async_r.setex(exact_key, 3600, json.dumps(sem_result))
        return sem_result

    # Tier 3: actual LLM call
    response = await async_client.chat.completions.create(
        model=model, messages=messages, temperature=0.0
    )
    result = response.choices[0].message.content
    await async_r.setex(exact_key, 3600, json.dumps(result))
    semantic_cache_set_namespaced(system_prompt, user_query, result)
    return result

Préchauffage du cache pour le démarrage à froid

Un cache sémantique vierge n’apporte aucun bénéfice tant qu’il n’est pas alimenté. Pour les applications dont le trafic suit des schémas prévisibles, préchauffez le cache au démarrage en générant les représentations vectorielles et en mettant en cache les réponses aux questions les plus fréquentes à partir de vos journaux de requêtes historiques. Vous éliminez ainsi la période de démarrage à froid, pendant laquelle chaque utilisateur des premières heures d’utilisation provoque une absence dans le cache et entraîne le coût total de l’API.

async def warm_semantic_cache(faq_list: list[dict], system_prompt: str):
    print(f'Warming cache with {len(faq_list)} FAQ entries...')
    for entry in faq_list:
        cached = semantic_cache_get_namespaced(system_prompt, entry['question'])
        if cached:
            print(f'  Already cached: {entry["question"][:50]}')
            continue
        # Generate and cache the response
        response = await async_client.chat.completions.create(
            model='gpt-4o-mini',
            messages=[
                {'role': 'system', 'content': system_prompt},
                {'role': 'user', 'content': entry['question']},
            ],
            temperature=0.0,
        )
        answer = response.choices[0].message.content
        semantic_cache_set_namespaced(system_prompt, entry['question'], answer)
        print(f'  Cached: {entry["question"][:50]}')
    print('Cache warming complete')

Vérification rapide

Vérifiez votre compréhension de la mise en cache sémantique présentée dans cette leçon.

Récapitulatif de la leçon

Dans cette leçon, vous avez appris que la mise en cache sémantique associe des requêtes similaires, mais non identiques, en comparant les représentations vectorielles des requêtes à celles stockées dans un système de stockage vectoriel, que le seuil de similarité détermine l’équilibre entre le taux de succès et l’exactitude des réponses, et que la gestion des espaces de noms selon le message système empêche les succès incorrects du cache entre différents contextes. Une architecture à deux niveaux, qui vérifie d’abord le cache exact puis le cache sémantique, réduit à la fois la latence et le coût des représentations vectorielles. Nous allons maintenant exploiter la mise en cache intégrée des préfixes de messages d’OpenAI.

Questions Fréquemment Posées

La leçon « Mise en cache sémantique avec des plongements » est-elle gratuite ?

Oui — le texte complet de « Mise en cache sémantique avec des plongements » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours AI Engineering Academy, passe à CoddyKit PRO. Le cours AI Engineering Academy comprend 4 leçons au total.

Qu'est-ce que j'apprendrai dans « Mise en cache sémantique avec des plongements » ?

Créez un cache sémantique qui récupère les réponses enregistrées pour des requêtes sémantiquement similaires mais non identiques, en comparant les plongements des requêtes à un cache de plongements d… Tu pratiques AI Engineering Academy avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.

Dois-je avoir de l'expérience pour commencer AI Engineering Academy ?

Aucune expérience préalable n'est requise. AI Engineering Academy sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 4.

Combien de temps prend la leçon « Mise en cache sémantique avec des plongements » ?

La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.

Peux-tu écrire et exécuter du code dans cette leçon AI Engineering Academy ?

Oui. Chaque leçon AI Engineering Academy inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.

Toutes les leçons de ce cours

  1. Mise en cache exacte avec Redis
  2. Mise en cache sémantique avec des plongements
  3. Mise en cache des préfixes d’invites d’OpenAI
  4. Traitement par lots, routage des modèles et tableaux de bord des coûts
← Retour à AI Engineering Academy