Ψ±ΨΊΨ―
Claude Sonnet 4.6
feat: complete platform β€” auth, deployment, hardening
0cce0b6
Raw History Blame Contribute Delete
4.7 kB
"""
In-memory TTL cache for expensive operations (RAG retrieval, embeddings).
LRU + TTL strategy. Thread-safe.
Replace with Redis when deploying multi-process.
Usage:
cache = TTLCache()
result = cache.get("rag:some_query")
if result is None:
result = expensive_call(...)
cache.set("rag:some_query", result, ttl=300)
"""
from __future__ import annotations
import re
import time
import threading
import hashlib
import json
from collections import OrderedDict
from typing import Any
# ── Arabic normalization for consistent cache keys ─────────────────────────
_ARABIC_NORM = str.maketrans(
"Ψ£Ψ₯Ψ’Ψ§Ω±" "Ω‰ΩŠ" "Ψ©" "Ψ€Ψ¦Ψ‘",
"aaaaa" "يي" "Ω‡" "ووؑ",
)
_WHITESPACE = re.compile(r"\s+")
def _normalize(text: str) -> str:
"""Normalize Arabic text for cache key stability."""
text = text.strip().lower()
text = text.translate(_ARABIC_NORM)
text = _WHITESPACE.sub(" ", text)
return text
# ── Cache implementation ───────────────────────────────────────────────────
class TTLCache:
"""
LRU cache with per-entry TTL and observable metrics.
max_size: maximum number of entries.
default_ttl: seconds before an entry expires.
"""
def __init__(self, max_size: int = 512, default_ttl: int = 300) -> None:
self._store: OrderedDict[str, tuple[Any, float]] = OrderedDict()
self._lock = threading.Lock()
self._max = max_size
self._ttl = default_ttl
self._hits = 0
self._misses = 0
self._evictions = 0
def _is_expired(self, expires_at: float) -> bool:
return time.monotonic() > expires_at
def get(self, key: str) -> Any | None:
with self._lock:
if key not in self._store:
self._misses += 1
return None
value, expires_at = self._store[key]
if self._is_expired(expires_at):
del self._store[key]
self._misses += 1
return None
self._store.move_to_end(key)
self._hits += 1
return value
def set(self, key: str, value: Any, ttl: int | None = None) -> None:
with self._lock:
expires_at = time.monotonic() + (ttl if ttl is not None else self._ttl)
if key in self._store:
self._store.move_to_end(key)
self._store[key] = (value, expires_at)
if len(self._store) > self._max:
self._store.popitem(last=False)
self._evictions += 1
def delete(self, key: str) -> None:
with self._lock:
self._store.pop(key, None)
def clear(self) -> None:
with self._lock:
self._store.clear()
def stats(self) -> dict:
with self._lock:
now = time.monotonic()
alive = sum(1 for _, (_, exp) in self._store.items() if exp > now)
total = self._hits + self._misses
return {
"size": len(self._store),
"alive": alive,
"max": self._max,
"hits": self._hits,
"misses": self._misses,
"evictions": self._evictions,
"hit_rate": round(self._hits / total, 3) if total else 0.0,
}
# ── Specialized caches ─────────────────────────────────────────────────────
# RAG context results (expensive: embedding + pgvector + Cohere)
rag_cache = TTLCache(max_size=256, default_ttl=300) # 5-minute TTL
# Chat medical-query intent check
intent_cache = TTLCache(max_size=1024, default_ttl=600) # 10-minute TTL
# Global KB search results (pgvector hit β€” ~5 s each)
search_cache = TTLCache(max_size=512, default_ttl=120) # 2-minute TTL
def rag_cache_key(query: str, panel: str = "", topic_type: str = "") -> str:
"""
Deterministic, normalized cache key for a RAG retrieval call.
Normalization: Arabic chars unified, whitespace collapsed, lowercased.
"""
norm_q = _normalize(query[:300])
raw = json.dumps({"q": norm_q, "panel": panel.lower(), "tt": topic_type.lower()},
sort_keys=True, ensure_ascii=False)
return "rag:" + hashlib.sha256(raw.encode()).hexdigest()[:16]
def intent_cache_key(query: str) -> str:
norm_q = _normalize(query[:200])
return "intent:" + hashlib.sha256(norm_q.encode()).hexdigest()[:16]