Situatie
Soluția RAG construită anterior cu ChromaDB funcționează bine pentru câteva sute de documente, dar pe măsură ce baza de cunoștințe crește (mii de documente, milioane de fragmente), lipsa filtrării avansate pe metadate și limitările de performanță devin vizibile.
Qdrant este o bază de date vectorială concepută pentru scalare — oferă filtrare puternică pe metadate (ex. „caută doar în documente din categoria Securitate, modificate în ultimele 30 de zile”), dashboard vizual integrat și performanță mai bună la volume mari.
Backup
Migrarea nu șterge datele din ChromaDB — se creează o bază Qdrant separată, în paralel. Păstrează folderul ChromaDB existent ca fallback până confirmi funcționarea corectă.
Copy-Item "C:\RAG\db" "C:\RAG\db_chromadb_backup" -Recurse
Solutie
Pasi de urmat
Instalare Qdrant (Docker) și client Python
# Ruleaza Qdrant local intr-un container
docker run -d --name qdrant `
-p 6333:6333 -p 6334:6334 `
-v "C:\RAG\qdrant_storage:/qdrant/storage" `
qdrant/qdrant
# Instaleaza clientul Python
pip install qdrant-client ollama pymupdf python-docx
Verificare funcționare Qdrant
# Dashboard-ul vizual Qdrant este disponibil in browser
Start-Process "http://localhost:6333/dashboard"
Script de migrare/indexare cu metadate extinse
Salvează ca qdrant_indexer.py:
# ============================================================
# Qdrant Indexer - migrare/indexare cu metadate avansate
# Autor: Claudiu C.
# ============================================================
import os
import fitz
import ollama
from datetime import datetime
from docx import Document
from qdrant_client import QdrantClient
from qdrant_client.models import Distance, VectorParams, PointStruct
DOCS_PATH = r"C:\RAG\Documente"
EMBED_MODEL = "nomic-embed-text"
COLLECTION = "documente_interne"
VECTOR_SIZE = 768 # dimensiunea vectorilor nomic-embed-text
client = QdrantClient(host="localhost", port=6333)
def ensure_collection():
collections = [c.name for c in client.get_collections().collections]
if COLLECTION not in collections:
client.create_collection(
collection_name=COLLECTION,
vectors_config=VectorParams(size=VECTOR_SIZE, distance=Distance.COSINE)
)
def extract_pdf(path):
doc = fitz.open(path)
return "\n".join(page.get_text() for page in doc)
def extract_word(path):
doc = Document(path)
return "\n".join(p.text for p in doc.paragraphs if p.text.strip())
def chunk_text(text, size=500):
words, chunks, current = text.split(), [], []
for word in words:
current.append(word)
if len(" ".join(current)) >= size:
chunks.append(" ".join(current))
current = []
if current:
chunks.append(" ".join(current))
return chunks
def get_embedding(text):
return ollama.embeddings(model=EMBED_MODEL, prompt=text)["embedding"]
def index_document(filepath, category="General"):
filename = os.path.basename(filepath)
if filename.endswith(".pdf"):
text = extract_pdf(filepath)
else:
text = extract_word(filepath)
chunks = chunk_text(text)
points = []
for i, chunk in enumerate(chunks):
embedding = get_embedding(chunk)
points.append(PointStruct(
id=hash(f"{filename}_{i}") & 0x7FFFFFFF,
vector=embedding,
payload={
"source": filename,
"category": category,
"chunk_text": chunk[:200],
"indexed_at": datetime.now().isoformat(),
"chunk_index": i
}
))
client.upsert(collection_name=COLLECTION, points=points)
return len(points)
def main():
ensure_collection()
files = [f for f in os.listdir(DOCS_PATH) if f.endswith((".pdf", ".docx"))]
for i, filename in enumerate(files, 1):
print(f"[{i}/{len(files)}] Indexez: {filename}")
filepath = os.path.join(DOCS_PATH, filename)
# Categorie simpla dupa numele folderului sau prefix fisier
category = "Securitate" if "securit" in filename.lower() else "General"
count = index_document(filepath, category=category)
print(f" -> {count} fragmente indexate (categorie: {category})")
print(f"\n[DONE] Total puncte in colectie: {client.count(COLLECTION).count}")
if __name__ == "__main__":
main()
Interogare cu filtrare pe metadate
Salvează ca qdrant_query.py:
# ============================================================
# Qdrant Query - interogare cu filtrare pe metadate
# ============================================================
import ollama
from qdrant_client import QdrantClient
from qdrant_client.models import Filter, FieldCondition, MatchValue
EMBED_MODEL = "nomic-embed-text"
GEN_MODEL = "llama3"
COLLECTION = "documente_interne"
client = QdrantClient(host="localhost", port=6333)
def get_embedding(text):
return ollama.embeddings(model=EMBED_MODEL, prompt=text)["embedding"]
def search(question, category_filter=None, top_k=3):
embedding = get_embedding(question)
query_filter = None
if category_filter:
query_filter = Filter(
must=[FieldCondition(key="category", match=MatchValue(value=category_filter))]
)
results = client.search(
collection_name=COLLECTION,
query_vector=embedding,
query_filter=query_filter,
limit=top_k
)
return results
def generate_answer(question, results):
context = "\n\n".join([r.payload["chunk_text"] for r in results])
sources = list(set(r.payload["source"] for r in results))
prompt = f"""Raspunde EXCLUSIV pe baza documentatiei de mai jos, in limba romana.
Daca informatia nu se regaseste, spune clar acest lucru.
DOCUMENTATIE:
{context}
INTREBARE:
{question}
"""
response = ollama.chat(model=GEN_MODEL, messages=[{"role": "user", "content": prompt}])
return response["message"]["content"], sources
if __name__ == "__main__":
print("=== Chatbot RAG cu Qdrant ===")
print("Scrie 'exit' pentru a iesi. Prefixeaza cu 'categorie:Securitate ' pentru filtrare.\n")
while True:
question = input("Intrebare: ").strip()
if question.lower() == "exit":
break
category = None
if question.startswith("categorie:"):
parts = question.split(" ", 1)
category = parts[0].replace("categorie:", "")
question = parts[1] if len(parts) > 1 else ""
results = search(question, category_filter=category)
answer, sources = generate_answer(question, results)
print(f"\nRASPUNS:\n{answer}")
print(f"\nSurse: {', '.join(sources)}\n")
print("-" * 50)
Rulare
# Indexare initiala (sau re-indexare la documente noi)
python qdrant_indexer.py
# Interogare interactiva
python qdrant_query.py
Tip solutie
PermanentPlan de restaurare in caz de nefunctionare
# Opreste si sterge containerul Qdrant
docker stop qdrant
docker rm qdrant
# ChromaDB (backup-ul facut la Pasul de backup) ramane functional ca fallback
# Sterge datele Qdrant daca migrarea se anuleaza definitiv
Remove-Item “C:\RAG\qdrant_storage” -Recurse -Force
Leave A Comment?