acb / src /vector_store.py
Kagan Tek
merge
79d4fd5
Raw
History Blame Contribute Delete
7.62 kB
from typing import List, Dict, Any, Optional, Set
from langchain_qdrant import QdrantVectorStore
from langchain_core.documents import Document
from qdrant_client import QdrantClient
from qdrant_client.http.models import Distance, VectorParams, Filter, FieldCondition, MatchValue
from config import (
QDRANT_PATH,
COLLECTION_NAME,
EMBEDDING_DIMENSION,
USE_MEMORY_MODE
)
from embeddings import get_embedder
from qdrant_client_manager import get_qdrant_client
class VectorStore:
"""Qdrant vector store wrapper."""
def __init__(
self,
path: str = QDRANT_PATH,
collection_name: str = COLLECTION_NAME,
use_memory: bool = USE_MEMORY_MODE,
embedder=None
):
self.collection_name = collection_name
self.use_memory = use_memory
self.path = path
# Use shared Qdrant client to prevent multiple instance conflicts
self._client = get_qdrant_client()
self._ensure_collection_exists()
self._embedder = embedder or get_embedder()
self._vector_store = QdrantVectorStore(
client=self._client,
collection_name=self.collection_name,
embedding=self._embedder
)
def _ensure_collection_exists(self):
"""Create collection if it doesn't exist."""
collections = self._client.get_collections().collections
names = [c.name for c in collections]
if self.collection_name not in names:
self._client.create_collection(
collection_name=self.collection_name,
vectors_config=VectorParams(
size=EMBEDDING_DIMENSION,
distance=Distance.COSINE
)
)
def add_documents(
self,
texts: List[str],
metadatas: Optional[List[Dict[str, Any]]] = None
) -> List[str]:
"""Add documents to the vector store."""
if not texts:
return []
if metadatas is None:
metadatas = [{} for _ in texts]
documents = [
Document(page_content=text, metadata=meta)
for text, meta in zip(texts, metadatas)
]
ids = self._vector_store.add_documents(documents)
return ids
def search(self, query: str, top_k: int = 20) -> List[Dict[str, Any]]:
"""Search for similar documents."""
results = self._vector_store.similarity_search_with_score(
query=query,
k=top_k
)
formatted = []
for doc, score in results:
formatted.append({
"id": doc.metadata.get("_id", ""),
"score": score,
"text": doc.page_content,
"source": doc.metadata.get("source", "Unknown"),
"chunk_index": doc.metadata.get("chunk_index", -1),
"page_number": doc.metadata.get("page_number", -1),
"metadata": doc.metadata
})
return formatted
def get_collection_stats(self) -> Dict[str, Any]:
"""Get collection statistics."""
try:
info = self._client.get_collection(self.collection_name)
count = info.points_count or 0
return {
"name": self.collection_name,
"vectors_count": count,
"points_count": count,
"status": str(info.status)
}
except Exception:
return {
"name": self.collection_name,
"vectors_count": 0,
"points_count": 0,
"status": "error"
}
def clear_collection(self):
"""Delete and recreate the collection."""
self._client.delete_collection(self.collection_name)
self._ensure_collection_exists()
self._vector_store = QdrantVectorStore(
client=self._client,
collection_name=self.collection_name,
embedding=self._embedder
)
def collection_exists(self) -> bool:
"""Check if collection has documents."""
stats = self.get_collection_stats()
return stats["points_count"] > 0
def document_exists(self, source: str) -> bool:
"""
Check if a document with the given source name exists in the collection.
Args:
source: The source filename to check for
Returns:
True if document exists, False otherwise
"""
try:
result = self._client.scroll(
collection_name=self.collection_name,
scroll_filter=Filter(
must=[
FieldCondition(
key="metadata.source",
match=MatchValue(value=source)
)
]
),
limit=1,
with_payload=False,
with_vectors=False
)
points, _ = result
return len(points) > 0
except Exception:
return False
def get_loaded_sources(self) -> Set[str]:
"""
Get set of all unique source names in the collection.
Returns:
Set of source filenames
"""
sources = set()
try:
offset = None
while True:
result = self._client.scroll(
collection_name=self.collection_name,
limit=100,
offset=offset,
with_payload=True,
with_vectors=False
)
points, offset = result
for point in points:
if point.payload:
# Check both possible metadata structures
source = None
if "metadata" in point.payload and isinstance(point.payload["metadata"], dict):
source = point.payload["metadata"].get("source")
if not source:
source = point.payload.get("source")
if source:
sources.add(source)
if offset is None:
break
return sources
except Exception:
return sources
_vector_store_instance = None
def get_vector_store() -> VectorStore:
"""Return singleton vector store instance."""
global _vector_store_instance
if _vector_store_instance is None:
_vector_store_instance = VectorStore()
return _vector_store_instance
def reset_vector_store():
"""Reset singleton for testing."""
global _vector_store_instance
if _vector_store_instance is not None:
try:
_vector_store_instance.clear_collection()
except Exception:
pass
_vector_store_instance = None
if __name__ == "__main__":
store = VectorStore(use_memory=True)
texts = ["Atlas ERP sistemi.", "Finans modülü özellikleri."]
metadatas = [
{"source": "test.pdf", "chunk_index": 0},
{"source": "test.pdf", "chunk_index": 1}
]
store.add_documents(texts, metadatas)
print(f"Stats: {store.get_collection_stats()}")
results = store.search("ERP nedir?", top_k=2)
for r in results:
print(f"Score: {r['score']:.4f} - {r['text'][:50]}")