Nexus / app /services /cache_layer.py
abc1181's picture
REBUILD
aca03d2
Raw
History Blame Contribute Delete
5.05 kB
from __future__ import annotations
import hashlib, json, time, math
from sqlalchemy import select, delete as sa_delete
from app.models.exact_cache import ExactCache
from app.models.semantic_cache import SemanticCache
from app.services.storage import async_session_maker
def compute_embedding(text: str) -> list[float]:
"""256-dim n-gram embedding using char-level n-grams (1-4)."""
dim = 256
vec = [0.0] * dim
text_lower = text.lower()
for n in range(1, 5):
for i in range(len(text_lower) - n + 1):
gram = text_lower[i:i + n]
idx = int(hashlib.md5(gram.encode()).hexdigest()[:8], 16) % dim
vec[idx] += 1.0
norm = math.sqrt(sum(v * v for v in vec))
if norm > 0:
vec = [v / norm for v in vec]
return vec
def cosine_similarity(a: list[float], b: list[float]) -> float:
dot = sum(ai * bi for ai, bi in zip(a, b))
na = math.sqrt(sum(ai * ai for ai in a))
nb = math.sqrt(sum(bi * bi for bi in b))
if na == 0 or nb == 0:
return 0.0
return dot / (na * nb)
def make_cache_key(model: str, messages: list, **kwargs) -> str:
raw = json.dumps({"model": model, "messages": messages, **kwargs}, sort_keys=True)
return hashlib.sha256(raw.encode()).hexdigest()
async def get_exact(session, key: str) -> dict | None:
result = await session.execute(select(ExactCache).where(ExactCache.cache_key == key))
row = result.scalar_one_or_none()
if not row:
return None
if time.time() >= row.expires_at:
await session.delete(row)
await session.commit()
return None
row.hit_count += 1
row.last_hit = time.time()
await session.commit()
return {"response": row.response, "hit_count": row.hit_count, "created_at": row.created_at}
async def store_exact(session, key: str, response: str, ttl: int = 3600) -> None:
now = time.time()
existing = await session.execute(select(ExactCache).where(ExactCache.cache_key == key))
row = existing.scalar_one_or_none()
if row:
row.response = response
row.expires_at = now + ttl
row.created_at = now
else:
session.add(ExactCache(
cache_key=key, response=response,
created_at=now, expires_at=now + ttl,
))
await session.commit()
async def get_semantic(session, model: str, query: str, threshold: float = 0.95) -> dict | None:
query_emb = compute_embedding(query)
now = time.time()
result = await session.execute(
select(SemanticCache).where(
SemanticCache.model == model,
SemanticCache.expires_at > now,
)
)
for row in result.scalars().all():
cached_emb = json.loads(row.embedding)
sim = cosine_similarity(query_emb, cached_emb)
if sim >= threshold:
row.hit_count += 1
await session.commit()
return {"response": row.response, "similarity": sim, "hit_count": row.hit_count}
return None
async def store_semantic(session, model: str, query: str, response: str, ttl: int = 3600) -> None:
now = time.time()
emb = compute_embedding(query)
key = hashlib.sha256(json.dumps({"model": model, "query": query}, sort_keys=True).encode()).hexdigest()
session.add(SemanticCache(
cache_key=key, model=model, query_text=query,
response=response, embedding=json.dumps(emb),
created_at=now, expires_at=now + ttl,
))
await session.commit()
async def get_stats(session) -> dict:
exact_count = await session.execute(select(ExactCache.cache_key))
exact_rows = exact_count.scalars().all()
exact_active = 0
now = time.time()
for row in exact_rows:
r = await session.execute(select(ExactCache).where(ExactCache.cache_key == row))
obj = r.scalar_one_or_none()
if obj and obj.expires_at > now:
exact_active += 1
sem_count = await session.execute(select(SemanticCache.cache_key))
sem_rows = sem_count.scalars().all()
sem_active = 0
for row in sem_rows:
r = await session.execute(select(SemanticCache).where(SemanticCache.cache_key == row))
obj = r.scalar_one_or_none()
if obj and obj.expires_at > now:
sem_active += 1
return {
"exact_total": len(exact_rows),
"exact_active": exact_active,
"semantic_total": len(sem_rows),
"semantic_active": sem_active,
}
async def clear_exact(session) -> None:
await session.execute(sa_delete(ExactCache))
await session.commit()
async def clear_semantic(session) -> None:
await session.execute(sa_delete(SemanticCache))
await session.commit()
async def clear_all(session) -> None:
await clear_exact(session)
await clear_semantic(session)
async def evict_expired(session) -> None:
now = time.time()
await session.execute(sa_delete(ExactCache).where(ExactCache.expires_at <= now))
await session.execute(sa_delete(SemanticCache).where(SemanticCache.expires_at <= now))
await session.commit()