Spaces:
Sleeping
Sleeping
| import os | |
| import logging | |
| from typing import Dict, Any, List, Optional | |
| import chromadb | |
| logger = logging.getLogger("vector_store") | |
| class VectorStore: | |
| def __init__(self, storage_path: Optional[str] = None): | |
| self.storage_path = storage_path or os.environ.get("CHROMA_DB_PATH", "./chroma_db") | |
| os.makedirs(self.storage_path, exist_ok=True) | |
| # Initialize persistent ChromaDB client | |
| self.client = chromadb.PersistentClient(path=self.storage_path) | |
| def get_collection(self, repo_id: str): | |
| # Convert UUID repo_id to a valid Chroma collection name (alphanumeric and underscores) | |
| coll_name = f"repo_{repo_id.replace('-', '_')}" | |
| return self.client.get_or_create_collection( | |
| name=coll_name, | |
| metadata={"hnsw:space": "cosine"} | |
| ) | |
| def add_documents( | |
| self, | |
| repo_id: str, | |
| documents: List[str], | |
| metadatas: List[Dict[str, Any]], | |
| ids: List[str], | |
| embeddings: List[List[float]] | |
| ): | |
| """Adds documents with their corresponding embeddings and metadata.""" | |
| if not documents: | |
| return | |
| collection = self.get_collection(repo_id) | |
| collection.add( | |
| documents=documents, | |
| metadatas=metadatas, | |
| ids=ids, | |
| embeddings=embeddings | |
| ) | |
| logger.info(f"Added {len(documents)} chunks to vector store collection for repo {repo_id}") | |
| def query_documents( | |
| self, | |
| repo_id: str, | |
| query_embedding: List[float], | |
| top_k: int = 5, | |
| where_filter: Optional[Dict[str, Any]] = None | |
| ) -> List[Dict[str, Any]]: | |
| """Queries ChromaDB using the query vector, returning matching chunks with similarity scores.""" | |
| collection = self.get_collection(repo_id) | |
| # Query ChromaDB | |
| results = collection.query( | |
| query_embeddings=[query_embedding], | |
| n_results=top_k, | |
| where=where_filter | |
| ) | |
| formatted = [] | |
| if results and "documents" in results and len(results["documents"]) > 0: | |
| docs = results["documents"][0] | |
| metas = results["metadatas"][0] if results.get("metadatas") else [{} for _ in range(len(docs))] | |
| ids = results["ids"][0] if results.get("ids") else [str(i) for i in range(len(docs))] | |
| distances = results["distances"][0] if results.get("distances") else [0.0 for _ in range(len(docs))] | |
| for doc, meta, doc_id, dist in zip(docs, metas, ids, distances): | |
| # Cosine distance to similarity: 1 - distance | |
| similarity = 1.0 - dist | |
| formatted.append({ | |
| "id": doc_id, | |
| "content": doc, | |
| "metadata": meta, | |
| "similarity": round(similarity, 4), | |
| "distance": round(dist, 4) | |
| }) | |
| # Sort by similarity descending | |
| formatted.sort(key=lambda x: x["similarity"], reverse=True) | |
| return formatted | |
| def delete_collection(self, repo_id: str): | |
| coll_name = f"repo_{repo_id.replace('-', '_')}" | |
| try: | |
| self.client.delete_collection(name=coll_name) | |
| logger.info(f"Deleted vector store collection: {coll_name}") | |
| except Exception as e: | |
| logger.warning(f"Could not delete collection {coll_name}: {e}") | |
| def count_documents(self, repo_id: str) -> int: | |
| try: | |
| collection = self.get_collection(repo_id) | |
| return collection.count() | |
| except Exception: | |
| return 0 | |