support-bot-saas/backend/app/services/vector_store.py
ed0ss 0063df0a87
Some checks are pending
CI / lint-backend (push) Waiting to run
CI / lint-frontend (push) Waiting to run
CI / build-backend (push) Blocked by required conditions
CI / build-frontend (push) Blocked by required conditions
Реструктуризация проекта в SaaS: FastAPI бэкенд, React SPA фронтенд, Helm-чарт, CI/CD
- Бэкенд: FastAPI + SQLAlchemy async + PostgreSQL, JWT-аутентификация,
  Qdrant multi-tenant векторное хранилище, Celery воркер
- Фронтенд: React + Vite + TypeScript SPA (логин, регистрация,
  дашборд, поиск по коллекциям, настройки)
- Инфраструктура: docker-compose, Helm-чарт, GitHub Actions CI,
  Prometheus/Grafana мониторинг
- Embedding через OpenRouter (nvidia/llama-nemotron-embed-vl-1b-v2:free)
- LLM: google/gemma-4-31b-it:free через OpenRouter
2026-06-20 17:40:27 +03:00

91 lines
2.6 KiB
Python

from qdrant_client import QdrantClient
from qdrant_client.http.models import (
PointStruct,
Filter,
FieldCondition,
MatchValue,
VectorParams,
Distance,
)
from app.config import settings
client = QdrantClient(url=settings.qdrant_url)
COLLECTION_NAME = "tickets"
_vector_size: int | None = None
def get_vector_size() -> int:
global _vector_size
if _vector_size is not None:
return _vector_size
collections = client.get_collections().collections
for c in collections:
if c.name == COLLECTION_NAME:
info = client.get_collection(COLLECTION_NAME)
_vector_size = info.config.params.vectors.size
return _vector_size
return 0
async def ensure_collection(size: int):
global _vector_size
collections = client.get_collections().collections
exists = any(c.name == COLLECTION_NAME for c in collections)
if not exists:
client.create_collection(
collection_name=COLLECTION_NAME,
vectors_config=VectorParams(size=size, distance=Distance.COSINE),
)
_vector_size = size
else:
get_vector_size()
async def upsert_chunks(tenant_id: str, chunks: list[dict], vectors: list[list[float]]):
size = len(vectors[0])
await ensure_collection(size)
points = []
for chunk, vector in zip(chunks, vectors):
points.append(
PointStruct(
id=hash(f"{tenant_id}:{chunk['ticket_id']}") & 0x7FFFFFFFFFFFFFFF,
vector=vector,
payload={
"tenant_id": tenant_id,
"ticket_id": chunk["ticket_id"],
"client": chunk.get("client", ""),
"category": chunk.get("category", ""),
"collection_id": chunk.get("collection_id", ""),
"search_text": chunk.get("search_text", ""),
"full_text": chunk.get("full_text", ""),
},
)
)
client.upsert(collection_name=COLLECTION_NAME, points=points)
async def search(
tenant_id: str,
query_vector: list[float],
collection_id: str | None = None,
k: int = 20,
):
must_conditions = [FieldCondition(key="tenant_id", match=MatchValue(value=tenant_id))]
if collection_id:
must_conditions.append(FieldCondition(key="collection_id", match=MatchValue(value=collection_id)))
results = client.query_points(
collection_name=COLLECTION_NAME,
query=query_vector,
query_filter=Filter(must=must_conditions),
limit=k,
with_payload=True,
)
return results.points