0015. RAG-минимум: контекст из базы знаний на pgvector
Копия ADR из репозитория:
backend/docs/adr/0015-rag-pgvector-knowledge-context.md. Канонический текст — в репозитории; портал обновляется командойnpm run sync:adr.
- Статус: accepted
- Дата: 2026-06-24
- Связанные задачи: OP-49 (F7 · базовый слой контекста/RAG)
Контекст и проблема
Модули ИИ-помощника (5.9) и юр-блока (5.12) подставляют в промпт релевантный контекст из базы знаний (5.15). Нужен базовый механизм (RAG-минимум): проиндексировать материал (эмбеддинги), по запросу вернуть top-k фрагментов и собрать их в контекст для промпта.
Ограничения и развилки:
- Тикет фиксирует подход — «pgvector в PostgreSQL». Векторное хранилище выбирать не нужно.
- Эмбеддинги — через общий LLM-слой F7 (OP-47,
LLM_PROVIDER.embeddings). Живого LLM-конфига пока нет; тесты — на моках (как OP-47/48). - Размерность вектора колонки pgvector фиксируется в DDL (
vector(N)) и зависит от модели эмбеддингов. На момент OP-49 модель не выбрана (нетLLM_BASE_URL/LLM_EMBEDDINGS_MODEL). - TypeORM не знает тип
vector: объявление такой колонки в entity ломает построение метаданных на старте (падаютAppModule/docs:openapi/e2e). - Источник материалов (5.15) — вне scope; реальных данных пока нет.
Рассмотренные варианты
Векторное хранилище
- pgvector в PostgreSQL (выбран — задан тикетом). Один управляемый Postgres, без нового
инфра-компонента; точный k-NN/ANN через
<=>. Минус — расширение должно быть установлено в образе БД (CI/managed). - Внешний векторный движок (Qdrant/Weaviate). Отвергнут: новый сервис в инфраструктуре заказчика ради «минимума» избыточен и противоречит тикету.
Моделирование вектор-колонки
- Raw-SQL репозиторий поверх
DataSource(выбран). Вектор пишем/читаем литералом'[..]'::vector; entity для таблицы не создаём. Плюсы: не ломает старт, полностью мокается в unit-тестах, нет лишней зависимости. Минус: отступление от entity-конвенции (оправдано типом). - Entity с колонкой
type: 'text'+ трансформер. Отвергнут: вводит в заблуждение (тип в коде ≠ тип в БД),.save()всё равно требует::vector-каста — выгоды нет. - Зависимость
pgvector(npm) + кастомный тип TypeORM. Отвергнут: лишняя зависимость, а<=>-поиск всё равно идёт через query-builder/raw — для «минимума» избыточно.
Размерность эмбеддинга
- Константа
KNOWLEDGE_EMBEDDING_DIMENSION = 1536(выбран) — размер OpenAI text-embedding-3-small / ada-002. Колонкаvector(1536); сервис валидирует длину каждого эмбеддинга и бросаетRagEmbeddingDimensionErrorпри несовпадении. - Env
RAG_EMBEDDING_DIMENSION. Отвергнут: DDL-размерность всё равно фиксирована в миграции — смена значения требует новой миграции, env лишь создаёт риск рассинхрона со схемой.
Решение
Модуль src/modules/rag/:
KnowledgeChunkRepository(DI-провайдер над@InjectDataSource()):insertMany(вставка с эмбеддингом через$n::vector),searchTopK(ORDER BY embedding <=> $1::vector LIMIT k, score =1 - distance). Параметризованный SQL — без склейки строк.RagService:index(input|input[])(эмбеддит контент → валидирует размерность → сохраняет),search(query, opts?)(эмбеддит запрос → top-k),buildContext(query, opts?)(search + чистыйformatContext→{ text, chunks }для подстановки в промпт). Зависит отLLM_PROVIDERи репозитория.- Миграция
CreateKnowledgeChunks:CREATE EXTENSION IF NOT EXISTS "vector"+ таблицаknowledge_chunks(embedding vector(1536) NOT NULL) + HNSW-индексvector_cosine_ops.down()дропает таблицу; расширение оставляет (идемпотентно, как иIF NOT EXISTSвup). embeddingне моделируется TypeORM — таблицей владеет миграция.
Последствия
- Положительные: появился переиспользуемый RAG-механизм за чистым API (
RagService); полностью покрыт unit-тестами на моках (БД/LLM не нужны для Gate 1); вектор-операции изолированы в одном репозитории; нет новых runtime-зависимостей. - Инфраструктура: образ Postgres должен нести pgvector. CI и
docker-composeпереведены наpgvector/pgvector:pg16. Managed Postgres в Coolify (prod/stage) должен иметь доступное расширениеvector— предусловие деплоя (DevOps, OP-51); вне кода OP-49. - Размерность 1536 — допущение. Если выбранная модель эмбеддингов даёт другую размерность,
нужна follow-up миграция (
ALTER/новая колонка) и правка константы; до тех пор сервис падает явнойRagEmbeddingDimensionError. Реальных данных пока нет (5.15 вне scope), риск миграции данных отсутствует. - Отступление от entity-конвенции для таблицы
knowledge_chunks— осознанное, обусловлено отсутствием поддержки типаvectorв TypeORM; задокументировано в TSDoc репозитория. - Без эндпоинтов — механизм потребляют будущие фиче-модули через DI; OpenAPI-контракт не меняется.