ИИ-подсистема
Общий слой 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_PROVIDER | openai-compatible | выбор провайдера |
LLM_BASE_URL, LLM_API_KEY | — | адрес и ключ API |
LLM_MODEL, LLM_EMBEDDINGS_MODEL | — | модель чата и модель эмбеддингов |
LLM_TIMEOUT_MS | 30 000 | таймаут запроса |
LLM_RETRY_ATTEMPTS, LLM_RETRY_BASE_MS | 3, 500 | политика повторов |
LLM_MAX_RETRY_AFTER_MS | 30 000 | потолок паузы по Retry-After |
LLM_MAX_TOKENS | 1024 | потолок ответа, если вызывающий не задал свой |
Полный перечень — Переменные окружения.
Лимиты расхода (ADR-0047)
Три независимых контроля стоимости:
max_tokensпо умолчанию (LLM_MAX_TOKENS) — потолок цены одного ответа.@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.- Суточная квота токенов на пользователя —
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(cron15 */3 * * *МСК) читает метод WBGET /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), бизнес-описание — ИИ-агент, ИИ-помощник, Генерация контента, База знаний.