Плейсхолдеры
Назначение: Динамическая подстановка значений в параметры действий сценариев.
Механика работы:
Все атрибуты в params автоматически обрабатываются плейсхолдерами перед выполнением действия. Это означает:
- Все значения в
params— строки, числа, массивы, объекты — проходят через обработку плейсхолдеров - Все уровни вложенности — плейсхолдеры работают на любом уровне структуры
params(в строках, элементах массивов, значениях объектов) - Автоматическая обработка — происходит для каждого шага сценария перед выполнением действия
- Источник данных — система использует накопленные данные события и результаты предыдущих действий как источник значений для подстановки
Синтаксис плейсхолдеров:
Простая замена:
params:
text: "Привет, {username}!"
chat_id: "{user_id}"С модификаторами:
params:
text: "Привет, {username|fallback:Гость}!"
price: "{amount|*0.9|format:currency}"Литеральные значения в кавычках:
params:
text: "{'hello'|upper}" # HELLO (без передачи через словарь)
duration: "{'1d 2w'|seconds}" # 1296000 (литеральное время)
calculation: "{'100'|+50}" # 150 (литеральная арифметика)
date_shift: "{'2024-12-25'|shift:+1 day}" # 2024-12-26 (литеральная дата)
# Двойные кавычки для строк с одинарными кавычками
text: "{"it's working"}" # it's working
# Экранированные кавычки
text: "{'it\'s working'}" # it's workingВложенные плейсхолдеры:
params:
text: "Ответ: {status|equals:{expected}|value:OK|fallback:BAD}"⚠️ Важно: Кавычки в YAML для плейсхолдеров с regex
Проблема: YAML по-разному обрабатывает escape-последовательности в одинарных и двойных кавычках, что критично для regex паттернов с обратными слешами.
Решение: Используйте двойные кавычки для regex паттернов с обратными слешами (\s, \d, \w и т.д.).
Примеры:
# ❌ ОШИБКА - в одинарных кавычках \\s не экранируется правильно
params:
value: '{event_text|regex:^([^\\s]+)|lower}' # \\s становится \s (обратный слеш + s), а не пробел!
# ✅ ПРАВИЛЬНО - двойные кавычки правильно экранируют обратные слеши
params:
value: "{event_text|regex:^([^\\s]+)|lower}" # \\s становится \s (пробел в regex)
# ✅ ПРАВИЛЬНО - для regex без обратных слешей можно использовать одинарные кавычки
params:
tenant_id: '{user_state|regex:tenant_(\d+)}' # Работает, так как \d не требует двойного экранирования
# ✅ ПРАВИЛЬНО - для простых regex с одним обратным слешем тоже двойные кавычки
params:
value: "{event_text|regex:\\s+([^\\s]+)$|lower}" # \\s для пробела, \\s для пробела
# ✅ ОБЫЧНЫЕ ПЛЕЙСХОЛДЕРЫ - без разницы
params:
text: "Привет, {username}!" # Работает
text: 'Привет, {username}!' # Тоже работаетКогда использовать двойные кавычки:
- Regex паттерны с обратными слешами —
{field|regex:pattern}где pattern содержит\s,\d,\w,\n,\tи т.д. - Важно: В двойных кавычках нужно использовать двойное экранирование:
\\sдля пробела,\\dдля цифры - Пример:
"{event_text|regex:^([^\\s]+)|lower}"—\\sстановится\s(пробел в regex)
Когда можно использовать одинарные кавычки:
- Regex паттерны без обратных слешей — простые паттерны без
\s,\d,\wи т.д. - Обычные плейсхолдеры —
{username},{user_id} - Простые модификаторы —
{name|upper},{price|format:currency} - Текстовые значения — без специальных символов
Правило: Если в regex паттерне есть обратные слеши (\s, \d, \w и т.д.) — всегда используйте двойные кавычки с двойным экранированием (\\s, \\d, \\w).
Доступ к вложенным данным:
params:
# Точечная нотация для объектов
text: "Пользователь: {user.profile.name|fallback:Неизвестный}"
text: "Сообщение: {message.text}"
# Индексы для массивов
text: "Первый файл: {attachment[0].file_id}"
text: "Последний пользователь: {users[-1].name}"
# Комбинированный доступ
text: "Событие: {event.attachment[0].type}"
text: "Данные: {response.data.items[0].value}"Доступные данные:
Все данные события и предыдущих действий доступны для использования в плейсхолдерах:
Данные события:
user_id— ID пользователяusername— имя пользователяchat_id— ID чатаevent_text— текст сообщенияcallback_data— данные callback кнопкиevent_date— время событияtenant_id— ID тенантаmessage_id— ID сообщенияreply_to_message_id— ID сообщения, на которое отвечаемforward_from— данные о пересылке- И любые другие поля — в зависимости от типа события
Конфигурация тенанта:
_config— словарь с конфигурацией тенанта (атрибуты изconfig.yaml)_config.openrouter_token— OpenRouter API ключ для тенанта (если установлен)- Другие атрибуты — в зависимости от конфигурации тенанта
- Автоматически доступен во всех сценариях тенанта
Данные предыдущих действий:
last_message_id— ID последнего отправленного сообщенияresponse_data— любые данные, возвращённые предыдущими действиями- Другие поля — в зависимости от действий в сценарии
Доступ к вложенным элементам:
Универсальный доступ к данным! Поддержка точечной нотации для объектов и индексов для массивов.
| Синтаксис | Описание | Пример | Результат |
|---|---|---|---|
| Объекты | |||
object.field | Доступ к полю объекта | {message.text} | Привет мир |
object.field.subfield | Вложенные поля | {user.profile.name} | Иван Петров |
| Массивы | |||
array[index] | Доступ к элементу массива | {attachment[0].file_id} | file_1 (первый файл) |
array[-index] | Отрицательный индекс | {attachment[-1].file_id} | file_3 (последний файл) |
array[index].field | Поле элемента массива | {users[1].name} | Боб (имя второго пользователя) |
array[index][index] | Вложенные массивы | {matrix[0][1]} | 2 (элемент матрицы) |
| Комбинированный | |||
object.array[index].field | Смешанный доступ | {event.attachment[0].file_id} | file_1 |
Примеры использования:
params:
# Объекты
text: "Сообщение: {message.text}"
text: "Пользователь: {user.profile.name|fallback:Неизвестный}"
# Массивы
text: "Первый файл: {attachment[0].file_id}"
text: "Последний пользователь: {users[-1].name|upper}"
text: "Файл: {attachment[0].file_id}, размер: {attachment[0].size}"
# Комбинированный доступ
text: "Событие: {event.attachment[0].type}"
text: "Данные: {response.data.items[0].value}"
# Конфигурация тенанта
text: "Токен: {_config.openrouter_token|fallback:Не установлен}"
# Безопасный доступ
text: "{attachment[10].file_id|fallback:Файл не найден}"Получение информации о файле
Практический пример: получение file_id и type для пересылки файлов
Создайте сценарий, который покажет file_id и type всех вложений из события:
file_info:
description: "Получение file_id и type вложений для последующей пересылки"
trigger:
- event_type: "message"
event_text: "/file"
step:
- action: "send_message"
params:
text: |
📎 Информация о файле:
file_id: <code>{event_attachment[0].file_id|fallback:{reply_attachment[0].file_id|fallback:Файл не найден}}</code>
type: <code>{event_attachment[0].type|fallback:{reply_attachment[0].type|fallback:Тип не найден}}</code>Обработка ошибок:
- При обращении к несуществующему полю/индексу возвращается
None - Можно использовать модификатор
fallbackдля значения по умолчанию - Отрицательные индексы работают как в Python:
-1= последний элемент - Поддерживается неограниченная глубина вложенности
Модификаторы:
| Модификатор | Описание | Пример | Результат |
|---|---|---|---|
| Арифметические операции | |||
+value | Сложение | {price|+100} | 1500 (если price=1400) |
-value | Вычитание | {price|-50} | 1350 (если price=1400) |
*value | Умножение | {price|*0.9} | 1260 (если price=1400) |
/value | Деление | {seconds|/3600} | 2.5 (если seconds=9000) |
%value | Остаток от деления | {number|%7} | 3 (если number=10) |
| Регистр | |||
upper | Верхний регистр | {name|upper} | ИВАН (если name="Иван") |
lower | Нижний регистр | {name|lower} | иван (если name="Иван") |
title | Заглавные буквы каждого слова | {name|title} | Иван Петров (если name="иван петров") |
capitalize | Первая заглавная буква | {name|capitalize} | Иван (если name="иван") |
case:type | Преобразование регистра | {name|case:upper} | ИВАН (если name="Иван") |
| Списки | |||
tags | Преобразование в теги | {users|tags} | @user1 @user2 |
list | Маркированный список | {items|list} | • item1\n• item2 |
comma | Через запятую | {items|comma} | item1, item2 |
expand | Разворачивание массива массивов на один уровень (только в массивах) | {keyboard|expand} | [[a, b], [c]] → [a, b], [c] (в массиве) |
keys | Извлечение ключей из объекта (словаря) в массив | {storage_values|keys} | ["group1", "group2"] (если storage_values={"group1": {...}, "group2": {...}}) |
| Преобразования | |||
code | Оборачивание значения в code блок | {field|code} | <code>value</code> |
| Форматирование строк | |||
truncate:length | Обрезка текста | {text|truncate:50} | Очень длинный текст... |
| Операции с датами и временем | |||
shift:±интервал | Сдвиг даты на интервал (PostgreSQL: +1 day, -2 hours, +1 year 2 months). Поддержка всех форматов дат, включая ISO с таймзоной, корректная обработка месяцев/лет | {created|shift:+1 day} | 2024-12-26 (если created="2024-12-25") |
seconds | Преобразование временных строк в секунды (формат: Xw Yd Zh Km Ms) | {duration|seconds} | 9000 (если duration="2h 30m") |
to_date | Приведение даты к началу дня (00:00:00), возвращает ISO формат | {created|to_date} | 2024-12-25 00:00:00 |
to_hour | Приведение даты к началу часа (минуты и секунды = 0), возвращает ISO формат | {created|to_hour} | 2024-12-25 15:00:00 (если created="2024-12-25 15:30:45") |
to_minute | Приведение даты к началу минуты (секунды = 0), возвращает ISO формат | {created|to_minute} | 2024-12-25 15:30:00 (если created="2024-12-25 15:30:45") |
to_second | Приведение даты к началу секунды (микросекунды = 0), возвращает ISO формат | {created|to_second} | 2024-12-25 15:30:45 |
to_week | Приведение даты к началу недели (понедельник 00:00:00), возвращает ISO формат | {created|to_week} | 2024-12-23 00:00:00 (если created="2024-12-25 15:30:45") |
to_month | Приведение даты к началу месяца (1 число, 00:00:00), возвращает ISO формат | {created|to_month} | 2024-12-01 00:00:00 (если created="2024-12-25 15:30:45") |
to_year | Приведение даты к началу года (1 января, 00:00:00), возвращает ISO формат | {created|to_year} | 2024-01-01 00:00:00 (если created="2024-12-25 15:30:45") |
| Форматирование дат и времени | |||
format:timestamp | Преобразование в Unix timestamp | {date|format:timestamp} | 1703512200 |
format:date | Формат даты (dd.mm.yyyy) | {timestamp|format:date} | 25.12.2024 |
format:time | Формат времени (HH:MM) | {timestamp|format:time} | 14:30 |
format:time_full | Формат времени с секундами (HH:MM:SS) | {timestamp|format:time_full} | 14:30:45 |
format:datetime | Полный формат (dd.mm.yyyy HH:MM) | {timestamp|format:datetime} | 25.12.2024 14:30 |
format:datetime_full | Полный формат с секундами (dd.mm.yyyy HH:MM:SS) | {timestamp|format:datetime_full} | 25.12.2024 14:30:45 |
format:pg_date | Формат даты для PostgreSQL (YYYY-MM-DD) | {timestamp|format:pg_date} | 2024-12-25 |
format:pg_datetime | Формат даты и времени для PostgreSQL (YYYY-MM-DD HH:MM:SS) | {timestamp|format:pg_datetime} | 2024-12-25 14:30:45 |
| Форматирование чисел | |||
format:currency | Форматирование валюты | {amount|format:currency} | 1000.00 ₽ |
format:percent | Форматирование процентов | {value|format:percent} | 25.5% |
format:number | Форматирование чисел | {value|format:number} | 1234.56 |
| Условные | |||
equals:value | Проверка равенства (строковое сравнение) | {status|equals:active} | true или false |
in_list:items | Проверка вхождения в список | {role|in_list:admin,moderator} | true или false |
true | Проверка истинности (рекомендуется для boolean) | {is_active|true} | true или false |
exists | Проверка существования значения (не None и не пустая строка) | {field|exists} | true или false |
is_null | Проверка на null (None, пустая строка или строка "null") | {field|is_null} | true или false |
value:result | Возврат значения при истинности | {status|equals:active|value:Активен} | Активен или null |
| Служебные | |||
fallback:value | Значение по умолчанию | {username|fallback:Гость} | Иван или Гость |
length | Подсчёт длины | {text|length} | 15 (количество символов) |
length | Подсчёт длины массива | {array|length} | 3 (количество элементов) |
regex:pattern | Извлечение по regex | {text|regex:(\d+)} | 123 (первое число) |
| Асинхронные действия | |||
ready | Проверка готовности async действия | {_async_action.ai_req_1|ready} | true если завершено, false если выполняется |
not_ready | Проверка что действие ещё выполняется | {_async_action.ai_req_1|not_ready} | true если выполняется, false если готово |
Примеры использования:
params:
# Простые операции
result: "{value|+100|format:currency}"
# Условная логика
status: "{user_status|equals:active|value:Активен|fallback:Неактивен}"
# Цепочка модификаторов
formatted: "{price|*0.9|format:currency|fallback:0.00 ₽}"
# Извлечение по regex (для обратных слешей — двойные кавычки!)
duration: "{event_text|regex:(\\d+)\\s*min|fallback:0}"
# Точечная нотация
name: "{user.profile.name|fallback:Неизвестный}"
# Временные значения
duration_seconds: "{duration|seconds}"
duration_minutes: "{duration|seconds|/60}"
duration_hours: "{duration|seconds|/3600}"
formatted_time: "{duration|seconds|format:number}"
# Сдвиг дат
tomorrow: "{created|shift:+1 day}"
next_month: "{created|shift:+1 month|format:date}"
complex_shift: "{created|shift:+1 year 2 months|format:datetime}"
# Приведение к началу периода
start_of_day: "{created|to_date}"
start_of_week: "{created|to_week}"
formatted_month_start: "{created|to_month|format:date}"
# Boolean поля (различать True/False/None)
is_active: "{is_active|equals:True|value:✅ Включено|fallback:{is_active|equals:False|value:❌ Выключено|fallback:❓ Неизвестно}}"
is_polling: "{is_polling|equals:True|value:✅ Активен|fallback:{is_polling|equals:False|value:❌ Неактивен|fallback:❓ Неизвестно}}"
# Строковые поля (используйте equals:value)
status: "{user_status|equals:active|value:Активен|fallback:Неактивен}"
# Упрощённый вариант (если None не ожидается)
is_simple: "{is_active|true|value:✅ Включено|fallback:❌ Выключено}"
# Проверка существования значения (в условиях)
# В условиях: {field|exists} == True или {field|exists} == False
# Проверка на null (в условиях)
# В условиях: {field|is_null} == True или {field|is_null} == FalseПример использования exists в условиях:
step:
- action: "validate"
params:
condition: "{response_value.feedback|exists} == True"
transition:
- action_result: "success"
transition_action: "continue" # Значение существует
- action_result: "failed"
transition_action: "jump_to_scenario"
transition_value: "create_feedback" # Значение не существуетВажно: boolean и строковые поля
Для boolean полей (True/False):
- Рекомендуется:
{is_active|true|value:✅ Включено|fallback:❌ Выключено} - Не работает:
{is_active|equals:true|value:✅ Включено}(сравнение строк)
Для строковых полей:
- Используйте:
{status|equals:active|value:Активен|fallback:Неактивен} - Модификатор
trueне подходит для строковых значений
Практические примеры
Пример 1: Персонализированное приветствие
step:
- action: "send_message"
params:
text: |
👋 Привет, {username|fallback:Гость}!
📊 Ваша статистика:
• ID: {user_id}
• Чаты: {chat_count|fallback:0}
• Последний вход: {last_login|format:datetime|fallback:Неизвестно}Пример 2: Динамическое меню с условиями
step:
- action: "send_message"
params:
text: |
🎛️ Главное меню
Статус: {user_status|equals:active|value:✅ Активен|fallback:❌ Неактивен}
Баланс: {balance|format:currency|fallback:0.00 ₽}
inline:
- [{"📊 Статистика": "stats"}, {"💰 Баланс": "balance"}]
- [{"⚙️ Настройки": "settings"}]Пример 3: Обработка ошибок с fallback
step:
- action: "send_message"
params:
text: |
❌ Ошибка обработки
Код ошибки: {error_code|fallback:НЕИЗВЕСТЕН}
Сообщение: {error_message|fallback:Неизвестная ошибка}
Время: {timestamp|format:datetime}
inline:
- [{"🔄 Повторить": "retry"}, {"🏠 Главное меню": "main"}]Пример 4: Математические расчёты
step:
- action: "send_message"
params:
text: |
💰 Расчёт стоимости
Цена: {base_price|format:currency}
Скидка: {discount_percent|fallback:0}%
Итого: {base_price|*{discount_percent|/100}|*{base_price|-1}|+{base_price}|format:currency}Пример 5: Извлечение данных из текста
step:
- action: "send_message"
params:
text: |
📝 Обработка заказа
Номер заказа: "{event_text|regex:order\\s*(\\d+)|fallback:Не найден}"
Сумма: "{event_text|regex:amount\\s*(\\d+)|format:currency|fallback:0.00 ₽}"
Время доставки: "{event_text|regex:(\\d+)\\s*min|fallback:30}" минутПример 6: Динамическая клавиатура с модификатором expand
step:
# Получаем список тенантов
- action: "get_tenants_list"
params: {}
# Строим динамическую клавиатуру из публичных тенантов
- action: "build_keyboard"
params:
items: "{public_tenant_ids}"
keyboard_type: "inline"
text_template: "Tenant $value$"
callback_template: "select_tenant_$value$"
buttons_per_row: 2
# Отправляем сообщение с динамической клавиатурой и статичными кнопками
- action: "send_message"
params:
text: "Выберите тенант:"
inline:
- "{keyboard|expand}" # Разворачиваем динамическую клавиатуру
- [{"🔙 Назад": "main_menu"}] # Статичная кнопкаКак работает expand:
- Модификатор
expandразворачивает массив массивов на один уровень только при использовании в массиве - В примере выше
{keyboard|expand}в массивеinlineразворачивает[[{...}, {...}], [{...}]]в[{...}, {...}], [{...}] - Это позволяет комбинировать динамически сгенерированные строки клавиатуры со статичными строками
- Важно:
expandработает только в контексте массива. В строке или словаре значение остаётся без изменений
Пример без expand (неправильно):
inline:
- "{keyboard}" # ❌ Получится [[[...]], [...]] - 3 уровня вложенности!
- [{"🔙 Назад": "main_menu"}]Пример с expand (правильно):
inline:
- "{keyboard|expand}" # ✅ Получится [[...], [...]] - 2 уровня вложенности
- [{"🔙 Назад": "main_menu"}]