Team Ai
Apppublic

Shashiguduri/github-code-explainer

sourceHugging Faceupdated 7mo agoView on Hugging Face
0likes
vector_store.py126 linesDownload Raw Back to services
1"""2backend/services/vector_store.py3----------------------------------4FAISS-based vector store: build, search, save, and load.5 6Index type: IndexFlatIP (Inner Product)7  - Exact nearest-neighbor search8  - Correct cosine similarity because embeddings are L2-normalised9  - Scales well for typical repository sizes (< 100k chunks)10"""11 12import os13import pickle14import logging15import numpy as np16import faiss17from langchain_core.documents import Document18 19logger = logging.getLogger(__name__)20 21INDEX_FILE = "faiss.index"22DOCS_FILE  = "faiss_docs.pkl"23DEFAULT_TOP_K = 524 25 26class VectorStore:27    """Wraps a FAISS index with the corresponding Document list."""28 29    def __init__(self):30        self.index: faiss.Index | None = None31        self.documents: list[Document] = []32 33    # ------------------------------------------------------------------34    # Building35    # ------------------------------------------------------------------36 37    def build(self, documents: list[Document], embeddings: np.ndarray) -> None:38        """39        Create a FAISS IndexFlatIP from pre-computed embeddings.40 41        Args:42            documents:  LangChain Documents — must be same order as embeddings.43            embeddings: float32 ndarray, shape (n, dim).44        """45        if len(documents) != len(embeddings):46            raise ValueError(47                f"Mismatch: {len(documents)} docs but {len(embeddings)} vectors."48            )49        if len(documents) == 0:50            raise ValueError("Cannot build an empty index.")51 52        n, dim = embeddings.shape53        self.documents = documents54        self.index = faiss.IndexFlatIP(dim)55        self.index.add(embeddings)56 57        logger.info("FAISS index built: %d vectors, %d dims.", n, dim)58 59    # ------------------------------------------------------------------60    # Searching61    # ------------------------------------------------------------------62 63    def search(self, query_vec: np.ndarray, top_k: int = DEFAULT_TOP_K) -> list[dict]:64        """65        Retrieve top-K documents most similar to the query vector.66 67        Returns:68            list[dict] with keys: document, score, source, snippet69        """70        if not self.is_ready:71            raise RuntimeError("VectorStore is empty. Call build() or load() first.")72 73        q = query_vec.reshape(1, -1).astype(np.float32)74        k = min(top_k, self.index.ntotal)75        scores, indices = self.index.search(q, k)76 77        results = []78        for score, idx in zip(scores[0], indices[0]):79            if idx < 0:80                continue81            doc = self.documents[idx]82            results.append({83                "document": doc,84                "score":    float(score),85                "source":   doc.metadata.get("source", "unknown"),86                "snippet":  doc.page_content[:300],87            })88        return results89 90    # ------------------------------------------------------------------91    # Persistence92    # ------------------------------------------------------------------93 94    def save(self, directory: str) -> None:95        """Persist index and docs to directory."""96        if not self.is_ready:97            raise RuntimeError("Nothing to save.")98        os.makedirs(directory, exist_ok=True)99        faiss.write_index(self.index, os.path.join(directory, INDEX_FILE))100        with open(os.path.join(directory, DOCS_FILE), "wb") as f:101            pickle.dump(self.documents, f)102        logger.info("VectorStore saved to '%s'.", directory)103 104    def load(self, directory: str) -> None:105        """Load a previously saved index and docs from directory."""106        ip = os.path.join(directory, INDEX_FILE)107        dp = os.path.join(directory, DOCS_FILE)108        if not (os.path.exists(ip) and os.path.exists(dp)):109            raise FileNotFoundError(f"No saved index found in '{directory}'.")110        self.index = faiss.read_index(ip)111        with open(dp, "rb") as f:112            self.documents = pickle.load(f)113        logger.info("VectorStore loaded: %d vectors.", self.index.ntotal)114 115    # ------------------------------------------------------------------116    # Properties117    # ------------------------------------------------------------------118 119    @property120    def is_ready(self) -> bool:121        return self.index is not None and self.index.ntotal > 0122 123    @property124    def total_chunks(self) -> int:125        return self.index.ntotal if self.index else 0126