Cache sémantique
Le cache sémantique fait correspondre les requêtes par signification plutôt que par comparaison exacte de chaînes. Lorsqu’une requête est sémantiquement similaire à une requête précédemment mise en cache, le cache renvoie les résultats stockés, ce qui évite le passage par l’intégration, la recherche, le prétraitement et le réordonnancement.
Configuration de base
Installation : Bibliothèque principale (aucune dépendance supplémentaire requise)
Exemple :
from mistralai.search.toolkit.retrieval.cache import (
CachedQueryEngine,
InMemoryCacheBackend,
SemanticCache,
EvictionPolicy,
)
# 1. Create a cache backend
backend = InMemoryCacheBackend(
dim=1024, # Embedding dimensionality
max_entries=500, # Max cached queries
ttl_seconds=3600, # 1-hour expiration
eviction_policy=EvictionPolicy.LRU,
)
# 2. Create the semantic cache
cache = SemanticCache(
backend=backend,
similarity_threshold=0.95, # 95% cosine similarity = cache hit
)
# 3. Wrap your QueryEngine
cached_engine = CachedQueryEngine(
engine=query_engine,
cache=cache,
embedder=embedder, # Used to embed incoming queries
)
# 4. Use normally — caching is transparent
result = await cached_engine.search("What is RAG?", top_k=10)
# First call: embedding + retrieval → cached
# Second call with similar query: cache hit → instantFonctionnement :
- La requête arrive
- Le module d’intégration convertit la requête en vecteur
- Le cache recherche des vecteurs de requêtes similaires déjà mis en cache
- Si la similarité > seuil : renvoie les résultats en cache (rapide)
- Si aucune correspondance : exécute le pipeline de recherche complet et met les résultats en cache (lent mais mis en cache pour une utilisation ultérieure)
Configuration
Options backend :
| Option | Valeur par défaut | Objectif |
|---|---|---|
dim | (obligatoire) | Dimensionnalité de l’intégration (1024 pour mistral-embed) |
max_entries | 1000 | Nombre maximal de requêtes en cache avant éviction |
ttl_seconds | Aucun | Durée de validité des entrées en secondes (Aucun = pas d’expiration) |
eviction_policy | LRU | LRU, LFU ou FIFO |
Sensibilité du cache :
| Paramètre | Effet | Cas d’usage |
|---|---|---|
similarity_threshold=0.99 | Très strict, moins de correspondances | Correspondance exacte des réponses |
similarity_threshold=0.95 | Équilibré (par défaut) | La plupart des cas d’usage |
similarity_threshold=0.90 | Permissif, plus de correspondances | Réponses approximatives acceptables |
# High sensitivity (strict matching)
cache = SemanticCache(
backend=InMemoryCacheBackend(dim=1024, max_entries=500),
similarity_threshold=0.99,
)
# Low sensitivity (broad matching)
cache = SemanticCache(
backend=InMemoryCacheBackend(dim=1024, max_entries=500),
similarity_threshold=0.90,
)Stratégies d’éviction :
- LRU (Least Recently Used) — Évite les entrées les moins récemment consultées
- LFU (Least Frequently Used) — Évite les entrées les moins fréquemment consultées
- FIFO (First In, First Out) — Évite les entrées les plus anciennes
backend = InMemoryCacheBackend(
dim=1024,
max_entries=500,
eviction_policy=EvictionPolicy.LFU, # Cache frequently asked queries
)Surveillance avec métriques
Suivez les performances du cache :
from mistralai.search.toolkit.retrieval.cache import CacheMetrics
# Create metrics tracker
metrics = CacheMetrics()
cached_engine = CachedQueryEngine(
engine=query_engine,
cache=cache,
embedder=embedder,
metrics=metrics,
)
# Run queries
for query in queries:
result = await cached_engine.search(query)
# Check performance
snapshot = metrics.snapshot()
print(f"Hit rate: {snapshot.hit_rate:.1%}")
print(f"Avg hit similarity: {snapshot.avg_hit_similarity:.3f}")
print(f"Total requests: {snapshot.total_requests}")
print(f"Evictions: {snapshot.evictions}")Métriques disponibles :
| Métrique | Description |
|---|---|
hit_rate | Part des requêtes traitées depuis le cache |
avg_hit_similarity | Similarité cosinus moyenne pour les correspondances dans le cache |
total_requests | Nombre total de requêtes traitées |
avg_embed_time_ms | Temps moyen d’intégration par requête |
avg_lookup_time_ms | Temps moyen de recherche dans le cache par requête |
avg_retrieval_time_ms | Temps moyen de recherche en cas d’absence dans le cache |
evictions | Nombre total d’entrées évictées en raison de la capacité |
errors | Nombre total d’erreurs de cache ou d’intégration |
Exemple de surveillance :
# Track performance over time
if snapshot.hit_rate > 0.5:
print(f"✓ Good hit rate: {snapshot.hit_rate:.1%}")
print(f" Avg similarity: {snapshot.avg_hit_similarity:.3f}")
else:
print(f"⚠ Low hit rate: {snapshot.hit_rate:.1%}")
print(f" Consider lowering similarity_threshold")
if snapshot.evictions > max_entries * 0.1:
print(f"⚠ High eviction rate")
print(f" Consider increasing max_entries or using LFU policy")Tolérance aux pannes
Toutes les opérations de cache sont non fatales. Si le cache ou le module d’intégration génère une exception, la requête est traitée par le chemin sans cache :
# Even if cache fails, retrieval continues
cached_engine = CachedQueryEngine(
engine=query_engine,
cache=cache,
embedder=embedder,
)
# If embedder or cache throws exception:
# - Exception is logged
# - Query runs through normal pipeline
# - Result is NOT cached
# - Pipeline reliability is unaffected
result = await cached_engine.search(query="...", top_k=10)
# Always returns a result, cache is best-effortSurveillance des erreurs :
snapshot = metrics.snapshot()
if snapshot.errors > 0:
print(f"Cache errors: {snapshot.errors}")
print("Check logs for details — retrieval pipeline is unaffected")Gestion du cache
Invalidation par namespace :
# Invalidate cache for a specific namespace
await cache.invalidate(namespace="query_engine")
# All entries in that namespace are removedEffacer l’intégralité du cache :
# Remove all cached entries
await cache.clear()Expiration avec TTL :
# Entries expire automatically after ttl_seconds
backend = InMemoryCacheBackend(
dim=1024,
max_entries=500,
ttl_seconds=3600, # 1-hour expiration
)
# Expired entries are removed on next access or evictionGestion manuelle (pour les backends personnalisés) :
from mistralai.search.toolkit.retrieval.cache import CacheBackend
class CustomCacheBackend(CacheBackend):
async def invalidate_old_entries(self, before_timestamp: float):
"""Remove entries older than timestamp."""
...
async def get_cache_size(self) -> int:
"""Return current cache size in entries."""
...Bonnes pratiques
1. Surveillez le taux de correspondances :
# Track performance to validate cache effectiveness
if metrics.snapshot().hit_rate < 0.2:
# Cache not being utilized effectively
# Consider:
# - Lowering similarity_threshold
# - Increasing max_entries
# - Analyzing query patterns
...2. Ajustez la taille du cache :
# Based on expected queries and memory
# 1024-dim embeddings ≈ 4KB per entry
backend = InMemoryCacheBackend(
dim=1024,
max_entries=1000, # ~4MB cache
ttl_seconds=3600,
)3. Utilisation avec une recherche multi-étapes :
# Caching is most valuable when downstream is expensive
cached_engine = CachedQueryEngine(
engine=QueryEngine(
retriever=vector_retriever,
rerankers=[
LLMReRanker(llm_provider=llm, top_k=10), # Expensive!
],
),
cache=cache,
embedder=embedder,
)
# Cache avoids expensive LLM reranking on cache hits4. Seuil pour différents cas d’usage :
# Conservative (exact answers)
similarity_threshold=0.98
# Balanced (most Q&A systems)
similarity_threshold=0.95
# Permissive (approximate answers ok)
similarity_threshold=0.90Voir aussi
- Présentation générale de la recherche — Architecture du pipeline de recherche
- Récupérateurs — Recherche vectorielle
- Rerankers — Affiner les résultats après la recherche
- Prétraitement des requêtes — Améliorer les requêtes avant la recherche