"""Одноразовая миграция векторов из файлового FAISS в Qdrant. Запуск (после старта qdrant и выставления VECTOR_BACKEND=qdrant в .env): docker compose -f docker-compose.prod.yml --profile qdrant up -d qdrant docker compose -f docker-compose.prod.yml exec worker-gpu python -m app.migrate_faiss_to_qdrant Идемпотентна: повторный запуск перезапишет те же точки по doc_id, дублей не будет. После миграции стоит сверить: QdrantManager.total_vectors() == count(documents). """ import logging import numpy as np logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s") logger = logging.getLogger("migrate_faiss_to_qdrant") def migrate(batch_size: int = 1000) -> int: """Перелить все векторы из FAISS-индекса в Qdrant. Возвращает число векторов.""" import faiss from app.config import settings from app.qdrant_manager import QdrantManager index = faiss.read_index(settings.FAISS_INDEX_PATH) if not hasattr(index, "id_map"): raise SystemExit("FAISS-индекс без id_map (несовместимый тип) — миграция невозможна") ids = faiss.vector_to_array(index.id_map).astype(np.int64) total = int(len(ids)) logger.info("FAISS: %d векторов к миграции в Qdrant", total) migrated = 0 for start in range(0, total, batch_size): chunk = ids[start : start + batch_size] vecs = np.vstack([index.reconstruct(int(i)) for i in chunk]).astype(np.float32) QdrantManager.add_vectors(vecs, [int(i) for i in chunk]) migrated += len(chunk) logger.info("Мигрировано %d/%d", migrated, total) logger.info("Готово. Точек в Qdrant: %d", QdrantManager.total_vectors()) return migrated if __name__ == "__main__": migrate()