Rishabh Shenoy
Added files to root
ab1e1af
Raw History Blame Contribute Delete
4.9 kB
from dataclasses import dataclass
from hashlib import sha256
import logging
from app.config import Settings
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class RetrievedDocument:
title: str
content: str
source_url: str | None = None
document_id: str | None = None
section: str | None = None
class CricketRetriever:
def __init__(self, settings: Settings):
self._settings = settings
self.collection = settings.qdrant_collection
self._client = None
self._encoder = None
if settings.qdrant_url and settings.qdrant_api_key:
self.status = "configured"
else:
self.status = "not_configured"
def _get_client(self):
if self._client is None and self._settings.qdrant_url and self._settings.qdrant_api_key:
try:
from qdrant_client import QdrantClient
self._client = QdrantClient(
url=self._settings.qdrant_url,
api_key=self._settings.qdrant_api_key,
timeout=10,
)
self.status = "connected"
except Exception as exc:
logger.warning(f"Could not connect to Qdrant: {exc}")
self.status = f"unavailable: {type(exc).__name__}"
self._client = None
return self._client
def _get_encoder(self):
if self._encoder is None:
try:
import torch
torch.set_num_threads(1)
from sentence_transformers import SentenceTransformer
self._encoder = SentenceTransformer(self._settings.embedding_model)
except Exception as exc:
logger.warning(f"Could not load SentenceTransformer: {exc}")
self._encoder = None
return self._encoder
def _ensure_collection(self) -> None:
client = self._get_client()
encoder = self._get_encoder()
if client and encoder:
from qdrant_client.models import Distance, VectorParams
size = encoder.get_sentence_embedding_dimension()
if not client.collection_exists(self.collection):
client.create_collection(
collection_name=self.collection,
vectors_config=VectorParams(size=size, distance=Distance.COSINE),
)
def index(self, documents: list[RetrievedDocument]) -> int:
client = self._get_client()
encoder = self._get_encoder()
if client is None or encoder is None:
raise RuntimeError("Qdrant or embedding model is not configured")
self._ensure_collection()
from qdrant_client.models import PointStruct
vectors = encoder.encode([document.content for document in documents]).tolist()
points = [
PointStruct(
id=int(sha256((document.document_id or str(index)).encode()).hexdigest()[:15], 16),
vector=vector,
payload={
"document_id": document.document_id,
"title": document.title,
"section": document.section,
"content": document.content,
"source_url": document.source_url,
"document_type": "cricket_rule",
},
)
for index, (document, vector) in enumerate(zip(documents, vectors))
]
client.upsert(collection_name=self.collection, points=points)
return len(points)
def retrieve(self, question: str, limit: int = 3) -> list[RetrievedDocument]:
client = self._get_client()
encoder = self._get_encoder()
if client is None or encoder is None:
return []
try:
vector = encoder.encode(question).tolist()
if hasattr(client, "query_points"):
result = client.query_points(
collection_name=self.collection,
query=vector,
limit=limit,
with_payload=True,
)
hits = result.points
else:
hits = client.search(collection_name=self.collection, query_vector=vector, limit=limit)
return [
RetrievedDocument(
title=str(hit.payload.get("title", "Cricket rules")),
content=str(hit.payload.get("content", "")),
source_url=hit.payload.get("source_url"),
document_id=hit.payload.get("document_id"),
section=hit.payload.get("section"),
)
for hit in hits
if hit.score >= 0.35
]
except Exception as exc:
logger.warning(f"Error retrieving from Qdrant: {exc}")
return []