Qdrant - Vector Database
Qdrant + NestJS
У вас есть RAG pipeline. Теперь нужно обернуть его в production NestJS сервис: принимать документы через REST API, асинхронно индексировать через BullMQ, отвечать на вопросы. Это финальная точка курса - соберём всё вместе.
- **Document management system:** POST /documents → BullMQ → chunking + embeddings + Qdrant; GET /search → semantic search → LLM answer
- **Knowledge base API:** мультитенантный поиск через JWT tenant_id extraction → TenantScopedRepository → QdrantService
- **AI assistant backend:** NestJS + Qdrant + OpenAI + BullMQ — полный production stack для RAG приложения
Предварительные знания
QdrantModule и QdrantService: NestJS паттерн
**NestJS** - идеальная среда для production Qdrant сервисов: DI, ConfigModule, модульность. Паттерн: кастомный `QdrantModule` с `forRoot()` (глобальная конфигурация) + `QdrantService` (обёртка над клиентом). Это то же самое что TypeORM, Redis и другие NestJS интеграции.
**`@Global()` + `forRoot()`** - паттерн из NestJS экосистемы (TypeOrmModule, BullModule). `@Global()` означает что QdrantService доступен во всём приложении без повторного импорта QdrantModule в каждый feature module. Один инстанс клиента = один пул соединений.
Вы добавили QdrantModule.forRoot() в AppModule. Теперь нужен QdrantService в SearchModule. Что нужно сделать?
REST API: endpoint для векторного поиска
**Полный пример:** принимаем документ через HTTP → возвращаем семантически похожие. Включает: DTO с валидацией, embeddings через OpenAI, поиск в Qdrant с фильтрацией, форматирование ответа.
**`score_threshold: 0.7`** - важный параметр. Без него Qdrant вернёт топ-N результатов даже если они нерелевантны. Для текстового поиска с text-embedding-3-small: 0.65-0.75 - хороший диапазон. Замерьте на своих данных: слишком высокий порог = пропущенные релевантные результаты.
SearchService делает OpenAI API вызов на каждый запрос. Как оптимизировать для частых повторяющихся запросов?
BullMQ: асинхронная индексация документов
**Синхронная индексация в HTTP запросе** - антипаттерн: пользователь ждёт генерации embeddings + записи в Qdrant (1-10 секунд). Решение: BullMQ очередь для асинхронной индексации. Эндпоинт принимает документ и сразу отвечает 202 Accepted. Processor обрабатывает в фоне.
«BullMQ только для тяжёлых задач - для индексации 1 документа достаточно синхронного await»
Индексация документа = 1-10 секунд (OpenAI API + Qdrant write). HTTP таймаут, плохой UX, невозможность retry. BullMQ - правильный выбор даже для «одного документа».
OpenAI embeddings API: p50 ~200ms, p99 ~2s. При 50 чанках × 200ms = 10 секунд minimum. За это время: HTTP может таймаутнуть, пользователь уйдёт, retry при ошибке сложен. BullMQ: immediate 202 response + retry logic + visibility в Bull Board dashboard.
IndexingProcessor падает с ошибкой OpenAI API (rate limit) на шаге генерации embeddings. Job настроен с attempts: 3. Что произойдёт?
Ключевые идеи
- **QdrantModule.forRoot()** — глобальный модуль с DI, @Global() + ConfigModule интеграция
- **QdrantService** — обёртка над @qdrant/js-client-rest: getClient() для нативного доступа, helper методы для частых операций
- **SearchService** — embedding генерация + поиск с фильтрацией, score_threshold для отсечения нерелевантных результатов
- **IndexingProcessor extends WorkerHost** — BullMQ processor: chunking → batch embeddings → upsert; attempts + backoff для надёжности
- **Полный flow:** POST /documents → 202 Accepted → BullMQ job → IndexingProcessor → Qdrant; POST /search → embedding → search → результаты
Что дальше
Вы прошли полный курс по Qdrant: от первого поиска до production NestJS интеграции с BullMQ, мультитенантностью и RAG pipeline.
- Production RAG Pipeline — Детали pipeline который лежит в основе IndexingProcessor и SearchService
- Мультитенантность — Добавить tenant isolation в QdrantService используя custom sharding
- Qdrant + LangChain — Альтернатива нативному QdrantService - использовать QdrantVectorStore из LangChain в NestJS
Вопросы для размышления
- IndexingProcessor использует OpenAI для embeddings. Как написать unit-тест для handleIndexDocument без реальных API вызовов? Как замокать QdrantService и OpenAI?
- В production: пользователь загрузил документ (202 Accepted), но хочет знать когда индексация завершилась. Как реализовать нотификацию? (WebSocket, SSE, webhook, polling)
- QdrantModule использует один @Global() сервис. Как адаптировать для мультитенантности — чтобы каждый запрос автоматически использовал правильный tenant context без явной передачи tenantId в каждый метод?