Spaces:
Sleeping
Sleeping
Download src/generation/semantic_cache.py from Harshavard21/FinRAG: direct link, hf CLI and curl.
- Browser
- Download file 3.39 kB
-
https://huggingface.co/spaces/Harshavard21/FinRAG/resolve/main/src/generation/semantic_cache.py
- Command line
-
hf download hf://spaces/Harshavard21/FinRAG/src/generation/semantic_cache.py
-
curl -L -o semantic_cache.py https://huggingface.co/spaces/Harshavard21/FinRAG/resolve/main/src/generation/semantic_cache.py
3.39 kB
| """ | |
| src/generation/semantic_cache.py | |
| ================================= | |
| In-memory Semantic Cache for RAG query results. | |
| Uses cosine similarity over NumPy matrices to find semantically identical questions. | |
| """ | |
| import numpy as np | |
| from typing import Tuple, List, Dict, Optional | |
| from src.utils.logger import logger | |
| class SemanticCache: | |
| """ | |
| Singleton Semantic Cache. | |
| Max 500 overall queries. FIFO eviction. | |
| """ | |
| _instance = None | |
| def __new__(cls, *args, **kwargs): | |
| if cls._instance is None: | |
| cls._instance = super().__new__(cls) | |
| cls._instance._initialized = False | |
| return cls._instance | |
| def __init__(self, max_size: int = 500, threshold: float = 0.95): | |
| if self._initialized: | |
| return | |
| self.max_size = max_size | |
| self.threshold = threshold | |
| # We store queries in a parallel array structure | |
| # embeddings: np.ndarray of shape (N, 768) | |
| self.embeddings: np.ndarray = np.empty((0, 768), dtype=np.float32) | |
| # payloads parallel to embeddings rows | |
| # [{"company": str, "fy": str, "text": str, "sources": list}, ...] | |
| self.payloads: List[Dict] = [] | |
| self._initialized = True | |
| logger.info(f"Semantic Cache initialized (max_size={max_size}, threshold={threshold})") | |
| def find_match(self, query_emb: np.ndarray, company: str, fiscal_year: Optional[str]) -> Optional[Tuple[str, list]]: | |
| """ | |
| Finds a cached response using cosine similarity. | |
| Query embedding must be L2 normalized (BGE embedder does this). | |
| """ | |
| if len(self.payloads) == 0: | |
| return None | |
| # query_emb shape: (768,) | |
| # self.embeddings shape: (N, 768) | |
| # Cosine similarity is just the dot product since vectors are L2 normalized | |
| similarities = np.dot(self.embeddings, query_emb) | |
| # Get the index of the highest similarity | |
| best_idx = int(np.argmax(similarities)) | |
| best_score = similarities[best_idx] | |
| if best_score >= self.threshold: | |
| # Check hard filters (company and FY must match exactly) | |
| p = self.payloads[best_idx] | |
| if p["company"] == company and p["fy"] == fiscal_year: | |
| logger.info(f"Semantic Cache HIT (score={best_score:.4f})") | |
| return p["text"], p["sources"] | |
| return None | |
| def add(self, query_emb: np.ndarray, company: str, fiscal_year: Optional[str], text: str, sources: list): | |
| """Adds a new response to the cache, evicting the oldest if full.""" | |
| # Check size and apply FIFO eviction | |
| if len(self.payloads) >= self.max_size: | |
| # Remove oldest (index 0) | |
| self.embeddings = self.embeddings[1:] | |
| self.payloads.pop(0) | |
| # Append new embedding | |
| query_emb_2d = query_emb.reshape(1, -1) | |
| if self.embeddings.shape[0] == 0: | |
| self.embeddings = query_emb_2d | |
| else: | |
| self.embeddings = np.vstack([self.embeddings, query_emb_2d]) | |
| # Append payload | |
| self.payloads.append({ | |
| "company": company, | |
| "fy": fiscal_year, | |
| "text": text, | |
| "sources": sources | |
| }) | |
| logger.info(f"Added to Semantic Cache (size={len(self.payloads)}/{self.max_size})") | |