Spaces:
Running on Zero
Running on Zero
File size: 7,073 Bytes
0828c2c 98516ab 0828c2c 98516ab 0828c2c 98516ab 0828c2c 98516ab 0828c2c 98516ab 0828c2c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | """Vector store module for ChromaDB operations."""
import logging
from pathlib import Path
from typing import Any
import chromadb
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_huggingface import HuggingFaceEmbeddings
from .config_loader import get_config
logger = logging.getLogger(__name__)
class VectorStoreManager:
"""Manage ChromaDB vector store for document embeddings."""
def __init__(self):
"""Initialize vector store manager with configuration."""
self.config = get_config()
# Get embedding configuration
embedding_model = self.config.get(
"embeddings.model_name", "sentence-transformers/all-MiniLM-L6-v2"
)
device = self.config.get("embeddings.device", "cpu")
# Initialize embeddings
# low_cpu_mem_usage=False avoids "Cannot copy out of meta tensor" error when
# transformers/accelerate loads models with meta device placement (Docker/constrained envs)
logger.info(f"Initializing embeddings model: {embedding_model}")
self.embeddings = HuggingFaceEmbeddings(
model_name=embedding_model,
model_kwargs={
"device": device,
"model_kwargs": {"low_cpu_mem_usage": False},
},
encode_kwargs={"normalize_embeddings": True},
)
# Get vector store configuration
self.persist_directory = self.config.get_env("CHROMA_PERSIST_DIR", "./chroma_db")
self.collection_name = self.config.get("vectorstore.collection_name", "profile_documents")
# Ensure persist directory exists
Path(self.persist_directory).mkdir(parents=True, exist_ok=True)
self.vectorstore: Chroma | None = None
def create_vectorstore(self, documents: list[Document]) -> Chroma:
"""Create a new vector store from documents.
Args:
documents: List of Document objects to embed
Returns:
Chroma vector store instance
"""
if not documents:
msg = "No documents provided to create vector store"
raise ValueError(msg)
logger.info(f"Creating vector store with {len(documents)} documents...")
try:
self.vectorstore = Chroma.from_documents(
documents=documents,
embedding=self.embeddings,
collection_name=self.collection_name,
persist_directory=self.persist_directory,
)
logger.info(f"Vector store created successfully at {self.persist_directory}")
return self.vectorstore
except Exception as e:
logger.error(f"Error creating vector store: {e}")
raise
def load_vectorstore(self) -> Chroma:
"""Load existing vector store from disk.
Returns:
Chroma vector store instance
"""
persist_dir = Path(self.persist_directory)
if not persist_dir.exists():
msg = f"Vector store not found at {self.persist_directory}"
raise FileNotFoundError(msg)
logger.info(f"Loading vector store from {self.persist_directory}...")
try:
self.vectorstore = Chroma(
collection_name=self.collection_name,
embedding_function=self.embeddings,
persist_directory=self.persist_directory,
)
# Verify the vector store has documents
collection_count = self.vectorstore._collection.count()
logger.info(f"Vector store loaded with {collection_count} embeddings")
return self.vectorstore
except Exception as e:
logger.error(f"Error loading vector store: {e}")
raise
def get_retriever(self, **kwargs: Any) -> Any:
"""Get retriever for the vector store.
Args:
**kwargs: Additional arguments for the retriever.
``search_type`` is accepted but must not be forwarded into
Chroma ``search_kwargs`` (newer chromadb rejects it).
Returns:
Retriever instance
"""
if self.vectorstore is None:
self.load_vectorstore()
# search_type belongs to as_retriever(), not Collection.query()
search_type = kwargs.pop(
"search_type",
self.config.get("retrieval.vector.search_type", "similarity"),
)
search_kwargs_config = self.config.get("retrieval.vector.search_kwargs", {})
k = self.config.get("retrieval.vector.k", 4)
# Build search_kwargs based on search_type
if search_type == "mmr":
filtered_kwargs = {
"k": k,
"fetch_k": search_kwargs_config.get("fetch_k", 20),
"lambda_mult": search_kwargs_config.get("lambda_mult", 0.5),
}
else:
filtered_kwargs = {"k": k}
# Override with provided kwargs (search_type already popped)
filtered_kwargs.update(kwargs)
filtered_kwargs.pop("search_type", None)
logger.debug(
f"Creating retriever with search_type={search_type}, search_kwargs={filtered_kwargs}"
)
return self.vectorstore.as_retriever(search_type=search_type, search_kwargs=filtered_kwargs)
def add_documents(self, documents: list[Document]) -> None:
"""Add new documents to existing vector store.
Args:
documents: List of Document objects to add
"""
if self.vectorstore is None:
self.load_vectorstore()
logger.info(f"Adding {len(documents)} documents to vector store...")
try:
self.vectorstore.add_documents(documents)
logger.info("Documents added successfully")
except Exception as e:
logger.error(f"Error adding documents: {e}")
raise
def delete_collection(self) -> None:
"""Delete the entire collection."""
logger.warning(f"Deleting collection: {self.collection_name}")
try:
client = chromadb.PersistentClient(path=self.persist_directory)
client.delete_collection(name=self.collection_name)
self.vectorstore = None
logger.info("Collection deleted successfully")
except Exception as e:
logger.error(f"Error deleting collection: {e}")
raise
def similarity_search(self, query: str, k: int = 4, **kwargs: Any) -> list[Document]:
"""Perform similarity search.
Args:
query: Query string
k: Number of documents to return
**kwargs: Additional search parameters
Returns:
List of similar documents
"""
if self.vectorstore is None:
self.load_vectorstore()
return self.vectorstore.similarity_search(query, k=k, **kwargs)
def get_vectorstore_manager() -> VectorStoreManager:
"""Get singleton vector store manager instance."""
return VectorStoreManager()
|