QuBot: Справочник
Структура проекта
Проект с чат-ботом является объектом, свойствами которого являются другие объекты. Базовыми объектами (разделами проекта) являются:
bot: # общие настройки бота slots: # перечень слотов (ячейк памяти) nlu: # работа в вводом на естественном языке states: # описание состояний (основной раздел) default: # поведение input и message по умолчанию (если их нет в состоянии) |
includes: - "about_us.yml" - "test_ML.yml" |
Структура состояния
Состояния описываются в разделе states. Каждое состояние имеет уникальное имя и содержит список объектов. У каждого объекта есть уникальное свойство (text, slots, ...), которое определят его тип. Часть объектов визуальные (text, button, image), другая часть - логические. Объекты одного и того же типа могут встречаться в списке несколько раз.
Списки визуальных объектов состояния могут быть разбиты на блоки. Начало следующего блока помечается объектом block. Каждый блок является отдельным сообщением. В web-chat это сообщение рисуется на подложке заданного (и при желании изменяемого) цвета. В месенджерах блоки могут оказаться разбитыми на отдельные сообщения.
В состояниях может не быть визуальных объектов. Такие состояния называются логическими. В них выполняются некоторые действия (action), устанавливаться слоты (slots) и т.п. В конце логического состояния ставится оператор перехода (goto) в новое состояние (визуальное или логическое).
Полный перечень объектов, которые могут встретиться в состоянии:
STATE: - block: # начало новой плашки-сообщения - text: # текст, возможно на нескольких языках - image: # картинка - map: # карта - random: # случайный выбор контента - again: # повторный запуск контента - run: # запуск контента другого состояния и возврат обратно - state: # состояние перехода по умолчанию (если нет в button, input, message) - goto: # переход в другое состояние без возврата - time: # время жизни состояния, после которого автоматически переходим на state - slots: # изменение значений слотов - action: # выполнение встроенных действий (операции со списками и т.п.) - if: # условие - while: # цикл - buttons: # раздел кнопок (не обязателен) - input: # раздел ввода текста (должен быть один) - message: # раздел получения сообщения (должен быть один) - button: # кнопка (может быть также в состоянии, вызываемом по run) - row: # строка из кнопок (аналогично) - intent: # обработка NLU-намерений последнего ввода INPUT_VALUE - extract: # извлечения слотов из последнего ввода INPUT_VALUE - value: # анализ последнего INPUT_VALUE - show: # показать список визуальных объектов (используется при обмене сообщениями) - print: # вывести отладочный текст в окно debug |
Визуальные объекты состояния
✒ text - добавляет текст в сообщение. В web-chat этот текст окружается тегами абзаца <p> ... </p>.
Если текст многострочный (внутри есть \n), то символы конца строк в web-chat заменяются на обрыв строки <br>.
Значение свойства text - это строка (единичная или многострочная):
STATE: - text: "Это текст в одну строчку" - text: | Это текст в несколько строк Так , это вторая строка |
STATE: - text: ru: "Мы писали, мы писали. Наши пальчики устали." en: | We wrote , we wrote... Our fingers are tired. |
В месенджерах (например telegram) объект text отправляется отдельным сообщением (нарисованным на фоне своей подложки). Если состояние имеет визуальные объекты, то для месенджеров один из них обязательно должен быть текстом или картинкой. Если состояние имеет кнопки, то перед ним обязательно должен идти хотя бы один text или image (для месенджеров).
В строке текста могут помещаться значения слотов (перед их именем ставится знак доллара) и фигурные скобки {...}, содержащие выражение, которое необходимо вычислить (это выражение на Python). В результирующей строке произойдёт замена слотов на их значения и будут проведены соответствующие вычисления внутри {...}:
STATE: - text: "Отлично $NAME. Вы купили {$ITEM_NUM1+$ITEM_NUM2} товаров." - text: "Компания $INFO.COMAPNY благодарит Вас!" |
slots: INFO: type: categorical value: COMPANY values: COMPANY: ru: "Абсолютист" en: "Absolutist" DESCR: "..." |
У объекта text может быть свойство p: false. Оно означает, что в web-chat текст тегами абзаца не нужно. Ещё одно свойство pre: true окружает текст тегами <pre> ... </pre> - моношириннный шрифт с сохранением всех пробелов.
✒ image - выводимая в сообщении картинка. Она может содержать необязательное свойство caption. Это подпись под картинкой, которая "прижимается" к ней.
STATE: - image: "im/squirrel.jpg" caption: "Это белочка" |
✒ map - вывод карты google. Ссылка соответствует полю src= в ссылке, которая генериться в www.google.com/maps. Для её получения, нажимаем на кнопку с полосочками в левом верхнем углу google map и выбираем "Ссылка/код", а там закладку "Встраивание карт".
STATE: |
✒ random - выбрать случайный объект из списка. Ниже будет случайно выбран один из двух текстов:
STATE: - random: - text: "Я Вас не понял" - text: "Не понятно... Попробуйте сформулировать по другому." |
Если случайно нужно выбрать не один объект, а группу, то используется блок actions:
STATE: - random: - text: "Я Вас не понял" - actions: - text: "Не понятно... Попробуйте сформулировать по другому." - image: im/image.png |
✒ again - выбор и выполнение списка объектов, в зависимости от номера повтора вызова этого блока. Для его использования, необходимо создать слот типа int с нулевым начальным значением (это будет так, если value не указать). Ниже такой слот имеет имя SLOT_WAS_ARE_YOU_BOT. При заходе в again, стоящий в нём слот (без знака доллара!) увеличивается на единицу, а затем выполняется соответствующий его значению раздел (приведенные ниже числа) из ключа cases. Эти числа не обязательно идут подряд и в них могут быть пропуски:
STATE: - again: SLOT_WAS_ARE_YOU_BOT cases: 1: - text: "Да, я искусственный разум, созданный компанией QuData." 3: - text: "Да, да, это я." more: - text: "Да, кожаный мешок, набитый костями. Я не человек." |
Если значение слота больше любого из чисел в списке, то выполняется болок more. В примере выше, при первом заходе будет выведен текст "Да, я искусственный разум...", при втором заходе ничего не будет выведено, при третьем - текст "Да, да, это я." Все последующие заходы будут порождать последний текст. В списке каждого блока могут быть любые объекты.
В again можно также передавать значение (доллар) слота (again: $SLOT), если в строковом слоте SLOT хранится имя слота, использующегося как счётчик.
В разделе more можно вызвать random, чтобы разнообразить контент при "переполнении" счётчика.
✒ video - выводимое в сообщении видео.
✒ audio - - выводимое в сообщении аудио.
Событийные объекты состояния
✒ buttons - перечисляет список кнопок button (они будут идти в столбик)
или строчек row, содержащих список кнопок button (они будут идти в строчку):
STATE: - text: "Нажми на кнопку" - buttons: - button: "Кнопка 1" - row: - button: "Кнопка 2.1" - button: "Кнопка 2.2" - button: "Кнопка 3" |
В списке раздела buttons (и в row), кроме row и button, могут быть объекты if, while, action, run. Если результатом их работы будет объект button, он добавится в соответствующем месте (в строчке или колонке). Это позволяет создавать меню кнопок, зависящее от, например, текущего значения слотов. Других визуальных элементов if, while, action, run в разделе buttons порождать не должны:
STATE: - text: "Текст" - buttons: - button: "Кнопка 1" - if: $AMOUNT > 3 then: - button: "Кнопка 2" - run: RUN_SHOW_BUTTONS_BACK # в этом состоянии есть button, row |
✒ button - одна кнопка. Кроме заголовка (возможно на разных языках) могут также находится следующие объекты, которые активируются при нажатии кнопку:
- button : "Текст" # надпись на кнопке answer: "Более полный текст" # если нужно вывести "ответ", отличный от button slots: { ITEM : $VAL1 , ANOUNT : 5 } # установка слотов, при нажатии state: STATE_NAME # состояние в которое переходим при нажатии run: RUN_SOME_STATE # сформировать контент перед переходом actions: [ ] # список любых объектов (см. ниже) |
- button : "Текст" # надпись на кнопке actions: # действия, выполняемые при на нажатии перед переходом - text: # визуальные элементы добавятся перед новым состоянием - image: #... - action: ... - if: ... - run: ... - slots: ... #... |
Если под button есть state, то переход по нажатию на кнопку произойдёт в это состояние. Иначе будет выбрано состояние по умолчанию, указанное в state в корне данного состояния. Если state нет ни в кнопке, но в корне состояния, то будет повторно показано текущее состояние.
Если в кнопке (под actions) есть визуальные элементы ( text, image,...), они будут выведены в начале следующего состояния.
В кнопке может быть свойство url: "http://google.com". Если оно есть, при нажатии кнопки произойдёт переход по ссылке (в другом окне браузера).
По умолчанию текст на кнопке центрируется. Это поведение можно изменить свойством text_align: left (или right). Надпись на кнопке в этом случае будет прижата влево или вправо. Второе свойство text_margin: 10 определяет отступ в пикселях от левого или правого края кнопки соответственно. Эти свойства сработают только в web-чате.
✒ input - строка ввода. В настоящее время она одна и её положение в списке объектов состояния роли не играет. Может содержать свойства, срабатывающие при вводе текста пользователем:
STATE: - input: - state: NEW_STATE # куда перейдём при нажатии ввода - slots: { NAME : $INPUT_VALUE } # какие слоты нужно изменить - action: ... - if: ... - run: ... - slots: ... |
Можно анализировать значение ввода в разделе value и выполнять те или иные действия:
STATE: - input: - value: "1" : # введен текст 1 - slots: { TEST_PYTHON : $TEST_PYTHON+1000 } - state: NEXT "2" : # введен текст 2 - slots: { WRONG : 1 } - state: GO - state: NEXT # переход по умолчанию, если нет в value |
Если каждое действия данного типа в разделе input присутствует в единственном экземпляре и их порядок не важен, то input может быть объектом (аналогично button):
STATE: - input: extract: [ EMAIL ] value: ... state: NEW_STATE # куда перейдём при нажатии ввода slots: { NAME : $INPUT_VALUE } # какие слоты нужно изменить run: ... actions: ... |
Если в input и в корне состояния нет state, то будет повторно вызвано текущее состояние.
✒ message - раздел активируется, если придёт сообщение от другого бота или от этого же бота, но от другого пользователя. Аналогично input может содержать список любых объектов, допустимых в состоянии. Если в нём и в корне состояния нет state, то будет повторно вызвано текущее состояние.
Управление сообщениями
Визуальные и событийные объекты состояния по умолчанию формируют одно сообщение. В web-chat оно рисуется на подложке заданного цвета (по умолчнию или из раздела bot), кроме кнопок buttons, которые идут ниже. В месенджерах такое сообщение, возможно будет разбито на несколько (будет несколько подложек).
В состоянии может быть несколько сообщений. Начало нового сообщения определяется объектом block:
STATE: - text: "Будет на первой подложке" - block: - text: "Будет на второй подложке" |
STATE: - block: background: "#09f" # RGB - цвет подложки delay: 500 # пауза 500 ms effect: TYPING # перед появлением иммитация набора |
Желательно раздел buttons (или список button, row) ставить последним визуальным компонентом в последнем блоке (сообщении). Для web-chat это не критично, но для работы бота в месенджерах важно придерживаться этого правила.
Объект block может стоять перед самым первым визуальным элементом для определения свойств первого сообщения. Например, в нём можно изменить цвет первой подложки и удалить предыдущее сообщение:STATE: - block: background: "#ааа" # RGB - цвет подложки clear: 1 # удалить предыдущее сообщение |
По умолчанию кнопки предыдущего сообщения удаляются, а после него имитируется ответ пользователя в виде строки, содержащей текст на кнопке или ввод в поле редактирования. При помощи clear из раздела block можно удалять любое число предыдущих сообщений (все визуальные элементы):
clear: 1 # удалить предыдущее сообщение clear: 10 # удалить 10 предыдущих сообщений clear: -1 # удалить все предыдущие сообщения clear: 0 # удалить себя, после перехода из состояния |
Переходы между состояниями
✒ goto - мгновенный переход на новое состояние. Игнорируются все остальные объекты ниже, поэтому обычно стоит последним в списке объектов состояния или под if. Всегда используется в стартовом состоянии для указания первого запускаемого состояния:
START_STATE: - slots: { LANGUAGE : ru } - goto: GREET_STATE |
В качестве значения goto выступает имя состояния или имя слота строкового типа (с префиксом $). Это позволяет сохранять в слотах имена состояний:
slots: SAVE_STATE: type: str states: ... STATE1: - slots { SAVE_STATE : STATE1 } # запомнили состояние (как строку STATE1) ... STATE2: ... - goto: $SAVE_STATE # перейдём в STATE1, полуив значение ($) слота |
Переход goto не сбрасывает визуальные элементы состояния (стоящие выше него) и они выводятся в чат.
✒ state - состояние для перехода при возникновении события (нажата кнопка, произведен ввод в строке редактирования). В списке объектов состояния state означает переход по умолчанию (значение state, если его не было в событийном объекте):
STATE: - text: "Привет. Как дела?" - button: "😆 Хорошо" state: SCR_OK # сюда попадут по нажатию этой кнопки - button: "😑 Так себе" - button: "😰 Ужасно" - state: SCR_WHAT_A_PITY # сюда попадут по кнопкам "Так себе" и "Ужасно" |
✒ run - аналогично state или goto содержит имя состояния или строковый слот, хранящий это имя (со знаком $). При встречи этого объекта, происходит временное покидание текущего состояния. Выполнятся все действия в новом состоянии и происходит возврат обратно в ту точку откуда был вызван run. Вызываемое состояние может также формировать визуальные элементы, которые добавятся к элементам состояния откуда был вызван run.
Логические объекты состояния
✒ slots - установка значения слотов:
STATE: - slots: SLOT3: 3.141592 - slots: { SLOT1 : "Привет" , SLOT2 : $SLOT3 } |
STATE: - slots: SLOT1: "Привет" SLOT2: $SLOT3 |
Напомним, что если значением слота является другой слот, то перед его именем необходимо поставить символ доллара, иначе он будет интерпретироваться как обычная строка, содержащая имя слота. В слотах можно использовать вычисляемые выражения:
STATE: - slots: SLOT1: $NAME # должны совпадать типы слотов SLOT1 и NAME SLOT2: $COUNT + 1 # запишется результат вычисления |
Раздел slots может присутствовать в событийных объектах (button, input, message). Тогда он выполняется только при срабатывании этого объекта.
✒ if - объект условия. Содержит строку с логическим выражением и раздел then, выполняемый, если выражение истинно и раздел else, если выражение ложно (один из этих разделов может отсутствовать).
В логическом выражении могут быть числа, слоты (начиняющиеся с $) и строки. Строки должны браться в одинарные кавычки 'Маша', '✅' и т.д.
Равенство двух величин это ==. Для сравнения чисел можно использовать >, <, ==, >=, <=, !=. Возможны логические связки and, or, not и скобки. Для чисел - арифметические выражения. Если переменная (слот) не определена у неё значение None. Вот несколько примеров:
if: ($NAME == 'Оля' or $NAME == 'Маша' ) and $AGE > 18 if: $X + $Y > $Z if: $NAME == None |
Полный пример раздела if:
STATE: - if: $AMOUNT > 5 then: - slots: { } # установить слоты - state: STATE1 # задать состояние перехода по умолчанию - text: "AAA" # вывести текст (всегда на первом уровне списка состояния) - buttons: # если другого buttons в состоянии нет (и на первом уровне) - button: # если if внутри buttons или row else: - goto: STATE2 # мгновенный переход |
Следующим образом можно проверить длину строки и добавить в сообщение текст:
- if : len($NAME) > 3 and $AGE < 16 then: - text: "Ты так молод $NAME!" |
✒ while - похож на if, но повторяет then пока условие истинно, но не более 1000 раз.
- slots : { COUNTER : 0 } - while : $COUNTER < 10 # повторяем then, пока выполняется условие then: - text: "$COUNTER, " p: false - slots: { COUNTER : $COUNTER+1 } # увеличиваем счётчик повторов |
✒ action - встроенное или пользовательское действие, написанное на Python. Оно может содержать различные аргументы и возвращать различные значения:
STATE: - action: ACTION_LIST_EMPTY # имя действия, проверяющего длину списка value: ORDER # аргумент действия (что проверяем) result: # возможные результаты работы true: [ state : THANK_YOU ] false: [ state : GET_EMAIL ] |
✒ time - время жизни сообщения. После его истечения (если не было событий от button, input или message) происходит переход в следующее состояние (state). Полезно, например, при проведении тестирования клиента, когда на каждый вопрос отведено определенное время:
JOB_TEST_ML1: - text: "[1/4] Переобучение модели может возникать, когда:" - buttons: - button: "Модель очень хорошая" - button: "В модели слишком много параметров" slots: { TEST_ML : $TEST_ML+1000 } - button: "Модель очень долго учится" - time: 60000 # на ответ отведена минута - state: JOB_TEST_ML2 # не нажмёт на кнопку, идём дальше |
При помощи time и clear следующим образом можно реализовать таймер, выводящий на протяжении одной минуты в тексте сообщения секунды (перезаписывая предыдущий текст):
TIMER_STATE: - if: $COUNTER > 60 then: - goto: PHONE_EXPLOSION_STATE # закончить работу таймера. - block: clear: 0 # удаляем себя после time - text: "Прошло $COUNTER секунд." # визуальный контент сообщения - slots: { COUNTER : $COUNTER+1 } # увеличиваем счётчик - time: 1000 # живём 1000 ms |
✒ intents: - обработка в произвольном месте последнего намерения человека (Работа с естественным языком):
#... RUN_CHITCHAT: # ответы на болтовню - intents: I_ARE_YOU_BOT: - text: "Да, я искусственный разум, созданный компанией QuData." I_WHAT_IS_YOUR_NAME: - text: "С утра меня звали Анна. Но для Вас - просто Нюшенька." |
✒ print: "строка" выводит на страницу в разделе Info строку. Можно использовать для отладочных целей.
Предопределённые состояния
Существуют три предопределённых состояния. Первое START_STATE описывает разработчик бота. Это состояние всегда должно быть в проекте. Оно запускается первым, делает необходимые настройки (например установку языка) и по goto переходит на первый экран.
Ещё два состояния PREV_STATE и PREV_STATE_2 могут использоваться в state и goto для перехода на одно или два состояния назад. Впрочем, для этих целей можно использовать и сохранённые в слотах имена состояний.
Слоты
Все данные хранятся в слотах, которые являются парами ключ-значение. Ключ - это имя слота. Его значение может иметь различные типы:
- int - целое число;
- float - вещественное число с плавающей точкой;
- str - строка;
- bool - логическое значение;
- list - список (массив);
- dict - словарь, состоящий из ключей и значений;
- categorical - множество заранее заданных значений;
- email - адрес электронной почты;
- http - web-адрес;
- phone - номер телефона;
- date - дата;
- time - время;
Слоты объявляются в разделе slots (он находится на корневом уровне файла проекта, там же, где и раздел states). У каждого слота обязательно надо указывать его тип (type) и можно, при необходимости, начальное значение (value):
slots: NAME: type: str # тип слота строка COUNT: type: int # тип - целое число value: 5 # начальное значение |
В результате работы (например, при попытке вытащить из строки ввода информацию), значение слота может оказаться равным None (не определено). Это можно использовать в условных операторах (if: $NAME == None).
Значением категориального слота (categorical) является один из определённых в разделе values ключей:
slots: ITEM: type: categorical value: Milk # начальное значение values: # возможнные значения: Milk: Молоко Wine: ru: Вино en: Wine |
В слотах строкового типа также можно использовать несколько языков.
Предопределённые слоты:
CURRENT_STATE | (type: str) | - имя текущего состояния |
LANGUAGE | (type: str) | - имя текущего языка |
INPUT_VALUE | (type: str) | - текст из последнего ввода в строке input |
BUTTON_ID | (type: int) | - номер последней нажатой кнопки (с единицы) |
BUTTON_NAME | (type: str) | - надпись на последней нажатой кнопке |
NLU_INTENT | (type: str) | - последнее распознанное намерение |
USER_ID | (type: str) | - идентификатор пользователя бота |
FRIENDS_ID | (type: list) | - список друзей (от которых можно принимать сообщения) |
MESSAGES | (type: list) | - список сообщений |
Вычисления
✒ Математические функции
- sqrt($X) - квадратный корень
- pow($X, 3) - возведение в степень
- fabs($X) - взятие по модулю вещественного числа
- exp($X) - экспонента;
- log($X) - натуральный логарифм;
- log10($X) - логарифм по основанию 10;
- log2($X) - логарифм по основанию 2;
- pi - число пи;
- e - основание натурального логарифма;
- cos($X) sin($X) tan($X) - тригонометрические функции;
- acos($X) asin($X) atan($X) atan2($X,$Y) - обратные тригонометрические функции.
✒ Округление
- floor($X) - округление чисел в меньшую сторону: floor(5.8) = 5, floor(-6.1) # = -7;
- trunc($X) - отбрасывание дробной части: trunc(5.51) = 5, trunc(-6.99) = -6;
- ceil($X) - округление чисел в большую сторону: ceil(5.15) = 6;
- round($X,$N) - округление до $N чисел после запятой: round(pi, 2) = 3.14;
✒ Дата и время
- now() - текущая дата и время в неотформатированном виде
- time() - текущее время
- date() - текущая дата
- text : "{time()}" # 09:02:12 - text : "{time('%H:%M')}" # 09:02 - text : "{date()}" # 2021-09-23 - text : "{date('%m/%d', days=5)}" # 09/28 - text : "{date('%b, %d')}" # Sep, 23 - text : "{date('%b, %d', weeks=1)}" # Sep, 30 |
✒ Случайные числа
- random() - возвращает случайное число float в диапазоне [0,1];
- randint(min,max) - возвращает целое случайное число int в диапазоне [min,max];
- choice(list) - возвращает случайный элемент списка list;
- shuffle(list) - перемешивает список list.
✒ Разное
- len($X) - длина списка list или строки str;
- min($X,$Y,...) - минимальное число;
- max($X,$Y,...) - максимальное число;
- sum($X,$Y,...) - сумма чисел; аргументом может список list из чисел.
Логические выражения
Значение ключа if - это строка, содержащая логическое выражение. Логическое выражение принимает два значения True и False. В выражении могут быть числа, переменные (имена слотов с $) и строки. Строки должны браться в одинарные кавычки 'Маша', '✅' и т.д.
Равенство двух величин это ==. Для сравнения чисел можно использовать >, <, ==, >=, <=, !=. Возможны логические связки and, or, not и скобки. Для чисел - арифметические выражения. Если переменная (слот) не определена у неё значение None. Вот несколько примеров:
if: ($NAME == 'Оля' or $NAME == 'Маша' ) and $AGE > 18 if: $X + $Y > $Z if: $NAME == None if: len($NAME) > 0 |