Spaces:
Sleeping
Sleeping
| """ | |
| vector_db.py — A minimal vector database backed by a JSON file. | |
| Flow: | |
| 1. Embed text using a sentence-transformer model (runs locally). | |
| 2. Store the embedding + metadata as a record in data.json. | |
| 3. At query time, embed the query and rank all records by cosine similarity. | |
| """ | |
| import json | |
| import math | |
| import os | |
| from typing import Any | |
| class VectorDB: | |
| def __init__(self, db_path: str = "data.json", model_name: str = "all-MiniLM-L6-v2"): | |
| self.db_path = db_path | |
| self._model_name = model_name | |
| self._model = None # loaded lazily so imports stay fast | |
| self._records: list[dict] = [] | |
| if os.path.exists(db_path): | |
| self.load() | |
| # ------------------------------------------------------------------ | |
| # Internal helpers | |
| # ------------------------------------------------------------------ | |
| def _get_model(self): | |
| if self._model is None: | |
| from sentence_transformers import SentenceTransformer | |
| print(f"[VectorDB] Loading model '{self._model_name}' (first run downloads ~80 MB)...") | |
| self._model = SentenceTransformer(self._model_name) | |
| print("[VectorDB] Model ready.\n") | |
| return self._model | |
| def _cosine_similarity(a: list[float], b: list[float]) -> float: | |
| dot = sum(x * y for x, y in zip(a, b)) | |
| mag_a = math.sqrt(sum(x * x for x in a)) | |
| mag_b = math.sqrt(sum(x * x for x in b)) | |
| if mag_a == 0 or mag_b == 0: | |
| return 0.0 | |
| return dot / (mag_a * mag_b) | |
| # ------------------------------------------------------------------ | |
| # Public API | |
| # ------------------------------------------------------------------ | |
| def add(self, text: str, metadata: dict[str, Any] | None = None) -> None: | |
| """Embed `text` and append a record to the in-memory database.""" | |
| model = self._get_model() | |
| embedding: list[float] = model.encode(text).tolist() | |
| record = { | |
| "id": len(self._records), | |
| "text": text, | |
| "metadata": metadata or {}, | |
| "embedding": embedding, | |
| } | |
| self._records.append(record) | |
| def save(self) -> None: | |
| """Persist all records (including raw embeddings) to JSON.""" | |
| with open(self.db_path, "w", encoding="utf-8") as f: | |
| json.dump(self._records, f, indent=2) | |
| print(f"[VectorDB] Saved {len(self._records)} records -> {self.db_path}") | |
| def load(self) -> None: | |
| """Load records from the JSON file (embeddings included).""" | |
| with open(self.db_path, encoding="utf-8") as f: | |
| self._records = json.load(f) | |
| print(f"[VectorDB] Loaded {len(self._records)} records <- {self.db_path}") | |
| def search(self, query: str, top_k: int = 5) -> list[dict]: | |
| """ | |
| Embed the query and return the top_k most similar records, | |
| ordered by cosine similarity (highest first). | |
| """ | |
| if not self._records: | |
| return [] | |
| model = self._get_model() | |
| query_vec: list[float] = model.encode(query).tolist() | |
| scored = [ | |
| { | |
| "score": self._cosine_similarity(query_vec, rec["embedding"]), | |
| "id": rec["id"], | |
| "text": rec["text"], | |
| "metadata": rec["metadata"], | |
| } | |
| for rec in self._records | |
| ] | |
| scored.sort(key=lambda r: r["score"], reverse=True) | |
| return scored[:top_k] | |