rajiv-ramteke's picture
initial commit
6b64d63
Raw
History Blame Contribute Delete
3.32 kB
"""
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 ────────────────────────────────────────────
@property
def size(self) -> int:
return self._index.ntotal if self._index else 0
@property
def is_ready(self) -> bool:
return self._index is not None and self._index.ntotal > 0