Руководство по созданию сценариев
📖 Полное руководство по созданию и настройке сценариев для Telegram-ботов с поддержкой плейсхолдеров, переходов и динамической логики.
📋 Структура сценария
Организация сценариев
Организация файлов — сценарии можно организовывать в папки и подпапки для удобства. Все YAML-файлы из папки scenarios/ (включая подпапки) автоматически загружаются.
Важные принципы:
- Уникальность названий: Название сценария должно быть уникальным в рамках всего тенанта
- Гибкая структура файлов: Можно создавать файлы с любыми названиями и организовывать их в подпапки — все файлы будут загружены
- Рекурсивная загрузка: Все YAML-файлы из
scenarios/и всех подпапок автоматически парсятся - Глобальные переходы: Сценарии могут переходить к любым другим сценариям тенанта через
jump_to_scenario
Пример структуры:
tenant_101/
scenarios/
commands.yaml # Сценарии: start, menu, help
settings.yaml # Сценарии: settings, profile
admin/ # Подпапка для организации
panel.yaml # Сценарии: admin_panel, logs
moderation.yaml # Сценарии: ban, kick
users/ # Еще одна подпапка
profile.yaml # Сценарии: user_profile, edit_profileВ этом примере:
- Все файлы из
scenarios/и подпапок загружаются автоматически - Сценарии могут переходить друг к другу независимо от расположения в папках
- Подпапки используются только для логической организации файлов
Структура YAML файла:
# Название сценария
scenario_name:
description: "Описание сценария (опционально)"
# Условия запуска сценария (опционально для scheduled сценариев)
trigger:
- event_type: "message"
event_text: "/start"
- event_type: "callback"
callback_data: "start"
# Расписание запуска (опционально, cron выражение)
schedule: "0 9 * * *" # Каждый день в 9:00
# Действия сценария
step:
- action: "send_message"
params:
text: "Привет!"
inline:
- [{"Кнопка": "callback_data"}]Параметры сценария:
description— описание сценария (опционально)trigger— условия запуска сценария (опционально для scheduled сценариев)schedule— cron выражение для запуска по расписанию (опционально)step— последовательность действий
Практический пример:
user_registration:
description: "Процесс регистрации пользователя с проверками и уведомлениями"
# ТРИГГЕРЫ: Каждый работает независимо - любой из них запустит сценарий
trigger:
- event_type: "message" # Команда /register
event_text: "/register"
- event_type: "callback" # Кнопка "Начать регистрацию"
callback_data: "start_registration"
# ШАГИ: Выполняются последовательно один за другим
step:
# Шаг 1: Отправляем приветствие
- action: "send_message"
params:
text: |
👋 Добро пожаловать, {username|fallback:Гость}!
Давайте зарегистрируем вас в системе.
inline:
- [{"✅ Продолжить": "continue_registration"}]
# Шаг 2: Проверяем права пользователя с переходами
- action: "validate"
params:
condition: "$user_role == 'admin'"
transition:
- action_result: "success"
transition_action: "jump_to_scenario"
transition_value: "admin_welcome" # Переход к админскому приветствию
- action_result: "error"
transition_action: "continue" # Обычный поток для пользователей
# Шаг 3: Запрашиваем имя
- action: "send_message"
params:
text: |
📝 Шаг 2 из 3
Пожалуйста, введите ваше имя:
inline:
- [{"❌ Отмена": "cancel_registration"}]
# Шаг 4: Отправляем подтверждение с вложениями
- action: "send_message"
params:
text: |
✅ Регистрация начата!
Мы отправили вам инструкции на email.
📎 Также прикрепляем полезные материалы:
attachment:
- file_id: "AgACAgIAAxkBAAIUJWjyLorr_d8Jwof3V_gVjVNdGHyXAAJ--zEb4vGQS-a9UiGzpkTNAQADAgADeQADNgW"
type: "photo"
- file_id: "AgACAgIAAxkBAAIUQWjyOH65wQYR4onkrrqM4J65yUD7AAM8-zEb4vGQS1mwvckFcZHlAQADAgADdwADNgQ"
type: "photo"
inline:
- [{"🏠 Главное меню": "main_menu"}]
# Шаг 5: Удаляем предыдущее сообщение
- action: "delete_message"
params:
delete_message_id: "{last_message_id}" # Используем ID предыдущего сообщения
admin_welcome:
description: "Приветствие для админов (транзишн-сценарий)"
# НЕТ ТРИГГЕРОВ - вызывается только программно
step:
- action: "send_message"
params:
text: |
🔧 Добро пожаловать, администратор!
У вас есть дополнительные права.
inline:
- [{"⚙️ Панель управления": "admin_panel"}]Ключевые моменты:
- Триггеры — логика ИЛИ: если сработал хотя бы один, сценарий запускается
- Шаги — выполняются строго по порядку записи
- Переходы — управляют потоком выполнения через
transition:continue— продолжить выполнение следующих действийbreak— прервать выполнение только текущего сценарияabort— прервать всю цепочку выполнения текущего сценария (включая вложенные)stop— прервать всю обработку события (все сценарии)move_steps— переместиться на указанное количество шагов (положительное = вперед, отрицательное = назад)jump_to_step— перейти к конкретному шагу по индексу (шаги нумеруются с 0)jump_to_scenario— перейти к другому сценарию (к любому сценарию тенанта)execute_scenario— выполнить другой сценарий и вернуться к текущему
- Транзишн-сценарии — сценарии без триггеров, вызываются программно из других сценариев
- Scheduled сценарии — сценарии с полем
schedule(cron выражение), запускаются автоматически по расписанию - Гибридные сценарии — могут иметь и
trigger, иscheduleодновременно (работают по событиям И по расписанию) - Данные события — доступны в параметрах действий (user_id, chat_id, timestamp и др.)
- Вложения — можно пересылать файлы через
attachment[0].file_idиattachment[0].type
🎯 Триггеры (trigger)
Назначение: Условия, при которых запускается сценарий.
Механика работы триггеров:
Простая форма (атрибуты):
trigger:
- event_type: "message"
event_text: "/start"Преобразуется в условие:
$event_type == "message" and $event_text == "/start"Сложная форма (с condition):
trigger:
- event_type: "message"
condition: |
$event_text == "test"
and $user_id > 100
or $username in ('@admin', '@moderator')Преобразуется в условие:
$event_type == "message" and ($event_text == "test" and $user_id > 100 or $username in ('@admin', '@moderator'))Логика триггеров:
- Между триггерами — логика ИЛИ (OR): если сработал хотя бы один, сценарий запускается
- Внутри триггера — логика И (AND): все условия должны выполняться
- В condition — полная свобода:
and,or,not, скобки, операторы
⚠️ Важно: Плейсхолдеры в условиях condition
Проблема: При использовании плейсхолдеров в condition триггеров для сравнения строк нужно оборачивать плейсхолдер в кавычки. Иначе плейсхолдер будет "разворачиваться" как атрибут для поиска данных в condition_parser, а не как строковое значение.
Решение: Всегда оборачивайте плейсхолдеры в кавычки при сравнении строк в условиях.
Примеры:
# ❌ ОШИБКА - плейсхолдер развернется в random_user без кавычек
# condition_parser попытается найти переменную random_user в данных
trigger:
- event_type: "message"
condition: "{_cache.detected_intent} == 'random_user'"
# После обработки плейсхолдера: random_user == 'random_user'
# condition_parser ищет переменную random_user в данных, а не сравнивает строки!
# ✅ ПРАВИЛЬНО - плейсхолдер обернут в кавычки
trigger:
- event_type: "message"
condition: "'{_cache.detected_intent}' == 'random_user'"
# После обработки плейсхолдера: 'random_user' == 'random_user'
# condition_parser корректно сравнивает строки
# ✅ ПРАВИЛЬНО - сравнение чисел (кавычки не нужны)
trigger:
- event_type: "message"
condition: "{user_id} > 100"
# После обработки плейсхолдера: 12345 > 100
# Числовое сравнение работает корректно
# ✅ ПРАВИЛЬНО - сравнение с модификаторами (кавычки не нужны для boolean)
trigger:
- event_type: "message"
condition: "{response_value.feedback|exists} == True"
# Модификатор exists возвращает boolean, сравнение работает корректноПравило:
- Для сравнения строк — всегда оборачивайте плейсхолдер в кавычки:
'{_cache.field}' == 'value' - Для сравнения чисел — кавычки не нужны:
{user_id} > 100 - Для boolean значений — кавычки не нужны:
{field|exists} == True
Доступные поля для условий:
⚠️ Важно: Все поля в условиях должны иметь маркер $name (например, $event_type, $user_id, $event_text).
Данные события:
$event_text— текст сообщения$event_type— тип события (message, callback)$user_id— ID пользователя$username— имя пользователя$chat_id— ID чата$callback_data— данные callback кнопки$event_date— время события$tenant_id— ID тенанта$message_id— ID сообщения$chat_type— тип чата$is_group— является ли чат группой- И другие поля события (см. Гайд по событиям)
Данные предыдущих действий:
$last_message_id— ID последнего отправленного сообщения$response_data— любые данные, возвращенные предыдущими действиями- Другие поля — в зависимости от действий в сценарии
Вложенные поля:
$message.text— доступ к вложенным полям через точку$user.profile.name— многоуровневая вложенность
Операторы в условиях:
==— равно!=— не равно>,<,>=,<=— сравнение чиселin,not in— вхождение в список~— содержит подстроку!~— не содержит подстрокуregex— регулярное выражениеis_null— поле пустое (None или пустая строка)not is_null— поле не пустое (существует и имеет значение)
⏰ Scheduled сценарии (запуск по расписанию)
Назначение: Автоматический запуск сценариев по расписанию через cron выражения.
Структура scheduled сценария:
daily_report:
description: "Ежедневный отчет в 9:00"
# Cron выражение для запуска каждый день в 9:00
schedule: "0 9 * * *"
# Триггеры опциональны - сценарий будет работать по расписанию
# Можно добавить триггеры для гибридного режима (по событиям И по расписанию)
step:
- action: "send_message"
params:
target_chat_id: 123456789
text: |
📊 Ежедневный отчет
Время запуска: {scheduled_at|format:datetime}
Сценарий: {scheduled_scenario_name}Cron выражения:
Формат: минута час день месяц день_недели
| Пример | Описание |
|---|---|
* * * * * | Каждую минуту |
*/5 * * * * | Каждые 5 минут |
0 * * * * | Каждый час в начале часа |
0 9 * * * | Каждый день в 9:00 |
0 9 * * 1-5 | Каждый рабочий день в 9:00 |
0 0 1 * * | Первое число каждого месяца в 00:00 |
ℹ️ Важно: Cron выражения интерпретируются в локальном времени платформы.
Доступные поля в scheduled сценариях:
В scheduled сценариях доступны специальные поля события. Подробное описание всех полей см. в разделе Поля scheduled сценариев в гайде по событиям.
Пример использования:
step:
- action: "send_message"
params:
text: |
⏰ Scheduled сценарий запущен
Время: {scheduled_at|format:datetime}
Название: {scheduled_scenario_name}
ID: {scheduled_scenario_id}Гибридный режим (trigger + schedule):
Сценарий может работать и по событиям, и по расписанию одновременно:
hybrid_scenario:
description: "Работает по событиям И по расписанию"
# Запуск по событиям
trigger:
- event_type: "message"
event_text: "/report"
# Запуск по расписанию (каждый день в 9:00)
schedule: "0 9 * * *"
step:
- action: "send_message"
params:
text: |
📊 Отчет
{scheduled_at|fallback:Запущено вручную|format:datetime}⚠️ Важные ограничения и рекомендации:
1. Синхронизация сценариев:
- При синхронизации сценариев (удаление и пересоздание) теряется информация о последнем запуске (
last_scheduled_run) - После синхронизации scheduled сценарии пересчитывают следующее время запуска от текущего момента
- Рекомендация: Синхронизируйте сценарии в периоды низкой активности scheduled задач
2. Пропуск запуска:
- Если синхронизация происходит в момент, когда должен запускаться scheduled сценарий, запуск может быть пропущен
- Рекомендация: Избегайте синхронизации сценариев за несколько секунд до запланированного времени запуска
3. Поведение при ошибках:
- Scheduled сценарии обновляют время следующего запуска даже при ошибке выполнения
- Это гарантирует предсказуемое поведение и предотвращает повторные запуски проблемных сценариев
4. Контекст выполнения:
- Scheduled сценарии выполняются с системным контекстом (автоматически получают
bot_id,tenant_id) - Общие поля события недоступны: В scheduled сценариях большинство общих полей (
user_id,chat_id,event_text,message_idи др.) недоступны, так как событие не связано с пользователем или чатом - Передача параметров: Необходимо передавать большинство параметров вручную в действиях или извлекать данные из storage тенанта или пользователя (из базы данных)
- Используйте
target_chat_idв параметрах действий для указания конкретного чата
⚡ Действия (step)
Назначение: Последовательность действий, выполняемых в сценарии.
Важно:
- Порядок выполнения шагов определяется их позицией в массиве
step(сверху вниз). Шаги выполняются последовательно в том порядке, в котором они указаны в YAML. Нумерация шагов начинается с 0. - Последовательное выполнение: По умолчанию сценарий идет последовательно по шагам независимо от результата действия. Если у шага нет
transitionили нет подходящего перехода для результата действия, выполнение автоматически переходит к следующему шагу. - Изменение потока: Переходы (
transition) позволяют изменить последовательность выполнения (перейти к другому шагу, сценарию, прервать выполнение и т.д.).
Отправка сообщения:
Inline кнопки:
step:
- action: "send_message"
params:
text: |
👋 Привет!
📋 Выберите действие:
inline:
- [{"📋 Меню": "main_menu"}, {"📚 Справка": "help"}]
- [{"🌐 Сайт": "https://example.com"}, {"📞 Поддержка": "tg://resolve?domain=support"}]
- [{"🔙 Назад": "start"}]Reply клавиатура:
step:
- action: "send_message"
params:
text: |
👋 Привет!
📋 Выберите действие:
reply:
- ["📋 Меню", "📚 Справка"]
- ["🔙 Назад"]Убрать клавиатуру:
step:
- action: "send_message"
params:
text: "Клавиатура убрана"
reply: [] # Пустой массив убирает клавиатуруПараметры сообщения:
text— текст сообщения (поддерживает многострочность с|)inline— массив кнопок:[{"Текст кнопки": "callback_data"}]— одна кнопка в строке[{"Кнопка 1": "data1"}, {"Кнопка 2": "data2"}]— несколько кнопок в строке[{"Ссылка": "https://example.com"}]— кнопка со ссылкой
reply— reply клавиатура:[["Кнопка 1", "Кнопка 2"]]— одна строка кнопок[["Кнопка 1"], ["Кнопка 2"]]— каждая кнопка в отдельной строке[]— убрать клавиатуру
attachment— массив вложений (фото, документы, видео, аудио):[{"file_id": "AgACAgI...", "type": "photo"}]— пересылка файла по file_id[{"file_id": "{event_attachment[0].file_id}", "type": "{event_attachment[0].type}"}]— динамическая пересылка из события[{...}, {...}]— несколько файлов за раз
Прикрепление файлов:
📎 Прикрепление файлов к сообщениям:
Можно отправлять фото, документы, видео, аудио. Файлы можно брать из событий или использовать сохранённые file_id.
Пример: Пересылка файла из события:
step:
- action: "send_message"
params:
text: "Пересылаю ваш файл обратно"
attachment:
- file_id: "{event_attachment[0].file_id}"
type: "{event_attachment[0].type}"Пример: Отправка файла по известному file_id:
step:
- action: "send_message"
params:
text: "Инструкция"
attachment:
- file_id: "AgACAgIAAxkBAAIUJWjyLorr_d8Jwof3V_gVjVNdGHyXAAJ--zEb4vGQS-a9UiGzpkTNAQADAgADeQADNgW"
type: "photo"Пример: Несколько файлов:
step:
- action: "send_message"
params:
text: "Полезные материалы"
attachment:
- file_id: "{event_attachment[0].file_id}"
type: "{event_attachment[0].type}"
- file_id: "BQACAgIAAxkBAAI..."
type: "document"📋 Как получить file_id и type: см. пример сценария ниже 👇
Конфигурация тенанта в действиях:
Назначение: Автоматическое использование атрибутов конфигурации тенанта в действиях.
Некоторые действия автоматически используют параметры из конфигурации тенанта (_config). Такие параметры не нужно передавать явно в params — они автоматически берутся из _config.
Пример использования:
step:
- action: "completion"
params:
prompt: "Привет, как дела?"
# openrouter_token автоматически берется из _config.openrouter_token
# Не нужно указывать openrouter_token явно!Доступ через плейсхолдеры:
Конфигурация тенанта также доступна напрямую через плейсхолдеры:
step:
- action: "send_message"
params:
text: |
Конфигурация тенанта:
Токен: {_config.openrouter_token|fallback:Не установлен}Важно:
_configдоступен во всех сценариях тенанта автоматически- Если атрибут не установлен в конфигурации тенанта, значение будет
None - Используйте
fallbackв плейсхолдерах для обработки отсутствующих значений - Если параметр передан явно в
params, явное значение имеет приоритет
📖 Подробнее о конфигурации тенанта: См. раздел config.yaml в TENANT_CONFIG_GUIDE.md
Кэширование данных в сценариях:
Назначение: Автоматическое сохранение результатов действий в _cache для использования в последующих шагах сценария.
⚠️ Важно: Область видимости кэша:
- Кэш
_cacheсохраняется только в рамках одной цепочки выполнения сценариев (одна цепочка обработки одного события) - Данные доступны между сценариями в рамках одной цепочки выполнения (например, при переходе через
jump_to_scenarioилиexecute_scenario) - Кэш НЕ сохраняется между разными цепочками выполнения (даже если они триггерятся одним событием — каждый сценарий имеет свой изолированный кэш)
- Кэш НЕ сохраняется между разными событиями (разными сообщениями, разными callback'ами)
- Для долгосрочного хранения данных используйте
set_storage/set_user_storageвместоset_cache - Подробнее см. раздел Сохранение контекста в рамках обработки события
Общая механика работы:
- Каждое действие может вернуть
response_dataс любыми полями - Данные автоматически сохраняются в
_cacheи доступны в последующих действиях через плейсхолдеры - Система кэширования поддерживает два системных поля для управления структурой данных:
_namespace— для контроля перезаписи и организации данных в вложенные структуры_response_key— для подмены ключа replaceable поля перед сохранением в_cache
- Все механизмы работают на уровне движка сценариев и не требуют изменений в сервисах
Плоское кэширование (по умолчанию):
По умолчанию используется плоское кэширование — данные мержатся напрямую в _cache без вложенности. Доступ к данным осуществляется через {_cache.field}:
step:
- action: "generate_int"
params:
min: 1
max: 100
# response_data: {"random_value": 42, "random_seed": 123}
# Попадает в: _cache.random_value = 42, _cache.random_seed = 123
- action: "send_message"
params:
text: "Число: {_cache.random_value}" # ✅ ПРАВИЛЬНОПример с несколькими действиями:
step:
- action: "generate_int"
params:
min: 1
max: 100
# _cache.random_value = 42, _cache.random_seed = 123
- action: "generate_array"
params:
min: 1
max: 10
count: 5
# _cache.random_list = [3, 7, 2, 9, 1], _cache.random_seed = 456
- action: "send_message"
params:
text: |
Число: {_cache.random_value}
Массив: {_cache.random_list|comma}⚠️ Проблемы плоского кэширования:
1. Конфликты имен между разными действиями:
step:
- action: "get_storage_value"
params:
group_key: "settings"
key: "api_key"
# _cache.response_value = "key1"
- action: "get_user_storage_value"
params:
key: "api_secret"
# _cache.response_value = "secret1" # ⚠️ Перезаписано!
- action: "send_message"
params:
text: "Key: {_cache.response_value}" # Покажет только "secret1"2. Перезапись при повторных вызовах одного действия:
step:
- action: "get_storage_value"
params:
group_key: "settings"
key: "api_key"
# _cache.response_value = "key1"
- action: "get_storage_value" # То же действие!
params:
group_key: "settings"
key: "api_secret"
# _cache.response_value = "secret1" # ⚠️ Первый результат перезаписан!Системное поле _namespace (контроль перезаписи):
Назначение: Сохранение данных в вложенную структуру _cache[_namespace] для контроля перезаписи и избежания конфликтов имен.
Механика работы:
- Если указан
_namespace, данные сохраняются в_cache[_namespace]вместо плоского_cache - Доступ к данным:
{_cache[_namespace].field} - Позволяет сохранять результаты повторных вызовов одного действия
- Позволяет избежать конфликтов имен между разными действиями
Пример использования:
step:
- action: "generate_int"
params:
min: 1
max: 100
_namespace: "my_random"
# Попадает в: _cache.my_random = {random_value: 42, random_seed: 123}
- action: "send_message"
params:
text: "Число: {_cache.my_random.random_value}" # ✅ ПРАВИЛЬНОРешение конфликтов с _namespace:
step:
- action: "get_storage_value"
params:
group_key: "settings"
key: "api_key"
_namespace: "first"
# _cache.first.response_value = "key1"
- action: "get_storage_value"
params:
group_key: "settings"
key: "api_secret"
_namespace: "second"
# _cache.second.response_value = "secret1"
# ✅ Оба результата сохранены!
- action: "send_message"
params:
text: |
Первый: {_cache.first.response_value}
Второй: {_cache.second.response_value}Системное поле _response_key (переименование основного поля):
Назначение: Позволяет задать кастомное имя для основного поля результата действия для удобного доступа к данным в _cache.
Механика работы:
- Некоторые действия возвращают основное значение под определенным ключом (например,
storage_values,user_storage_values,formatted_text) - По умолчанию данные сохраняются в
_cacheс оригинальным ключом из результата действия - Если указан
_response_key, движок автоматически переименовывает основное поле перед сохранением в_cache - Работает только для действий, которые поддерживают переименование основного поля
Пример использования:
step:
# Получаем storage значение (по умолчанию будет в _cache.storage_values)
- action: "get_storage"
params:
tenant_id: "{tenant_id}"
group_key: "settings"
key: "api_key"
_response_key: "api_key" # Переименовываем storage_values в api_key
# response_data: {"storage_values": "secret_key_123"}
# Попадает в: _cache.api_key = "secret_key_123" (вместо _cache.storage_values)
# Используем переименованное значение
- action: "send_message"
params:
text: "API ключ: {_cache.api_key}" # ✅ ПРАВИЛЬНО - используем кастомный ключПример с несколькими вызовами одного действия:
step:
# Первый вызов - сохраняем в api_key
- action: "get_storage"
params:
tenant_id: "{tenant_id}"
group_key: "settings"
key: "api_key"
_response_key: "api_key"
# _cache.api_key = "secret_key_123"
# Второй вызов - сохраняем в api_secret
- action: "get_storage"
params:
tenant_id: "{tenant_id}"
group_key: "settings"
key: "api_secret"
_response_key: "api_secret"
# _cache.api_secret = "secret_secret_456"
# ✅ Оба значения сохранены под удобными ключами!
- action: "send_message"
params:
text: |
API Key: {_cache.api_key}
API Secret: {_cache.api_secret}Пример с форматированием текста:
step:
# Форматируем данные и сохраняем под кастомным ключом
- action: "format_data_to_text"
params:
format_type: "list"
title: "Доступные намерения:"
item_template: '- "$id" - $description'
input_data: "{storage_values.ai_router.intents}"
_response_key: "formatted_intents"
# response_data: {"formatted_text": "• intent1\n• intent2"}
# Попадает в: _cache.formatted_intents = "• intent1\n• intent2"
# (вместо _cache.format_data_to_text.formatted_text)
- action: "send_message"
params:
text: "{_cache.formatted_intents}" # ✅ Используем удобный ключКомбинация _namespace и _response_key:
step:
# Используем и _namespace, и _response_key одновременно
- action: "get_storage"
params:
tenant_id: "{tenant_id}"
group_key: "settings"
key: "api_key"
_namespace: "first"
_response_key: "api_key"
# _cache.first.api_key = "secret_key_123"
# (вместо _cache.first.storage_values)
- action: "get_storage"
params:
tenant_id: "{tenant_id}"
group_key: "settings"
key: "api_secret"
_namespace: "second"
_response_key: "api_secret"
# _cache.second.api_secret = "secret_secret_456"
- action: "send_message"
params:
text: |
Первый: {_cache.first.api_key}
Второй: {_cache.second.api_secret}Пример с форматированием текста:
step:
# Форматируем данные и сохраняем под кастомным ключом
- action: "format_data_to_text"
params:
format_type: "list"
title: "Доступные намерения:"
item_template: '- "$id" - $description'
input_data: "{storage_values.ai_router.intents}"
_response_key: "formatted_intents"
# response_data: {"formatted_text": "• intent1\n• intent2"}
# Попадает в: _cache.formatted_intents = "• intent1\n• intent2"
# (вместо _cache.format_data_to_text.formatted_text)
- action: "send_message"
params:
text: "{_cache.formatted_intents}" # ✅ Используем удобный ключРекомендации по использованию:
Общие рекомендации:
- ✅ Плоское кэширование по умолчанию — меньше вложенности, проще доступ
- ✅ Используйте
_namespaceдля контроля перезаписи при повторных вызовах одного действия - ✅ Используйте
_namespaceдля избежания конфликтов имен между разными действиями - ✅ Используйте
_response_keyдля удобства доступа к основным значениям действий - ✅ Комбинируйте
_namespaceи_response_keyдля полного контроля над структурой данных в_cache - ✅ Используйте осмысленные имена для
_namespaceи_response_key - ✅ Используйте
set_cacheдля выноса данных из вложенности во флэт при необходимости
⚠️ Важно:
_response_keyработает только для действий, которые поддерживают подмену ключа основного значения- Если действие не поддерживает подмену ключа,
_response_keyигнорируется (без ошибки) - Подмена происходит автоматически перед сохранением в
_cache
Сохранение контекста в рамках обработки события:
⚠️ Важно: Кэш _cache сохраняется только в рамках одной цепочки выполнения сценариев. Это означает, что данные доступны между сценариями в рамках одной цепочки (когда один сценарий переходит к другому через jump_to_scenario или execute_scenario), но не сохраняются между разными цепочками выполнения (даже если они триггерятся одним событием) и между разными событиями.
Что такое "одна цепочка выполнения":
- Одна цепочка = последовательность сценариев, связанных переходами (
jump_to_scenario,execute_scenario) - Если сценарий A переходит к сценарию B через
jump_to_scenario— это одна цепочка, кэш сохраняется - Если сценарий A вызывает сценарий B через
execute_scenario— это одна цепочка, кэш сохраняется - Если событие триггерит несколько независимых сценариев — это разные цепочки, у каждой свой изолированный кэш
Что такое "одно событие":
- Одно событие = одно сообщение от пользователя, один callback от кнопки, один scheduled запуск и т.д.
- Если пользователь отправил сообщение
/start— это одно событие - Если пользователь нажал кнопку с
callback_data: "menu"— это другое, отдельное событие - Каждое событие может триггерить несколько сценариев, но каждый сценарий выполняется независимо со своим кэшем
Что сохраняется в рамках одного события:
- Все данные события (event data:
user_id,chat_id,event_textи т.д.) - Все результаты действий (данные из
response_dataавтоматически попадают в плоский_cacheили_cache[_namespace]если указан_namespace, с учетом подмены ключа через_response_key) - Временные данные из
_cache(сохраненные через действиеset_cacheили другие действия) - Любые другие данные, накопленные в контексте обработки события
Когда кэш сохраняется (в рамках одной цепочки выполнения):
✅ При переходе через jump_to_scenario:
# Сценарий 1: main_scenario
step:
- action: "set_cache"
params:
cache:
selected_user: "@username"
- action: "send_message"
transition:
- action_result: "success"
transition_action: "jump_to_scenario"
transition_value: "next_scenario"
# Сценарий 2: next_scenario
# ✅ Все данные из main_scenario доступны (одна цепочка выполнения):
step:
- action: "send_message"
params:
text: "Пользователь: {_cache.set_cache.selected_user}" # ✅ ДоступенКогда кэш НЕ сохраняется:
❌ Между разными сценариями, которые триггерятся одним событием:
# Событие: message "/start"
# Сценарий 1: welcome_scenario (триггерится "/start")
step:
- action: "set_cache"
params:
cache:
welcome_sent: true
# Сценарий 2: analytics_scenario (тоже триггерится "/start", но выполняется независимо!)
step:
- action: "send_message"
params:
text: "Welcome sent: {_cache.set_cache.welcome_sent}" # ❌ НЕ доступно! Разные цепочки выполнения❌ Между разными событиями:
# Событие 1: Пользователь отправил "/start"
step:
- action: "set_cache"
params:
cache:
step: 1
# Событие 2: Пользователь отправил "/menu" (другое событие!)
step:
- action: "send_message"
params:
text: "Step: {_cache.set_cache.step}" # ❌ НЕ доступно! Разные события🔄 Переходы (transition)
Назначение: Управление потоком выполнения сценария.
step:
- action: "validate"
params:
condition: "$user_role == 'admin'"
transition:
- action_result: "success"
transition_action: "jump_to_scenario"
transition_value: "admin_panel" # Переход к сценарию админки
- action_result: "error"
transition_action: "continue" # Обычный поток для пользователей
- action_result: "timeout"
transition_action: "break" # Прервать только текущий сценарий при таймауте
- action_result: "error"
transition_action: "abort" # Прервать всю цепочку при критической ошибке
- action_result: "system_failure"
transition_action: "stop" # Прервать всю обработку события при системной ошибкеТипы переходов:
continue— продолжить выполнение следующих действийbreak— прервать выполнение только текущего сценарияabort— прервать всю цепочку выполнения текущего сценария (включая вложенные)stop— прервать всю обработку события (все сценарии)move_steps— переместиться на указанное количество шагов (положительное число = вперед, отрицательное = назад). Например,move_step: 1перейдет к следующему шагу,move_step: 2пропустит 1 шаг и перейдет к следующемуjump_to_step— перейти к конкретному шагу по индексу (шаги нумеруются с 0, например,jump_to_step: 5перейдет к шагу с индексом 5)jump_to_scenario— перейти к другому сценарию (аналогично break + прыжок, текущий сценарий далее не выполняется)execute_scenario— выполнить другой сценарий и вернуться к текущему (продолжить выполнение текущего сценария после завершения вызванного)any— универсальный переход, который обрабатывается первым независимо от результата действия
Особенности переходов:
transition_value— обязателен дляmove_steps(количество шагов),jump_to_step(индекс шага, начиная с 0),jump_to_scenarioиexecute_scenario(название сценария или массив названий)- Поиск сценария — система ищет сценарий в том же tenant'е
- Приоритет переходов — переход
anyобрабатывается первым, независимо от результата действия - Единственный переход — всегда выполняется только один переход из всех возможных
- Блокировка других переходов — если есть переход
any, остальные переходы (success,error,timeoutи т.д.) не будут обработаны - Различие между переходами:
break— прерывает только текущий сценарий, другие сценарии (если есть) продолжают обработку событияabort— прерывает всю цепочку выполнения текущего сценария (включая все вложенные сценарии), но позволяет другим сценариям (из других триггеров) продолжить работуstop— полностью прекращает обработку события во всех сценариях, возвращает управление системеjump_to_scenario— прерывает выполнение текущего сценария и переходит к другому (аналогично break + прыжок)execute_scenario— выполняет другой сценарий, затем возвращается и продолжает выполнение текущего сценария (кэш из вызванного сценария доступен в текущем)
🔧 Плейсхолдеры (Placeholders)
В параметрах действий (params) все значения обрабатываются плейсхолдерами: подставляются данные события, результаты предыдущих действий, доступны модификаторы и вложенный доступ.
📖 Полный синтаксис, модификаторы и примеры: Справочник по плейсхолдерам
Примеры переходов:
Пример 1: Переход к сценарию (прерывание текущего)
step:
- action: "check_user_role"
params:
required_role: "admin"
transition:
- action_result: "success"
transition_action: "jump_to_scenario"
transition_value: "admin_panel" # Переход к админке, текущий сценарий прерывается
- action_result: "error"
transition_action: "continue" # Продолжить выполнение текущего сценария
# Этот шаг НЕ выполнится, если сработал jump_to_scenario
- action: "send_message"
params:
text: "Обычное меню для пользователей"Пример 1.1: Выполнение сценария с возвратом
step:
- action: "validate"
params:
condition: "{user_id} in {_cache.system.admins|fallback:[]}"
transition:
- action_result: "success"
transition_action: "execute_scenario"
transition_value: "add_admin_buttons" # Выполнить сценарий и вернуться
# Этот шаг ВЫПОЛНИТСЯ после завершения add_admin_buttons
- action: "send_message"
params:
text: |
Привет! {_cache.inline}
inline:
- "{_cache.inline|fallback:[]}" # Кнопки из add_admin_buttons доступны
- [{"ℹ️ Справка": "help"}]Пример 2: Перемещение по шагам
step:
- action: "check_permissions"
params:
required_role: "admin"
transition:
- action_result: "success"
transition_action: "move_steps"
transition_value: 2 # Переместиться на 2 шага вперед (пропустить 1 шаг, перейти к следующему)
- action_result: "error"
transition_action: "continue" # Выполнить все шагиПример 2.1: Переход к конкретному шагу
step:
- action: "validate"
params:
condition: "$user_role == 'admin'"
transition:
- action_result: "success"
transition_action: "jump_to_step"
transition_value: 5 # Перейти к шагу с индексом 5 (шаги нумеруются с 0)
- action_result: "failed"
transition_action: "continue" # Продолжить выполнениеПример 3: Универсальный переход "any"
step:
- action: "send_message"
params:
text: "Сообщение отправлено"
transition:
- action_result: "any" # Выполняется независимо от результата
transition_action: "abort" # Всегда прерываем выполнение
- action_result: "success"
transition_action: "continue" # ❌ Этот переход НЕ выполнится из-за "any"
- action_result: "error"
transition_action: "continue" # ❌ Этот переход НЕ выполнится из-за "any"⚠️ Важно: Переход any имеет высший приоритет и блокирует выполнение всех остальных переходов. Система всегда выполняет только один переход из всех возможных.
⚡ Асинхронные действия (Async Actions)
Назначение: Запуск долгих действий в фоне с возможностью продолжения выполнения сценария и проверки готовности результата.
Механика работы:
Асинхронные действия позволяют запускать долгие операции (например, запросы к LLM, обработку больших данных) в фоне, не блокируя выполнение сценария. Сценарий может продолжать выполнять другие действия, проверять готовность результата и ожидать завершения когда это необходимо.
Структура async действия:
step:
- step_id: 1
action_name: "ai_request"
async: true # Флаг асинхронного выполнения
action_id: "ai_req_1" # Уникальный ID для отслеживания (обязателен)
params:
prompt: "Привет!"Параметры:
async: true— флаг, что действие должно выполняться асинхронноaction_id— уникальный идентификатор действия (обязателен для async действий)action_name— имя действия для выполненияparams— параметры действия
Как работает механизм async действий:
Что происходит при запуске async действия:
Запуск действия в фоне:
- При запуске async действия оно начинает выполняться в фоне, не блокируя выполнение сценария
- Сценарий продолжает работать и может выполнять другие действия, пока async действие выполняется
- Система автоматически отслеживает состояние выполнения действия
Сохранение ссылки на действие в
_async_action:- Система сохраняет ссылку на выполняющееся действие в специальном поле
_async_action[action_id] _async_action— это системный словарь, который хранит все запущенные async действия- Ключ словаря — это
action_id(который вы указали), значение — ссылка на выполняющееся действие - Важно:
_async_actionхранится отдельно от_cache, чтобы быть доступным для проверки готовности между шагами
- Система сохраняет ссылку на выполняющееся действие в специальном поле
Автоматическое отслеживание состояния:
- Когда действие завершается (успешно или с ошибкой), система автоматически обновляет его состояние
- Обновление происходит автоматически в фоне, независимо от выполнения сценария
- Можно проверить готовность через плейсхолдер
{_async_action.action_id|ready}в любой момент
Почему используется синтаксис {_async_action.action_id|ready}:
_async_action— системное поле, которое автоматически создается при запуске async действийaction_id— это ID, который вы указали при запуске async действия (например,"ai_req_1")ready— модификатор, который проверяет, завершено ли действие (возвращаетTrueесли готово,Falseесли еще выполняется)- Это позволяет использовать проверку готовности в условиях валидации и создавать сложные сценарии с ожиданиями
Пример: После запуска нескольких async действий система автоматически создает структуру:
_async_action["ai_req_1"]— ссылка на первый AI запрос_async_action["data_proc_1"]— ссылка на обработку данных_async_action["ai_req_2"]— ссылка на второй AI запрос
Вы можете проверить готовность любого из них через плейсхолдер: {_async_action.ai_req_1|ready}
Проверка готовности:
Для проверки готовности асинхронного действия используются модификаторы ready и not_ready в плейсхолдерах. Это позволяет создавать сложные сценарии с ожиданиями через валидацию и переходы.
Модификаторы:
{_async_action.action_id|ready}— возвращаетTrueесли действие завершено,Falseесли еще выполняется{_async_action.action_id|not_ready}— возвращаетTrueесли действие еще выполняется,Falseесли готово
Как это работает:
- Плейсхолдер
{_async_action.action_id|ready}обращается к системному полю_async_actionи проверяет состояние действия с указаннымaction_id - Модификатор
readyпроверяет, завершено ли действие — возвращаетTrueесли действие готово,Falseесли еще выполняется - Результат можно использовать в условиях валидации для создания циклов ожидания и сложной логики
Пример использования в валидации:
step:
- action: "validate"
params:
condition: "{_async_action.ai_req_1|ready} == True" # Проверяем готовность
transition:
- action_result: "failed"
transition_action: "move_steps"
transition_value: -2 # Возвращаемся назад, если еще не готовоОжидание результата:
Для получения результата асинхронного действия используется действие wait_for_action:
step:
- step_id: 3
action_name: "wait_for_action"
params:
action_id: "ai_req_1"
timeout: 60 # Опциональный таймаут в секундахПараметры:
action_id— ID асинхронного действия для ожидания (обязателен)timeout— таймаут ожидания в секундах (опционально)
Результат:
wait_for_actionвозвращает результат основного действия AS IS (как будто оно выполнилось напрямую)- Если основное действие завершилось успешно → возвращается его результат с
response_data, который автоматически попадает в плоский_cache(или_cache[_namespace]если указан_namespace, с учетом подмены ключа через_response_key) - Если основное действие завершилось с ошибкой → возвращается его ошибка
- Если ошибка ожидания (таймаут, Future не найден) → возвращается ошибка
wait_for_action
Практический пример:
ai_processing:
description: "Обработка запроса к AI с показом загрузки"
trigger:
- event_type: "message"
event_text: "/ai"
step:
# Шаг 1: Запускаем AI запрос асинхронно
- action: "completion"
async: true
action_id: "ai_req_1"
params:
prompt: "{event_text|regex:/ai\\s+(.+)}"
# Шаг 2: Отправляем сообщение о загрузке
- action: "send_message"
params:
text: "⏳ Обрабатываю запрос..."
# Шаг 3: Задержка перед проверкой
- action: "sleep"
params:
seconds: 1.0 # Задержка 1 секунда между проверками
# Шаг 4: Проверяем готовность через валидацию
# Используем плейсхолдер {_async_action.ai_req_1|ready} для проверки состояния действия
- action: "validate"
params:
condition: "{_async_action.ai_req_1|ready} == True"
transition:
- action_result: "failed"
transition_action: "move_steps"
transition_value: -1 # Возвращаемся на шаг с задержкой (шаг 3), если еще не готово
# Шаг 5: Удаляем сообщение о загрузке (выполнится только когда действие готово)
- action: "delete_message"
params:
delete_message_id: "{last_message_id}"
# Шаг 6: Ожидаем результат AI (получаем данные в контекст)
- action: "wait_for_action"
params:
action_id: "ai_req_1"
timeout: 60 # Таймаут 60 секунд
# response_data попадает в плоский _cache = {response_completion: "Ответ от AI", prompt_tokens: 50, completion_tokens: 100}
# Шаг 7: Отправляем результат AI
- action: "send_message"
params:
text: "{_cache.response_completion|fallback:Ошибка обработки}"Что происходит в этом примере:
- Запускается async действие — система сохраняет ссылку на него в
_async_action["ai_req_1"] - Отправляется сообщение о загрузке
- Делается задержка 1 секунда (предотвращает 100% CPU)
- Проверяется готовность через валидацию — плейсхолдер
{_async_action.ai_req_1|ready}проверяет, завершено ли действие - Если не готово — возврат на шаг с задержкой (цикл: задержка → проверка → задержка → проверка...)
- Если готово — удаление сообщения о загрузке и получение результата
Этот пример показывает:
- Как использовать проверку готовности через валидацию для создания циклов ожидания
- Как правильно организовать цикл с задержкой для предотвращения 100% CPU
- Механизм работы плейсхолдера
{_async_action.action_id|ready}в условиях валидации
Важные особенности:
1. Результат попадает в контекст только через wait_for_action:
- Результат основного действия попадает в
_cacheсценария только после вызоваwait_for_action wait_for_actionполучает результат из завершенного действия и возвращает его как обычный результат действия- Результат автоматически сохраняется в плоский
_cache(или_cache[_namespace]если указан_namespace, с учетом подмены ключа через_response_key) - Без
wait_for_actionрезультат остается в системе и не доступен в сценарии через плейсхолдеры
2. Несколько async действий:
- Можно запустить несколько async действий одновременно с разными
action_id - Каждое действие отслеживается независимо через свой
action_idв_async_action - Можно проверять готовность каждого действия отдельно:
{_async_action.action_id_1|ready},{_async_action.action_id_2|ready} - Можно создавать сложные условия:
{_async_action.ai_1|ready} and {_async_action.data_1|ready}
3. Таймауты:
- Можно указать
timeoutвwait_for_actionдля ограничения времени ожидания - При превышении таймаута
wait_for_actionвернет ошибкуtimeout, но выполнение сценария продолжается - Основное действие продолжит выполняться в фоне даже после таймаута — ссылка на действие останется в
_async_actionи можно будет проверить готовность позже
Пример с несколькими async действиями:
parallel_processing:
description: "Параллельная обработка нескольких запросов"
trigger:
- event_type: "message"
event_text: "/process"
step:
# Запускаем несколько действий параллельно
- action: "completion"
async: true
action_id: "ai_1"
params:
prompt: "Вопрос 1"
# response_data: {"response_completion": "Ответ 1", "prompt_tokens": 50, "completion_tokens": 100}
- action: "process_data"
async: true
action_id: "data_proc_1"
params:
data: "{event_text}"
# response_data: {"processed_data": "Обработанные данные", "status": "success"}
# Ожидаем результат AI (сохраняется в _cache)
# ВАЖНО: Не используем цикл проверки готовности без задержки - это приведет к 100% CPU!
# Просто ждем результат напрямую через wait_for_action с таймаутом
- action: "wait_for_action"
params:
action_id: "ai_1"
timeout: 60 # Таймаут 60 секунд
# wait_for_action возвращает результат основного действия AS IS
# response_data из результата попадает в плоский _cache = {response_completion: "Ответ 1", prompt_tokens: 50, completion_tokens: 100}
# Ожидаем результат обработки данных (сохраняется в _cache)
- action: "wait_for_action"
params:
action_id: "data_proc_1"
timeout: 30 # Таймаут 30 секунд
_namespace: "data_processing" # Используем кастомный ключ для сохранения обоих результатов
# response_data попадает в _cache.data_processing = {processed_data: "Обработанные данные", status: "success"}
# Используем результаты (все данные доступны через _cache)
- action: "send_message"
params:
text: |
AI ответ: {_cache.response_completion|fallback:Не получен}
Токены: {_cache.completion_tokens|fallback:0}
Обработанные данные: {_cache.data_processing.processed_data|fallback:Не обработаны}
Статус: {_cache.data_processing.status|fallback:Неизвестно}ℹ️ Важно:
wait_for_actionвозвращает результат основного действия AS IS (как будто оно выполнилось напрямую)- Результат обрабатывается на уровне
scenario_engineкак обычныйresponse_data - Данные попадают в плоский
_cache(или_cache[_namespace]если указан_namespace, с учетом подмены ключа через_response_key) - Если
wait_for_actionвызывается дважды без_namespace, второй результат перезапишет первый (данные мержатся через глубокое слияние) - Решение: Используйте
_namespaceдля сохранения результатов разных async действий в разные ключи - Дополнительно: Используйте
_response_keyдля удобной подмены ключа replaceable поля в результатах async действий
Рекомендации:
- Всегда указывайте
action_idдля async действий — это единственный способ отслеживать их статус - Добавляйте таймауты в
wait_for_actionдля долгих операций — это предотвращает бесконечное ожидание - Обрабатывайте ошибки — добавляйте
transitionдля обработкиerrorиtimeoutрезультатов вwait_for_action - Используйте циклы проверки готовности с задержкой — если нужен цикл проверки готовности, обязательно добавляйте
sleepперед возвратом к проверке - Для нескольких async действий используйте
wait_for_actionнапрямую — не создавайте циклы проверки готовности для нескольких действий, просто ждите каждое черезwait_for_actionс таймаутом - Используйте
_namespaceдля сохранения результатов разных async действий в разные ключи_cache