FinanceEducationAssistant / src /data /SemanticCache.py
Fade0510's picture
Fix logging and enable semantic cache, stock qoute cache
357c48c
Raw
History Blame Contribute Delete
3.13 kB
import logging
import hashlib
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from src.data.chroma_config import COLLECTION_NAME, PERSIST_DIRECTORY, ensure_persist_dir
from src.core.settings import get_settings
logger = logging.getLogger(__name__)
class SemanticCache:
def __init__(
self,
persist_directory: str = PERSIST_DIRECTORY,
collection_name: str = COLLECTION_NAME,
):
"""
Initializes the Semantic Caching layer utilizing ChromaDB.
"""
self.persist_directory = persist_directory
settings = get_settings()
self.embeddings = OpenAIEmbeddings(model=settings.models.embedding_model)
ensure_persist_dir(self.persist_directory)
self.vector_store = Chroma(
collection_name=collection_name,
embedding_function=self.embeddings,
persist_directory=self.persist_directory,
)
def _doc_id(self, normalized_query: str) -> str:
h = hashlib.sha256()
h.update(normalized_query.encode("utf-8"))
return h.hexdigest()
def check_cache(self, query: str, threshold: float = 0.70):
"""
Check the vector store for a semantically similar query.
Returns the cached response if a match is found above the similarity threshold.
"""
# Normalize the query to prevent case/whitespace from skewing the embeddings
query_normalized = query.lower().strip()
where = {"namespace": "semantic_cache"}
try:
results = self.vector_store.similarity_search_with_relevance_scores(
query_normalized, k=1, filter=where
)
except TypeError:
# Older wrappers use `where` not `filter`.
results = self.vector_store.similarity_search_with_relevance_scores(
query_normalized, k=1, where=where
) # type: ignore[call-arg]
except Exception:
logger.exception("SemanticCache: check_cache failed")
return None
if results:
doc, score = results[0]
if score >= threshold:
logger.info(f"Semantic Match Score: {score:.3f} greater than threshold {threshold:.3f}")
meta = getattr(doc, "metadata", {}) or {}
return meta.get("response")
return None
def save_to_cache(self, query: str, response: str):
"""
Save the given query and response combo to the semantic cache.
"""
query_normalized = query.lower().strip()
doc_id = self._doc_id(query_normalized)
# Best-effort de-dupe: keep the first cached answer for a given normalized query.
try:
existing = set(self.vector_store.get(ids=[doc_id]).get("ids", []))
except Exception:
existing = set()
if doc_id in existing:
return
self.vector_store.add_texts(
texts=[query_normalized],
metadatas=[{"namespace": "semantic_cache", "response": response}],
ids=[doc_id],
)