Задача: Реализовать собственное ядро ИИ-диалогов с динамическими сценариями
Реализовать собственное ядро ИИ-диалогов с динамическими сценариями
С нуля реализовать мультипользовательский диалоговый runtime без n8n и подобных workflow-решений, с внешне задаваемыми комплексными сценариями и независимым состоянием каждого диалога.
Реализовать собственное ядро ИИ-диалогов с динамическими сценариями
Контекст
Проект «Центр ИИ-агентов» должен иметь собственную диалоговую часть. Нельзя строить её на n8n, Make, готовых chatbot/workflow-конструкторах или аналогичных системах, где основная логика диалога исполняется внешним движком.
Нужно реализовать своё ядро, которое принимает извне описание комплексного сценария, ведёт пользователя по нему динамически и возвращает результат. Каждый пользовательский диалог должен существовать и рассчитываться как отдельная независимая сущность со своим состоянием, историей и результатом.
Цель
Создать универсальный сервис исполнения ИИ-диалогов, который можно использовать разными сайтами и агентами, не зашивая конкретный сценарий в код приложения.
Сервис должен уметь:
- принять сценарий диалога извне;
- создать отдельную сессию/диалог для конкретного пользователя;
- на каждом сообщении определить следующий шаг динамически, исходя из сценария, накопленного состояния и ответа пользователя;
- обращаться к LLM там, где это предусмотрено логикой;
- сохранять состояние и историю диалога;
- завершать диалог с формализованным результатом;
- параллельно обслуживать много пользователей и много независимых диалогов.
Ключевой архитектурный принцип
Сценарий — это данные, а не код конкретного чата.
Диалоговый движок должен быть универсальным. Внешняя система передаёт ему декларативное описание сценария, а runtime интерпретирует его и управляет переходами.
Не должно требоваться программировать новый backend-flow под каждый новый сценарий.
Что реализовать
1. Модель сценария
Спроектировать формат сценария, который можно передавать через API и хранить версионно.
Минимально предусмотреть:
- идентификатор и версию сценария;
- стартовую точку;
- шаги/узлы;
- системные инструкции для LLM;
- ожидаемые пользовательские данные;
- условия и ветвления;
- переходы между шагами;
- промежуточные переменные/контекст;
- вычисляемые значения;
- действия до/после шага;
- условия завершения;
- схему итогового результата.
Сценарий должен поддерживать не только линейную цепочку вопросов, но и динамические ветвления.
2. Runtime диалога
Реализовать собственную машину исполнения сценария.
На каждом входящем сообщении runtime должен:
- загрузить состояние конкретного диалога;
- определить текущий узел сценария;
- обработать вход пользователя;
- при необходимости вызвать LLM;
- валидировать/нормализовать полученные данные;
- вычислить условия перехода;
- обновить состояние;
- сформировать следующий ответ;
- сохранить историю и новое состояние атомарно;
- вернуть наружу ответ и техническое состояние выполнения.
Логика переходов должна исполняться внутри нашего сервиса, а не внутри внешнего workflow-движка.
3. Состояние диалога
Каждый диалог хранить отдельно.
Нужно предусмотреть как минимум:
dialogId;- tenant/site/agent/user identifiers;
- scenarioId + scenarioVersion;
- текущий узел;
- accumulated state / variables;
- историю сообщений;
- результаты LLM-вызовов, необходимые для воспроизводимости;
- статус: active / completed / failed / cancelled;
- timestamps;
- финальный структурированный result.
Один пользователь может иметь несколько независимых диалогов.
4. Мультипользовательский режим
Сервис должен корректно работать при параллельных диалогах разных пользователей и сайтов.
Обязательно предусмотреть:
- изоляцию состояния по диалогам;
- отсутствие глобального mutable state между пользователями;
- защиту от одновременной обработки двух сообщений одного диалога;
- идемпотентность повторной доставки сообщения;
- возможность горизонтального масштабирования нескольких экземпляров runtime;
- tenant/site isolation на уровне API и хранения данных.
5. Внешний API
Нужен понятный программный контракт для внешних систем.
Минимальный набор операций:
- зарегистрировать/обновить версию сценария;
- создать диалог по сценарию;
- отправить сообщение в конкретный диалог;
- получить текущее состояние/статус;
- получить историю;
- получить финальный результат;
- отменить/закрыть диалог.
Ответ на sendMessage должен содержать не только текст для пользователя, но при необходимости и машинно-читаемую часть:
- статус;
- текущий/следующий шаг;
- extracted values;
- result при завершении;
- технические метаданные выполнения.
6. Работа с LLM
LLM является частью runtime, но не должна сама быть единственным источником управления потоком.
Нужно разделить:
- детерминированную логику переходов;
- извлечение/классификацию данных через LLM;
- генерацию текста ответа;
- валидацию структурированных ответов модели.
Для структурированных операций использовать schema-constrained output и обязательную валидацию результата модели.
Предусмотреть обработку:
- timeout;
- malformed output;
- retry;
- fallback;
- rate limits;
- ошибок провайдера.
7. Версионирование и воспроизводимость
Запущенный диалог должен продолжать исполняться на той версии сценария, на которой был создан, даже если позже опубликована новая версия.
Нельзя менять семантику уже идущего диалога незаметной правкой сценария.
8. Наблюдаемость
Для каждого шага сохранять технический trace:
- вход;
- выбранный узел;
- принятое решение;
- переход;
- вызовы модели;
- latency;
- ошибки;
- token/cost usage, если доступно;
- итог шага.
Это должно позволять расследовать, почему конкретный диалог пошёл по определённой ветке.
9. Безопасность выполнения сценариев
Внешний сценарий не должен позволять выполнять произвольный код на сервере.
Условия, expressions и действия должны исполняться через ограниченный DSL/набор разрешённых операций.
Нельзя использовать eval или произвольный JavaScript из присланного сценария.
Что не входит в эту задачу
- визуальный конструктор сценариев;
- UI оператора/менеджера;
- виджет чата на конкретном сайте;
- CRM-интеграции как отдельный большой слой;
- аналитическая BI-панель;
- построение workflow на n8n/Make и подобных продуктах.
Эти части могут использовать диалоговый runtime позже, но не должны определять его архитектуру.
Минимальный проверочный сценарий
Для приёмки реализовать хотя бы один сценарий, который подтверждает динамическую работу движка:
- несколько вопросов;
- минимум одно условное ветвление;
- извлечение структурированного значения через LLM;
- возврат к уточняющему вопросу при недостаточных данных;
- завершение с машинно-читаемым результатом.
Одновременно запустить несколько диалогов по одному сценарию с разными ответами и подтвердить, что состояния и результаты не смешиваются.
Критерии готовности
- Диалоговый runtime написан нами и не зависит от внешнего workflow-движка.
- Новый сценарий можно передать через внешний контракт без изменения кода runtime.
- Поддерживаются нелинейные ветвления и динамические переходы.
- Каждый диалог имеет независимое персистентное состояние.
- Один пользователь может иметь несколько диалогов, а сервис — одновременно обслуживать множество пользователей/сайтов.
- Есть защита от race conditions и повторной доставки сообщений.
- Версия сценария фиксируется на время жизни диалога.
- Финальный результат доступен в структурированном виде.
- По trace можно понять, как и почему был выполнен каждый шаг.
- Проверочный сценарий проходит end-to-end и подтверждает изоляцию параллельных диалогов.
Ворклоги
Фундамент runtime: потоковое выполнение и отмена
Для будущего собственного диалогового runtime уже реализован низкоуровневый механизм исполнения:
- общий
ExecutionContextс идентификатором запуска, abort signal и доставкой событий; - потоковый NDJSON-контракт
POST /api/chat; - корректные состояния started/delta/done/error/cancelled;
- явная отмена выполнения и отмена по disconnect/timeout/shutdown;
- независимость параллельных запусков;
- ограничения входа и размера ответа.
HTTP-слой повторно проверен 8/8 тестами.
Это не означает готовность сценарного движка: декларативная модель сценария, узлы/ветвления, версия сценария, персистентное состояние диалога, идемпотентность сообщений и атомарные переходы ещё не реализованы.
Минимальная событийная модель агента и LLM
Реализован базовый слой реакции агента, пригодный как строительный блок будущего runtime:
- класс
Agentи endpointPOST /api/agent; - базовая цепочка
message.received → Agent.react → message.publish; - связь действия с исходным событием через
stimulusIdи собственныйactionId; - JSON/NDJSON и общий механизм отмены;
Agent.reactтеперь вызывает реальныйrunChat/LLM вместо мока;- повторно прошли 2/2 теста агента на фрагменты/идентичность action, отмену и ошибку провайдера.
Текущая реализация обрабатывает один тип входящего события и канал chat; модель получает только текущее сообщение. Пока нет памяти разговора, накопления нескольких событий, дедупликации, общей системы принятия решений и сценарной машины переходов.