Video-Note-Taker / backend /database /vector_store.py
rajiv-ramteke's picture
initial commit
6b64d63
Raw
History Blame Contribute Delete
3.87 kB
"""
backend/database/vector_store.py
===================================
High-level vector store abstraction used by the RAG pipeline.
Stores both vectors and associated metadata (chunk dicts).
"""
import json
import os
from typing import Any, Dict, List, Optional
import numpy as np
from backend.database.faiss_db import FAISSDatabase
from backend.utils.helper import ensure_dir, save_json, load_json
from backend.utils.logger import get_logger
logger = get_logger(__name__)
class VectorStore:
"""
Combines a FAISS index with a metadata store.
Provides add / search / clear operations.
"""
def __init__(self, index_path: str = None, meta_path: str = None):
self.index_path = index_path or "data/embeddings/faiss.index"
self.meta_path = meta_path or "data/embeddings/metadata.json"
self._db: Optional[FAISSDatabase] = None
self._meta: List[Dict] = []
# ── Indexing ──────────────────────────────────────────────
def add(self, vectors: np.ndarray, metadata: List[Dict]) -> None:
"""
Add vectors and their metadata to the store.
Args:
vectors: np.ndarray of shape (N, D)
metadata: List of N dicts (chunk info, text, timestamps…)
"""
assert len(vectors) == len(metadata), "Vector/metadata length mismatch"
dim = vectors.shape[1]
self._db = FAISSDatabase(dim=dim, index_path=self.index_path)
self._db.build(vectors)
self._meta = metadata
logger.info(f"VectorStore: {len(metadata)} items indexed")
def save(self) -> None:
"""Persist index and metadata to disk."""
if self._db:
self._db.save()
save_json(self._meta, self.meta_path)
logger.info("VectorStore saved to disk")
def load(self) -> bool:
"""Load index and metadata from disk."""
if not os.path.exists(self.index_path):
return False
# Peek at metadata to get dimension
if os.path.exists(self.meta_path):
self._meta = load_json(self.meta_path)
# Detect dim from a dummy load
import faiss
idx = faiss.read_index(self.index_path)
dim = idx.d
self._db = FAISSDatabase(dim=dim, index_path=self.index_path)
self._db.load()
logger.info(f"VectorStore loaded: {len(self._meta)} items")
return True
# ── Search ────────────────────────────────────────────────
def search(self, query_vector: np.ndarray, top_k: int = 5) -> List[Dict]:
"""
Search the store and return metadata for top-k matches.
Returns:
List of metadata dicts with an added 'score' field.
"""
if not self._db or not self._db.is_ready:
logger.warning("VectorStore not ready. Call add() or load() first.")
return []
distances, indices = self._db.search(query_vector, top_k)
results = []
for dist, idx in zip(distances, indices):
if 0 <= idx < len(self._meta):
item = dict(self._meta[idx])
item["score"] = float(dist)
results.append(item)
return results
# ── Utilities ─────────────────────────────────────────────
def clear(self) -> None:
"""Reset the store."""
self._db = None
self._meta = []
logger.info("VectorStore cleared")
@property
def size(self) -> int:
return self._db.size if self._db else 0
@property
def is_empty(self) -> bool:
return self.size == 0