Перейти к основному содержимому

ИИ-подсистема

Общий слой F7 даёт всем функциональным модулям единый доступ к нейросети, контролю расхода и векторному поиску. Решения — ADR-0014, ADR-0015, ADR-0047, ADR-0045.

Абстракция LLM-провайдера (modules/llm)​

  • Интерфейс LlmProvider: chat, generate (обёртка над chat), embeddings. Типы запросов нейтральны к провайдеру; LlmUsage несёт promptTokens/completionTokens/totalTokens.
  • Единственная реализация — OpenAiCompatibleProvider на нативном fetch: POST {LLM_BASE_URL}/chat/completions и /embeddings, заголовок Authorization: Bearer (если ключ задан). Модуль @Global(), потребители инжектят токен LLM_PROVIDER без импорта.
  • Устойчивость (llm-retry.ts, зеркало wb-retry): повторы на 429/5xx/сети с экспоненциальным backoff и jitter; на 4xx — без повторов; Retry-After учитывается с потолком LLM_MAX_RETRY_AFTER_MS.
  • Старт без LLM: конфигурация опциональна; отсутствие LLM_BASE_URL/LLM_MODEL бросает LlmConfigError в момент вызова, а не при запуске приложения.
  • Логи: model, токены, durationMs, status, code; ключ и контент промптов не логируются.
ПеременнаяПо умолчаниюНазначение
LLM_PROVIDERopenai-compatibleвыбор провайдера
LLM_BASE_URL, LLM_API_KEY—адрес и ключ API
LLM_MODEL, LLM_EMBEDDINGS_MODEL—модель чата и модель эмбеддингов
LLM_TIMEOUT_MS30 000таймаут запроса
LLM_RETRY_ATTEMPTS, LLM_RETRY_BASE_MS3, 500политика повторов
LLM_MAX_RETRY_AFTER_MS30 000потолок паузы по Retry-After
LLM_MAX_TOKENS1024потолок ответа, если вызывающий не задал свой

Полный перечень — Переменные окружения.

Лимиты расхода (ADR-0047)​

Три независимых контроля стоимости:

  1. max_tokens по умолчанию (LLM_MAX_TOKENS) — потолок цены одного ответа.
  2. @ThrottleLlm() на всех LLM-роутах — не более THROTTLE_LLM_LIMIT (30) вызовов в час с IP: POST /ai-assistant/{ask,analyze}, POST /ai-agent/tasks/{parse,plan}, POST /content/cards/generate, POST /legal/claims, GET /knowledge-base/search.
  3. Суточная квота токенов на пользователя — LLM_DAILY_TOKEN_QUOTA = 200 000 (константа модуля, не env). Счётчик INCRBY в Redis по ключу op:llm:quota:{userId}:{YYYY-MM-DD} с TTL до полуночи UTC; списывается фактический usage.totalTokens (генерация и эмбеддинги). Проверка — интерцептором @LlmQuota() до вызова обработчика; при исчерпании — 429 + Retry-After до конца суток.

Длина входа ограничена валидацией (question — до 2000 символов, поисковый запрос — до 500).

RAG на pgvector​

  • Таблица knowledge_chunks: embedding vector(1536) + HNSW-индекс vector_cosine_ops; таблицей владеет миграция (TypeORM тип vector не поддерживает). Образ PostgreSQL — pgvector/pgvector:pg16.
  • RagService: index (эмбеддинг → проверка размерности → сохранение), search (эмбеддинг запроса → top-k, по умолчанию 5, score = 1 − cosine distance), buildContext (поиск + форматирование для промпта). Несовпадение размерности — RagEmbeddingDimensionError.
  • Контроль доступа в SQL: searchTopK обязательно принимает актёра и фильтрует по видимости, статусу и автору материала до ORDER BY … LIMIT; гранты проверяются живым EXISTS (отзыв действует сразу). Каждая строка выдачи повторно проверяется canAccessMaterial, расхождение логируется как rag.access-drift. Фрагменты без владельца видны только администратору.

База знаний и индексация (modules/knowledge-base)​

  • Материалы (kb_materials) группируются по разделам, имеют visibility, status (черновик/опубликован) и гранты доступа.
  • KbIngestService: извлекает текст изолированными экстракторами (PDF, Excel, текст), режет на перекрывающиеся фрагменты (по умолчанию 1000 символов с перекрытием 200) и индексирует через RagService, привязывая фрагмент к материалу и его правам. Ссылки (URL) сервер не скачивает (защита от SSRF) — индексируется собственный текст материала.
  • Приём видео и записей через распознавание речи — выбранное направление (ADR-0032), в текущем коде приёма видео нет.
  • Новости WB: очередь kb-wb-news-sync (cron 15 */3 * * * МСК) читает метод WB GET /api/communications/v2/news токеном любого активного кабинета (лимит WB — 1 запрос в минуту, при первом запуске — история 90 дней) и складывает новости в общую библиотеку. Подробности — Фоновые задачи.
  • Поиск GET /knowledge-base/search векторизует запрос на каждый вызов, поэтому защищён @ThrottleLlm().

Потребители​

МодульЧто делает с LLMОсобенности
ai-assistant (5.9)отвечает на вопросы по WB с опорой на RAG; анализирует товарпри отсутствии релевантного контекста — детерминированный ответ без вызова LLM; каталог инструментов: метрики РНП, прибыль, продажи, реклама по товару
ai-agent (5.10)разбор задачи → план → выполнениенейросеть только планирует; применение — после подтверждения и через guardrails (см. ниже)
content (5.13)генерация текстов карточекстрогий JSON-формат ответа; при сбое LLM — детерминированный черновик
autoreply (5.5)черновики ответов на отзывы/вопросызащита от prompt injection (ADR-0045): текст покупателя — недоверенные данные, проверка ответа перед авто-отправкой, аудит срабатываний
legal (5.12)черновики досудебных претензий@ThrottleLlm()
prompt-templatesверсионируемые шаблоны промптовподстановка переменных, конфликт версий — явная ошибка

Guardrails ИИ-агента​

Нейросеть не меняет рекламу сама: ai-agent строит план, сохраняет его в очередь подтверждений (ADR-0061) и после решения пользователя выполняет действия через инструменты автобиддера. Чистая функция checkGuardrails отклоняет опасные значения (изменение ставок не более ±50 % за раз, дневной бюджет 1–1 000 000 ₽, границы цели стратегии). Все шаги пишутся в аудит ai-agent.action.*.

См. также: Безопасность, Данные (pgvector), бизнес-описание — ИИ-агент, ИИ-помощник, Генерация контента, База знаний.