Spaces:
Running
Running
| """pipeline/cache.py β LRU answer cache. | |
| Keyed on MD5(author_id + book_id + normalized query). | |
| Stores raw LLM answers only β upsell formatting is re-applied per session on hit. | |
| Max 256 slots. Evicts LRU on overflow. | |
| NOT cached: purchase_intent, complaint, greeting (personal / time-sensitive). | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| from collections import OrderedDict | |
| from dataclasses import dataclass, field | |
| class CachedAnswer: | |
| """Raw generation output β upsell/link state is NOT cached.""" | |
| raw_response: str | |
| faithfulness_score: float = 1.0 | |
| hallucination_detected: bool = False | |
| prompt_tokens: int = 0 | |
| completion_tokens: int = 0 | |
| top_book_ids: list[str] = field(default_factory=list) | |
| intent: str = "question" | |
| intent_confidence: float = 0.7 | |
| _CACHE_MAX = 256 | |
| _answer_cache: OrderedDict[str, CachedAnswer] = OrderedDict() | |
| def cache_key(author_id: str, book_id: str | None, query: str) -> str: | |
| """Generate a stable MD5 cache key.""" | |
| raw = f"{author_id}:{book_id or ''}:{query.lower().strip()}" | |
| return hashlib.md5(raw.encode()).hexdigest() | |
| def cache_get(key: str) -> CachedAnswer | None: | |
| """Return cached raw answer or None. Promotes the key to most-recently-used.""" | |
| if key in _answer_cache: | |
| _answer_cache.move_to_end(key) | |
| return _answer_cache[key] | |
| return None | |
| def cache_set(key: str, answer: CachedAnswer) -> None: | |
| """Store a raw answer. Evicts least-recently-used entry when over capacity.""" | |
| _answer_cache[key] = answer | |
| _answer_cache.move_to_end(key) | |
| if len(_answer_cache) > _CACHE_MAX: | |
| _answer_cache.popitem(last=False) | |
| def invalidate_book_cache(author_id: str, book_id: str) -> int: # noqa: ARG001 | |
| """Remove ALL cached answers for this author when a book changes. | |
| Called by the ingest pipeline on re-upload to prevent stale answers. | |
| Returns: | |
| Number of cache entries removed. | |
| """ | |
| count = len(_answer_cache) | |
| _answer_cache.clear() | |
| return count | |