Spaces:
Sleeping
Sleeping
Ayush
feat: Planner-Executor AI agent with autonomous task execution, memory, and weather tool
7d3b88b | """Vector store β semantic similarity index for memory retrieval. | |
| Provides fast nearest-neighbour search over embedding vectors. | |
| Backend strategy: | |
| 1. FAISS (preferred) β fast, production-grade | |
| 2. NumPy cosine similarity (fallback) β zero extra deps | |
| """ | |
| from __future__ import annotations | |
| import logging | |
| import threading | |
| import numpy as np | |
| logger = logging.getLogger(__name__) | |
| # ββ Backend detection ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| try: | |
| import faiss # type: ignore[import-untyped] | |
| _HAS_FAISS = True | |
| except ImportError: | |
| _HAS_FAISS = False | |
| # ββ VectorStore ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| class VectorStore: | |
| """Thread-safe vector index with add / search / remove support. | |
| Uses FAISS when available; falls back to brute-force NumPy cosine | |
| similarity for environments where FAISS cannot be installed. | |
| """ | |
| def __init__(self, dimension: int = 384) -> None: | |
| self._dimension = dimension | |
| self._lock = threading.Lock() | |
| if _HAS_FAISS: | |
| base = faiss.IndexFlatIP(dimension) | |
| self._index: faiss.IndexIDMap = faiss.IndexIDMap(base) | |
| self._backend = "faiss" | |
| else: | |
| self._vectors: dict[int, np.ndarray] = {} | |
| self._backend = "numpy" | |
| logger.info( | |
| "[VECTOR] Initialised (dim=%d, backend=%s)", | |
| dimension, self._backend, | |
| ) | |
| # ββ Properties βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def backend(self) -> str: | |
| """Return the active backend name.""" | |
| return self._backend | |
| def size(self) -> int: | |
| """Current number of indexed vectors.""" | |
| with self._lock: | |
| if self._backend == "faiss": | |
| return self._index.ntotal | |
| return len(self._vectors) | |
| # ββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def add(self, embedding: list[float], entry_id: int) -> None: | |
| """Index an embedding under the given *entry_id*.""" | |
| vec = self._to_unit_vec(embedding) | |
| with self._lock: | |
| if self._backend == "faiss": | |
| ids = np.array([entry_id], dtype=np.int64) | |
| self._index.add_with_ids(vec, ids) | |
| else: | |
| self._vectors[entry_id] = vec.flatten() | |
| logger.debug("[VECTOR] Added entry_id=%d (total=%d)", entry_id, self.size) | |
| def remove(self, entry_id: int) -> None: | |
| """Remove an embedding by *entry_id*.""" | |
| with self._lock: | |
| if self._backend == "faiss": | |
| ids = np.array([entry_id], dtype=np.int64) | |
| try: | |
| self._index.remove_ids(ids) | |
| except Exception as exc: | |
| logger.warning( | |
| "[VECTOR] FAISS remove failed for id=%d: %s", | |
| entry_id, exc, | |
| ) | |
| else: | |
| self._vectors.pop(entry_id, None) | |
| logger.debug("[VECTOR] Removed entry_id=%d", entry_id) | |
| def search( | |
| self, | |
| query_embedding: list[float], | |
| top_k: int = 5, | |
| ) -> list[tuple[int, float]]: | |
| """Return the *top_k* most similar entries. | |
| Returns: | |
| List of ``(entry_id, similarity_score)`` sorted descending. | |
| """ | |
| vec = self._to_unit_vec(query_embedding) | |
| with self._lock: | |
| if self._backend == "faiss": | |
| return self._search_faiss(vec, top_k) | |
| return self._search_numpy(vec, top_k) | |
| def clear(self) -> None: | |
| """Drop all indexed vectors.""" | |
| with self._lock: | |
| if self._backend == "faiss": | |
| base = faiss.IndexFlatIP(self._dimension) | |
| self._index = faiss.IndexIDMap(base) | |
| else: | |
| self._vectors.clear() | |
| logger.info("[VECTOR] Index cleared") | |
| # ββ Internals ββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def _to_unit_vec(self, embedding: list[float]) -> np.ndarray: | |
| """Convert to float32 row vector and L2-normalise.""" | |
| vec = np.array(embedding, dtype=np.float32).reshape(1, -1) | |
| norm = np.linalg.norm(vec) | |
| if norm > 0: | |
| vec = vec / norm | |
| return vec | |
| def _search_faiss( | |
| self, query_vec: np.ndarray, top_k: int, | |
| ) -> list[tuple[int, float]]: | |
| if self._index.ntotal == 0: | |
| return [] | |
| k = min(top_k, self._index.ntotal) | |
| scores, ids = self._index.search(query_vec, k) | |
| return [ | |
| (int(eid), float(score)) | |
| for score, eid in zip(scores[0], ids[0]) | |
| if eid >= 0 | |
| ] | |
| def _search_numpy( | |
| self, query_vec: np.ndarray, top_k: int, | |
| ) -> list[tuple[int, float]]: | |
| if not self._vectors: | |
| return [] | |
| q = query_vec.flatten() | |
| scored = [ | |
| (eid, float(np.dot(q, vec))) | |
| for eid, vec in self._vectors.items() | |
| ] | |
| scored.sort(key=lambda x: x[1], reverse=True) | |
| return scored[:top_k] | |