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. Для типовых ответов (гарантия, сроки доставки) вариативность модели скорее вредна, а токены тратятся.
Рассмотренные варианты
-
Расширить
prompt_templatesполемwb_account_id. Плюс: одна сущность. Минусы: версионирование и обязательное объявление переменных продавцу не нужны и мешают; общесистемные и приватные шаблоны смешались бы в одной таблице, и любая выборка обязана была бы помнить про это различие. -
Отдельная сущность
autoreply_reply_templates;templateIdсценария резолвится в два реестра по очереди. -
Отдельная сущность + миграция старых ссылок. Отвергнут: системные промпт-шаблоны продолжают использоваться генерацией, переносить их в кабинеты некуда.
Решение
Выбран вариант 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 и убирает вариативность там, где она не нужна;
- ошибка в шаблоне ловится на сохранении, а не в проде.
-
Отрицательные / компромиссы:
- два реестра шаблонов — это две сущности с похожим названием. Разведены ролями и порядком резолва, но новичку придётся объяснить разницу;
- у шаблонов продавца нет версионирования: правка перезаписывает текст. Осознанно — версии нужны промптам, а не типовым ответам;
- список переменных закрыт, расширение требует правки кода. Это цена ранней проверки.
-
Дальше: если понадобится история правок шаблонов — отдельное решение, по образцу версий промпт-шаблонов.