Spaces:
Sleeping
Sleeping
| """ | |
| Two-layer cache: | |
| 1. Exact-match hash cache (Redis/in-memory fallback) | |
| 2. Semantic near-duplicate cache using cosine similarity on query embeddings | |
| Semantic caching prevents re-querying the LLM for paraphrased versions of the | |
| same question - a major cost & latency win in production | |
| """ | |
| import hashlib | |
| import json | |
| import logging | |
| import time | |
| from typing import Optional | |
| from google_crc32c import value | |
| import numpy as np | |
| from .config import get_settings | |
| from .embeddings import cosine_similarity | |
| logger = logging.getLogger(__name__) | |
| settings = get_settings() | |
| # In memory fallback (used when Redis is unavailable) | |
| class InMemoryCache: | |
| def __init__(self,ttl: int = 3600, max_size: int = 1000): | |
| self._store: dict[str,tuple[str, float]] = {} # Key -> (value, expiry) | |
| self.ttl = ttl | |
| self.max_size = max_size | |
| def get(self,key: str) -> Optional[str]: | |
| entry = self._store.get(key) | |
| if entry is None: | |
| return None | |
| value, expiry = entry | |
| if time.time() > expiry: | |
| del self._store[key] | |
| return None | |
| return value | |
| def set(self,key: str, value: str) -> None: | |
| if len(self._store) >= self.max_size: | |
| oldest = next(iter(self._store)) | |
| del self._store[oldest] | |
| self._store[key] = (value, time.time() + self.ttl) | |
| def ping(self) -> bool: | |
| return True | |
| def _build_redis_client(): | |
| try: | |
| import redis | |
| client = redis.from_url(settings.redis_url, decode_responses=True) | |
| client.ping() | |
| logger.info("Redis cache connected") | |
| return client | |
| except Exception as e: | |
| logger.warning(f"Redis unavalaible ({e}) - using in-memory cache.") | |
| return InMemoryCache(ttl=settings.cache_ttl_seconds) | |
| _cache_client = _build_redis_client() | |
| # Exact match cache | |
| def _cache_key(query: str, collection: str, mode: str) -> str: | |
| payload = f"{query}::{collection}::{mode}" | |
| return "rag:exact:" + hashlib.sha256(payload.encode()).hexdigest()[:32] | |
| def get_exact(query: str, collection: str, mode: str) -> Optional[dict]: | |
| key = _cache_key(query, collection, mode) | |
| raw = _cache_client.get(key) | |
| if raw: | |
| logger.debug(f"Exact cache hit: {key[:16]}...") | |
| return json.loads(raw) | |
| return None | |
| def set_exact(query: str, collection: str, mode: str, value: str) -> None: | |
| key = _cache_key(query,collection,mode) | |
| serialized = json.dumps(value) | |
| if hasattr(_cache_client,"setex"): | |
| _cache_client.setex(key,settings.cache_ttl_seconds,serialized) | |
| else: | |
| _cache_client.set(key,serialized) | |
| # Semantic Cache | |
| # stores (embedding, serialized_response) pairs keyed by short hash | |
| _semantic_index: list[tuple[list[float],str,dict]] = [] # (vec,key,response) | |
| def get_semantic(query_vec: list[float]) -> Optional[dict]: | |
| """Return the cache response if cosine similarity > threshold""" | |
| best_score = 0.0 | |
| best_response = None | |
| for vec, _,response in _semantic_index: | |
| score = cosine_similarity(query_vec,vec) | |
| if score > best_score: | |
| best_score = score | |
| best_response = response | |
| if best_score >= settings.semantic_cache_threshold: | |
| logger.info(f"Semantic Cache hit (score={best_score:.3f})") | |
| return best_response | |
| return None | |
| def set_semantic(query_vec: list[float], query: str, response: dict) -> None: | |
| h = hashlib.md5(query.encode()).hexdigest()[:8] | |
| _semantic_index.append((query_vec, h, response)) | |
| if len(_semantic_index) > 5000: # cap memory | |
| _semantic_index.pop(0) | |
| def cache_connected() -> bool: | |
| try: | |
| return bool(_cache_client.ping()) | |
| except Exception: | |
| return False | |
| def get_cache_stats() -> dict: | |
| stats = {} | |
| if isinstance(_cache_client, InMemoryCache): | |
| stats["system"] = "in-memory (python dictionary)" | |
| stats["exact_matches_cached"] = len(_cache_client._store) | |
| else: | |
| stats["system"] = "redis" | |
| try: | |
| stats["exact_matches_cached"] = _cache_client.dbsize() | |
| except: | |
| stats["exact_matches_cached"] = "unknown" | |
| stats["semantic_matches_cached"] = len(_semantic_index) | |
| return stats | |
| print("[cache] Module ready") |