Spaces:
Sleeping
Sleeping
| """ | |
| backend/database/faiss_db.py | |
| ============================== | |
| FAISS index management β create, save, load, search. | |
| """ | |
| import os | |
| from typing import List, Optional, Tuple | |
| import numpy as np | |
| from backend.utils.config import settings | |
| from backend.utils.helper import ensure_dir | |
| from backend.utils.logger import get_logger | |
| logger = get_logger(__name__) | |
| class FAISSDatabase: | |
| """ | |
| Low-level FAISS index wrapper. | |
| Handles creation, persistence, and k-NN search. | |
| """ | |
| def __init__(self, dim: int = 384, index_path: str = None): | |
| self.dim = dim | |
| self.index_path = index_path or settings.FAISS_INDEX_PATH | |
| self._index = None | |
| # ββ Build βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def build(self, vectors: np.ndarray) -> None: | |
| """Build a flat inner-product FAISS index from vectors.""" | |
| import faiss | |
| assert vectors.ndim == 2 and vectors.shape[1] == self.dim, \ | |
| f"Expected shape (N, {self.dim}), got {vectors.shape}" | |
| vectors = vectors.astype("float32") | |
| faiss.normalize_L2(vectors) | |
| self._index = faiss.IndexFlatIP(self.dim) | |
| self._index.add(vectors) | |
| logger.info(f"FAISS index built: {self._index.ntotal} vectors") | |
| # ββ Persist βββββββββββββββββββββββββββββββββββββββββββββββ | |
| def save(self) -> None: | |
| """Write FAISS index to disk.""" | |
| import faiss | |
| ensure_dir(os.path.dirname(self.index_path)) | |
| faiss.write_index(self._index, self.index_path) | |
| logger.info(f"FAISS index saved β {self.index_path}") | |
| def load(self) -> bool: | |
| """Load FAISS index from disk. Returns True if successful.""" | |
| import faiss | |
| if not os.path.exists(self.index_path): | |
| logger.warning(f"No index at {self.index_path}") | |
| return False | |
| self._index = faiss.read_index(self.index_path) | |
| logger.info(f"FAISS index loaded: {self._index.ntotal} vectors") | |
| return True | |
| # ββ Search ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def search( | |
| self, query_vector: np.ndarray, top_k: int = 5 | |
| ) -> Tuple[List[float], List[int]]: | |
| """ | |
| Nearest-neighbour search. | |
| Returns: | |
| (distances, indices) β lists of length top_k. | |
| """ | |
| import faiss | |
| if self._index is None: | |
| raise RuntimeError("FAISS index not built or loaded.") | |
| q = query_vector.astype("float32").reshape(1, -1) | |
| faiss.normalize_L2(q) | |
| k = min(top_k, self._index.ntotal) | |
| distances, indices = self._index.search(q, k) | |
| return distances[0].tolist(), indices[0].tolist() | |
| # ββ Properties ββββββββββββββββββββββββββββββββββββββββββββ | |
| def size(self) -> int: | |
| return self._index.ntotal if self._index else 0 | |
| def is_ready(self) -> bool: | |
| return self._index is not None and self._index.ntotal > 0 | |