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

0055. Реестр шаблонов ответов продавца и режим без LLM

Копия ADR из репозитория: backend/docs/adr/0055-seller-reply-templates.md. Канонический текст — в репозитории; портал обновляется командой npm run sync:adr.

  • Статус: accepted
  • Дата: 2026-09-18
  • Связанные задачи: OP-224 (5.5); опирается на OP-48 (промпт-шаблоны), OP-72/73 (сценарии и генерация), ADR-0045 (защита от инъекций)

Контекст и проблема​

Вкладка «Шаблоны» в интерфейсе автоответов была пустой, а сценарий не мог сослаться на шаблон через API. При этом «шаблоны» в системе уже существовали — prompt_templates (OP-48): версионируемые, с объявленными переменными, общие на всю систему и не привязанные к кабинету.

Это инструмент разработки промптов, а не редактируемый продавцом текст. Отдать его в UI значило бы дать одному продавцу править текст, которым отвечают все остальные.

Вторая часть задачи: сценарий всегда прогонял шаблон через LLM. Для типовых ответов (гарантия, сроки доставки) вариативность модели скорее вредна, а токены тратятся.

Рассмотренные варианты​

  1. Расширить prompt_templates полем wb_account_id. Плюс: одна сущность. Минусы: версионирование и обязательное объявление переменных продавцу не нужны и мешают; общесистемные и приватные шаблоны смешались бы в одной таблице, и любая выборка обязана была бы помнить про это различие.

  2. Отдельная сущность autoreply_reply_templates; templateId сценария резолвится в два реестра по очереди.

  3. Отдельная сущность + миграция старых ссылок. Отвергнут: системные промпт-шаблоны продолжают использоваться генерацией, переносить их в кабинеты некуда.

Решение​

Выбран вариант 2.

Две сущности с разными ролями. prompt_templates остаётся инструментом разработки промптов; autoreply_reply_templates — приватный для кабинета редактируемый текст с name/kind/body.

templateId сценария резолвится сначала в реестр продавца, затем в системный. Порядок такой, потому что id из пользовательского реестра встречается чаще, а обратный порядок дал бы системному шаблону возможность перехватить ссылку при коллизии UUID. Сценарии, созданные до OP-224, продолжают работать без миграции.

Плейсхолдеры валидируются при сохранении, а не при генерации. Список закрытый и совпадает с контекстом обращения (reviewText, rating, productName, brand, supplierArticle, kind). Ошибка в шаблоне видна продавцу сразу, а не превращается в неотправленный ответ спустя сутки. Шаблон без единой переменной — нормальный случай, в отличие от промпт-шаблона.

useLlm на сценарии, по умолчанию true. При false ответ собирается подстановкой и модель не вызывается вовсе. Код сравнивает именно с false: трактовать отсутствие поля как «без LLM» значило бы молча сменить поведение сценариев, созданных раньше.

Проверка на инъекции применяется и к ответам без LLM. Подозрительное обращение не должно уходить в автопубликацию независимо от того, кто сочинил ответ — модель или шаблон (ADR-0045).

Удаление мягкое. Сценарии ссылаются на шаблон значением (без FK), и жёсткое удаление оставило бы висячую ссылку, которая проявилась бы только в момент генерации ответа.

Последствия​

  • Положительные:

    • вкладка «Шаблоны» наполняется; сценарий ссылается на шаблон через API;
    • режим «отвечать строго моим текстом» экономит бюджет LLM и убирает вариативность там, где она не нужна;
    • ошибка в шаблоне ловится на сохранении, а не в проде.
  • Отрицательные / компромиссы:

    • два реестра шаблонов — это две сущности с похожим названием. Разведены ролями и порядком резолва, но новичку придётся объяснить разницу;
    • у шаблонов продавца нет версионирования: правка перезаписывает текст. Осознанно — версии нужны промптам, а не типовым ответам;
    • список переменных закрыт, расширение требует правки кода. Это цена ранней проверки.
  • Дальше: если понадобится история правок шаблонов — отдельное решение, по образцу версий промпт-шаблонов.