Vineetiitg
feat(backend): integrate Redis workers, persistent model caching, Cohere V2 fallback, and 5x TTFT fast-path RAG
d816f3a
Raw
History Blame Contribute Delete
6.04 kB
import logging
from typing import List, Tuple
from langchain_core.documents import Document
from app.core.config import settings
logger = logging.getLogger(__name__)
_cohere_client = None
_flashrank_client = None
_nli_model = None
def get_cohere_client():
global _cohere_client
if _cohere_client is None:
if not settings.COHERE_API_KEY or "your_cohere" in settings.COHERE_API_KEY:
logger.warning("COHERE_API_KEY not set or default! Cohere reranking disabled.")
return None
try:
import cohere
logger.info(f"Initializing Cohere API Client with model: {settings.RERANKER_MODEL}")
_cohere_client = cohere.ClientV2(api_key=settings.COHERE_API_KEY)
except Exception as e:
logger.error(f"Failed to initialize Cohere client: {e}")
return None
return _cohere_client
def get_flashrank_client():
global _flashrank_client
if _flashrank_client is None:
try:
from flashrank import Ranker
import os
cache_dir = os.environ.get("FLASHRANK_CACHE_DIR", "/app/data/flashrank_cache" if os.path.exists("/app") else "./data/flashrank_cache")
os.makedirs(cache_dir, exist_ok=True)
model_name = getattr(settings, "FLASHRANK_MODEL", "ms-marco-TinyBERT-L-2-v2")
logger.info(f"Initializing local FlashRank ONNX Client with model: {model_name}")
_flashrank_client = Ranker(model_name=model_name, cache_dir=cache_dir)
except Exception as e:
logger.error(f"Failed to initialize FlashRank client: {e}")
return None
return _flashrank_client
def rerank_with_flashrank(question: str, documents: List[Document], top_k: int) -> List[Document]:
client = get_flashrank_client()
if not client:
logger.warning("FlashRank unavailable, returning un-reranked documents.")
return documents[:top_k]
try:
from flashrank import RerankRequest
passages = [
{"id": str(i), "text": doc.page_content, "meta": doc.metadata}
for i, doc in enumerate(documents)
]
request = RerankRequest(query=question, passages=passages)
results = client.rerank(request)[:min(top_k, len(documents))]
reranked = []
for r in results:
idx = int(r["id"])
doc = documents[idx]
doc.metadata["rerank_score"] = float(r["score"])
doc.metadata["relevance_score"] = float(r["score"])
reranked.append(doc)
highest_score = reranked[0].metadata["rerank_score"] if reranked else 0.0
logger.info(f"FlashRank ONNX Reranked {len(documents)} docs down to top-{len(reranked)} (highest score: {highest_score:.4f})")
return reranked
except Exception as e:
logger.error(f"FlashRank reranking failed ({e}), falling back to top_k truncation without scoring.")
return documents[:top_k]
def rerank_documents(question: str, documents: List[Document], top_k: int = 3) -> List[Document]:
if not documents or len(documents) <= 1:
return documents
provider = getattr(settings, "RERANKER_PROVIDER", "auto").lower()
has_cohere_key = bool(settings.COHERE_API_KEY and settings.COHERE_API_KEY.strip() != "" and "your_cohere" not in settings.COHERE_API_KEY)
# 1. Try Cohere API if explicitly selected or if 'auto' with a valid API key
if provider == "cohere" or (provider == "auto" and has_cohere_key):
client = get_cohere_client()
if client:
try:
doc_texts = [doc.page_content for doc in documents]
response = client.rerank(
model=settings.RERANKER_MODEL,
query=question,
documents=doc_texts,
top_n=min(top_k, len(documents))
)
reranked = []
for r in response.results:
doc = documents[r.index]
doc.metadata["rerank_score"] = float(r.relevance_score)
doc.metadata["relevance_score"] = float(r.relevance_score)
reranked.append(doc)
highest_score = reranked[0].metadata["rerank_score"] if reranked else 0.0
logger.info(f"Cohere API Reranked {len(documents)} docs down to top-{len(reranked)} (highest score: {highest_score:.4f})")
return reranked
except Exception as e:
logger.warning(f"Cohere API reranking failed ({e}). Attempting seamless fallback to local FlashRank...")
# 2. Use local FlashRank ONNX reranker (if provider=='flashrank', no Cohere key, or Cohere API fallback)
logger.info("Using local FlashRank CPU ONNX reranker.")
return rerank_with_flashrank(question, documents, top_k)
def evaluate_nli_groundedness(premise: str, hypothesis: str) -> Tuple[str, float]:
try:
model = get_nli_model()
scores = model.predict([(premise, hypothesis)], apply_softmax=True)[0]
id2label = getattr(model.model.config, "id2label", {0: "contradiction", 1: "entailment", 2: "neutral"})
entailment_score = 0.0
contradiction_score = 0.0
for idx, prob in enumerate(scores):
label = str(id2label.get(idx, "")).lower()
if "entail" in label:
entailment_score = float(prob)
elif "contradict" in label:
contradiction_score = float(prob)
logger.info(f"NLI Groundedness evaluation - Entailment: {entailment_score:.4f}, Contradiction: {contradiction_score:.4f}")
if contradiction_score < 0.40:
return "yes", float(1.0 - contradiction_score)
return "no", float(1.0 - contradiction_score) if contradiction_score > 0 else 0.5
except Exception as e:
logger.warning(f"NLI evaluation failed ({e}), defaulting to grounded=yes.")
return "yes", 0.5