QuBot: 1. Введение - создаём простого бота


Введение

QuBot - это набор инструментов по созданию чат-ботов и виртуальных помощников. Ниже описан бот, основанный на правилах. Кроме навигации при помощи кнопок, он понимает текстовый ввод на естественном языке, использует множество встроенных действий, которые можно расширять при помощи пользовательских скриптов, написанных на Python.

Этот и последующие документы являются учебником. Параллельно стоит просматривать Справочник, в котором возможности кнопочного бота описаны в более формальном и сжатом виде.


Hello bot

Пусть бот состоит из трёх экранов. На первом экране будет текст, картинка и две кнопки. Нажатие на кнопки приводит к переходу на один из ещё двух экранов.

Экраны в QuBot называются состояниями. Все они перечисляются в разделе states. В этом примере будет три состояния: SCR_INTRO, SCR1, SCR2.

Кроме этого, всегда есть обязательное системное состояние с именем START_STATE.
Оно запускается первым. Ниже оно состоит из единственного объекта goto, который определяет в какое состояние необходимо перейти (это первый экран SCR_INTRO):

 
states:
    
    START_STATE:   # стартовое состояние с настройками
    
      - goto: SCR_INTRO            # показываем Стартовый экран
    #------------------------------------------------------------
    
    SCR_INTRO:     # Стартовый экран
    
      - text: "Привет, я умный бот 🤖. Отвечу на любой вопрос."

      - image: "im/squirrel.jpg"   # путь относительно config.yml
        caption: "А это белочка."  # подпись под кнопкой
      
      - button: "Кто виноват?"     # первая кнопка
        state:  SCR1               # куда перейдём при нажатии

      - button: "Что делать?"      # вторая кнопка
        state:  SCR2

    #------------------------------------------------------------
    
    SCR1:          # Попадаем из SCR_INTRO по "Кто виноват?"
    
      - text: "Как всегда, виноваты враги. Согласны?"
      
      - row:                       # две кнопки в линию 
          - button: "✔️ Да"
            state:  SCR_INTRO

          - button: "❌ Нет"
            state:  SCR2
    #------------------------------------------------------------
    
    SCR2:          # Попадаем из "Что делать?" и по "Нет" (SCR1).
    
      - text: "Нужно делать ботов!"

Состояние состоит из списка (тире - это элемент списка) визуальных элементов, которые выводятся последовательно сверху вниз. Так, в SCR_INTRO первый элемент списка - поле text, содержащее текст, который будет выведен первым. Затем в image находится путь к картинке. Необязательное свойство caption этого раздела выводит текст, прижатый к картинке снизу. Затем перечисляются кнопки, каждая из которых содержит название (button) и состояние state в которое бот перейдёт при нажатии этой кнопки. Порядок всех визуальных элементов можно изменять, но для совместимости с месенджерами рекомендуется кнопки помещать последними, а перед ним должен быть хотя бы один text или image.

По умолчанию, кнопки выводятся вертикально, одна под другой. Для горизонтального расположения, их необходимо "обернуть" объектом row, так как это сделано в состоянии SCR1. Соответственно, таких строчек из кнопок может быть несколько и они могут чередоваться с кнопками в столбик.


Немного о yml-синтаксисе

Логика поведения бота описывается в yaml-файлах. Это легко читаемый формат, но в нём необходимо строго придерживаться выравнивания блоков по вертикали. Чтобы уменьшить число ошибок, стоит для каждого уровня вложенности использовать четыре пробела (такие редакторы как Notepad++ на это настроены по умолчанию). При использовании табуляции важно, чтобы редактор заменял её на пробелы (убедитесь, что Ваш редактор это делает; в Notepad++: Опиции - Настройки - Синтаксисы [x] Заменять пробелом). Если что-то пошло не так, можно проверить синтаксис файла на yamlchecker.com. Ошибки синтаксиса будут также выводиться в отладочной области браузера (см. ниже)

После названия объекта стоит двоеточие, после которого обязательно должен следовать хотя бы один пробел. Например state: SCR_INTRO это пара - ключ state и его значение SCR_INTRO. Если значение не строка, а список, то каждый его элемент начинается с тире. Например, для повышения "читаемости" состояния, кнопки можно перечислить под необязательным объектом buttons:

      - buttons:
          - button: "Кто виноват?"
            state:  SCR1

          - button: "Что делать?"
            state:  SCR2
Значением этого объекта является список (ниже квадратные скобки) из двух элементов. Каждый элемент, в свою очередь является объектом (ниже фигурные скобки), состоящим из двух пар ключ-значение:
buttons: [ { button: "Кто виноват?", state: SCR1}, { button: "Что делать?", state: SCR2} ]
К слову, такой синтаксис в yaml-файле также допустим, так как этот формат является надмножеством JSON.

Строгих правил форматирования разделов (объектов) и списков нет (лишь бы были отступы и выравнивание). Однако, хорошим стилем является сдвиг тире списка на два пробела вправо от начала "имени" этого раздела. Тогда, если элементами списка являются ключи и значения, то они сдвигаются далее табуляцией автоматически на правильную позицию:

states:
◌◌◌◌SOME_STATE:
◌◌◌◌◌◌- image: im/image.png
◌◌◌◌◌◌◌◌caption: "Подпись"
┕━━┙┕━━┙

После имени ключа, перед двоеточием не стоит делать пробел. Тогда этот ключ легко находится поиском в редакторе как строка "SCR_INTRO:" (без двоеточия он может встречаться и в других местах, например в state).

Обратим внимание на двойные кавычки в которые заключены тексты. В yaml в принципе это делать не обязательно. Однако, если текст начинается с тире, скобок [, { или оканчивается двоеточием (что часто бывает в текстах), будет нарушен синтаксис файла. Поэтому хорошей идей является всегда использовать такие кавычки. Хотя в state: SCR1 значение SRC1 это тоже строка, но тут кавычки явно излишни. Решётка # и текст после неё игнорируется (это комментарий). Чем больше используется комментариев, тем легче будет поддерживать проект бота в дальнейшем.


Тестирование

Чтобы протестировать бота локально на своей машине, необходимо скачать архив bbot.zip и распаковать его в любом удобном месте. Затем последовательно проделываются следующие действия:

  1. Проинсталируйте Python с сайта python.org. На первом экране инсталлятора обязательно внизу слева поставьте галку "Add Python to PATH." Если Python у Вас уже стоит, убедитесь, что он версии 3.9 (python --version)
  2. В распакованном архиве bbot.zip запустите файл install.cmd. Он установить необходимые для работы библиотеки.
  3. Создайте в папке static распакованного архива папку с любым именем (например mybots). В эту папку поместите файл с расширением yml (например main1.yml), в который скопируйте код бота с этой страницы. Внимание! - все боты должны находиться в папке static.
  4. Вернитесь в корень и отредактируйте файл bots.yml. В этом файле перечисляются все боты, содержащиеся в папке static. Нужно выбрать идентификатор для своего бота (например mybot1) и описать путь к его папке и имя главного модуля в разделе bots. Кроме этого, в ключе bot надо задать идентификатор активного бота:
    #bots.yml
    bot: mybot1                      # активный бот
    bots:                            # список всех ботов
        mybot1:                      # уникальный идентификатор бота
            main: main1.yml          # имя главного файла бота       
            root: static/mybots/     # путь к проекту
    
  5. Запустите скрипт server_web.pyc (python server_web.pyc или py server_web.pyc) и после этого в браузере введите: http://127.0.0.1:5000/bot.

В браузере слева Вы должны увидеть окно бота. Справа - список значений слотов (переменных). Слоты типа list выводятся только, если они не пустые и для них показывается не более 100 первых элементов. Если произошла ошибка, она будет введена справа от окна чата. На ошибки необходимо обращать внимание и обязательно их устранять. При изменении проекта в редакторе, в строке адреса браузера нажимается Enter, после чего проект перегрузится.

Окно с содержимым текущего состояния можно перемещать по экрану (схватившись за его заголовок) и менять его размер (кружок в правом нижнем углу). При двойном клике по заголовку окно сворачивается и при ещё одном клике разворачивается. Кликая в этом окне на на линки переходов (state, goto, run), можно попасть в другое состояние (это работает только внутри этого окна). Чтобы вернуться в описание текущего состояния, необходимо кликнуть на ссылку в заголовке.

В правом верхнем углу можно поменять значение любого слота. В первое поле редактирования вводится его имя, а во втором - значение.

В строке браузера, после вопросительного знака можно указать имя активного бота следующим образом: http://127.0.0.1:5000/bot?bot=mybot1. Тогда свойство bot в файле bots.yml будет проигнорировано. Это позволяет запускать в разных вкладках браузера одновременно несколько ботов. Примеры подобных линков можно увидеть, запустив http://127.0.0.1:5000.

Проект с ботом можно разбить на несколько файлов, включая их при помощи раздела includes (он может быть также в любом из подключаемых из главного модуля файлов):

includes:                       # config.yml
  - "create_order.yml"
  - "delivery.yml"

Мультиязычность

В отличии от большинства других конструкторов ботов, QuBot позволяет легко создавать мультиязычные интерфейсы, в которых нужный язык переключается "налету". Язык по умолчанию обычно задаётся в стартовом состоянии:

    START_STATE:
      - slots: 
            LANGUAGE: en            # устанавливаем английский язык
      - goto:  SCR_INTRO            # показываем Стартовый экран

В объекте slots предопределённый слот ( = ячейка памяти = переменная) LANGUAGE устанавливается в значение en. После этого происходит переход (goto) на стартовый экран (SCR_INTRO).

Теперь все (или некоторые) тексты можно превратить в объекты: ключ (язык) - значение (текст на нём):

    SCR_INTRO:    
      - text: 
            ru: "Привет, я умный бот 🤖. Могу ответить на любой вопрос."
            en: "Hi, I'm a smart bot 🤖. I can answer any question."
      
      - button: 
            ru: "Кто виноват?"
            en: "Who is to blame?"
        state: SCR1
      
      - button: "Что делать?"
        state: SCR2          

В тех случаях, когда языки не перечислены, или активного языка нет в списке, берётся первый текст. Выше текст и первая кнопка будут на английском языке (благодаря slots в START_STATE), а вторая кнопка ("Что делать?") останется на русском языке (для неё загловки на разных языках не определены).

Чтобы изменить язык интерфейса бота в процессе его работы, необходимо создать соответствующий экран (состояние) и откуда-то его вызвать:

    SCR_CHANGE_LANGUAGE:                         # экран смены языка    
      - text: 
            ru: "На каком языке Вы хотите общаться?"
            en: "What language do you want to communicate in?"
                     
      - button: "English"
        slots: { LANGUAGE: en }  
        state:  PREV_STATE              
                      
      - button: "Русский"
        slots: { LANGUAGE: ru }       
        state:  PREV_STATE
Раздел slots находится "внутри" кнопки. Это означает, что изменение значения слота произойдёт только при нажатии этой кнопки.

Обратим внимание на PREV_STATE. Как и START_STATE это встроенное состояние, которое означает переход на предыдущее состояние из которого мы попали в данное. Можно, конечно, вместо этого указать и переход на любое определённое состояние или состояние, имя которого сохраняется в слоте.


Слоты

В реальных приложениях бот должен обладать памятью и уметь работать с данными. В QuBot все данные хранятся в слотах (переменных), которые являются парами ключ-значение. Ключ - это имя слота. Значение может иметь различные типы (числа, строки, структуры данных). Слоты сначала необходимо объявить в разделе slots (он находится на корневом уровне yaml-файла, там же где и раздел states):

slots:
    NAME:                                   # слот с именем клиента
        type: str                           # его тип - строка
        value: ""                           # начальное значение

Теперь сделаем два состояния. В первом будет запрашиваться имя клиента. При отправки его из поля ввода (input), соответствующая строка записывается в INPUT_VALUE (это встроенный текстовый слот). Этот слот, в разделе slots сохраняется в объявленном выше слоте NAME:

states:
    SCR_YOUR_NAME:
      - text: Как тебя зовут?
      
      - input:                              # свободный ввод
          - slots:  
                NAME: $INPUT_VALUE          # запоминаем ввод
          - state:  GLAD_TO_MEET_YOU        # куда переходим

Теперь слот NAME можно использовать, например, в текстах других состояний, ставя перед его именем знак доллара $, что означает получение значения:

    GLAD_TO_MEET_YOU:
      - text: "Рад знакомству $NAME!"       # будет вставлено имя клиента            
Подробнее работа со слотами рассматривает в следующем документе.


Как разнообразить тексты

В данное состояние пользователь может попадать много раз. Чтобы он не видел один и тот же текст, можно ввести некоторое разнообразие при помощи объектов again и random.

Объявим в разделе слотов целочисленный слот с именем SCR_INTRO_COUNT:


slots:
    SCR_INTRO_COUNT:
        type: int
Пусть состояние SCR_INTRO может быть быть показано несколько раз. Поставим в нём объект again:

state:
    SCR_INTRO:
    
      - again: SCR_INTRO_COUNT
        cases:
            1: 
              - text: "Привет, я умный бот 🤖. Могу ответить на любой вопрос."
            2: 
              - text: "И снова здравствуйте. Я отвечу на любой вопрос."
            more:
              - random:
                  - text: "Я вижу у Вас ещё есть вопросы."
                  - text: "Любые Ваши вопросы нам интересны."
                  - text: "Ура! Снова Вы."
По умолчанию, начальное значение слота SCR_INTRO_COUNT равно нулю. Когда запускается объект again с эти слотом, его значение увеличивается на единицу. Далее ищется это значение в целочисленных ключах (выше числа 1,2) раздела cases и выводится контент из списка этого ключа. В примере, при первом заходе будет выведен текст "Привет, я умный бот 🤖..." При втором заходе в состояние появится тест "И снова здравствуйте..."

Когда значение слота SCR_INTRO_COUNT превысит все целочисленные ключи, выполнится список из раздела more. В этом разделе стоит ещё один объект "разнообразия" random. Он будет равновероятно выбирает один из элементов списка. Таким образом при третьем и далее заходе в состояние SCR_INTRO будет случайно выводится один из приведенных в random текстов.


Переходы между состояниями

Существует три объекта для перемещения между состояниями бота.

Приведём пример с различными переходами. Для работы объекта again объявим слот SCR_MAIN_COUNT:


slots:
    SCR_MAIN_COUNT:
        type: int
В стартовом состоянии START_STATE выведем текст и сделаем "мгновенный" переход goto в состояние SCR_MAIN:
        
states:

    START_STATE:
      - text: "Наш славный бот начинает работу."
      - goto: SCR_MAIN
              
#--------------------------------------------------------------------------
    
    SCR_MAIN:
      - again: SCR_MAIN_COUNT
        cases:
            1:
              - text: "Привет. Как дела?"
            more:
              - goto: SCR_END
          

      - button: "😆 Хорошо"     
        state:  SCR_OK                 # сюда попадут по нажатию этой кнопки

      - run: RUN_BUTTONS
       
      - state: SCR_WHAT_A_PITY             # сюда попадут по кнопкам "Так себе" и "Ужасно"
      
#--------------------------------------------------------------------------

    RUN_BUTTONS:
      - row:
          - button: "😑 Так себе"
          - button: "😰 Ужасно"   

#--------------------------------------------------------------------------
    
    SCR_END:
      - text: "Это конец, больше никуда не попадём."      

В состоянии SCR_MAIN, в объекте again при втором заходе в сработает goto: SCR_END и вместо идущих ниже кнопок, будет выведен финальный текст "Это конец, больше никуда не попадём" из SCR_END (естественно в реальных ботах подобных тупиковых ситуаций необходимо избегать).

Далее в состоянии SCR_MAIN выводится кнопка "😆 Хорошо" с состоянием перехода (при её нажатии) SCR_OK. Затем по run запускается состояние RUN_BUTTONS, которое порождает ещё две кнопки (в строчку) и возвращается обратно.

Состояние перехода по умолчанию SCR_WHAT_A_PITY описано в последней строке состояния SCR_MAIN. Так как кнопки внутри RUN_BUTTONS не содержат state именно оно выполнится при нажатии любой из этих кнопок.

              
    SCR_OK:
    
      - text: "Рад за тебя!"      
      - button: "🚀 В начало "
        state: SCR_MAIN     
    
#--------------------------------------------------------------------------
    
    SCR_WHAT_A_PITY:
    
      - text: "Сочувствую..."      
      - button: "🚀 В начало "
        state: SCR_MAIN      


Ещё немного о кнопках

В кнопках можно выводить текст и другой визуальный контент. Для этого необходимо поставить объект actions:

              
    SOME_STATE:
    
      - button: "🚀 Поехали.."
        answer: "Полетели дальше"
        state:  STATE_FLY
        actions:
          - text: "И он сказал поехали и махнул рукой."
          - image: im/Gagarin.png
Визуальный контент из actions будет выведен перед началом обработки следующего состояния STATE_FLY.
В actions можно перечислять любой контент (например, менять слоты, вызывать условия if или действия action).

Обратим внимание на свойство кнопки answer. По умолчанию, после нажатия на кнопку, все кнопки из окна бота убираются и имитируется ответ человека, текст для которого берётся из заголовка кнопки. Иногда этот текст необходимо изменить, для чего и служит answer.

Для web-чата в одном сообщении можно делать несколько групп кнопок, "перемежая" их другим визуальным контентом:

              
    SCR_MAIN:
    
      - text: "Привет. Как дела?"
      
      - button: "😆 Хорошо"     

      - text: "А может очень хорошо?"
           
      - button:  "😆 Очень хорошо"     

Сначала будет выведена одна кнопка, затем текст и после него вторая кнопка. Впрочем, в некоторых месенджерах все кнопки могут объединиться в одну группу, которая покажется в конце сообщения.


Экраны и сообщения

В состоянии (экране) обычно есть визуальные компоненты (тексты, картинки, кнопки, ...). В месенджерах (telegram, facebook, ...) они посылаются (если это возможно ) в одном сообщении. Если API месенджера это не позволяет, то движок бота сам разобъёт их на отдельные сообщения.

В web-chat (чат бот для сайта) визуальные блоки могут выделяться различным цветом "подложки". Между визуальными блоками могут быть таймерные задержки с различными эффектами (эмуляция набора текста).

Каждый визуальный блок может состоять из компонент с фиксированным порядком и произвольными повторами: текст, картинка, картинка, текст. Ниже состояние состоит из одного блока-сообщения:

  
    SCR_INTRO:   
      - text:  "Привет, я могу ответить на любой вопрос:"
      - image: "im/squirrel.jpg"         
      - text:  "Не забудь нажать на кнопку 😂."
               
      - row:
          - button: "Кто виноват?"
            state:  SCR1            
          - button: "Что делать?"
            state:  SCR2
            
      - button: "Почему 137"
        state:  SCR137

Хотя это один блок, для telegram он будет разбит на три (текст; картинка; текст-кнопки;).

Некоторые месенджеры требуют обязательного сочетания элементов (например, в telegram перед кнопками должен быть текст или картинка).

Перед блоками могут стоять управляющие команды (таймер, стиль,...):
  
    SCR_INTRO:   
      - text: "Как говаривал о чат-ботах Иммануил Кант:"
        
      - block:                              # начало второго блока
            delay: 100                      # перед показом пауза 100 ms
            effect: TYPE                    # в web-chat до показа эмулировать ввод текста
            background: "#eea"              # в web-chat рисовать на жёлтой подложке

      - text: "При разговоре..."     # содержимое второго блока.
      ...
Выше явным образом объявлен второй блок. Его настройки действуют до следующего блока. Все параметры block не обязательны, т.к. есть значения по умолчанию:
  
{ delay: 0, effect: None, background: bot["background"] }

Для форматирования текста используются html-теги: <b>Жирный шрифт</b>, <i>Наклонный шрифт</i>.


Пиктограммы

В любые тексты (например, надписи на кнопках) можно включать пиктограммы, которые являются символами unicode. Они есть на различных сайтах, например здесь (поиск по ключевым словам), или здесь (по категориям). Ниже приводятся некоторые полезные пиктограммы, которые можно скопировать прямо со страницы и вставить в текст сообщения бота:

Да: ✔️ 👌 👍   On: ● ☑️ ✅ ❎ 🔘   Назад: ◁ △ ◀️ 🔺
Нет: ❌ ⛔ 👎   Off: ○ 🟦 🟩 ⚪   Вперёд: ▷ ▽ ▶️ 🔻
В начало: 🚀 🏠   Вернуться:   ↻ 🔙 ↩️ ⬆️   Арифметика: ➕ ➖ ✖️ ➗
Часы работы:   Телефон: 📞   Почта: 💻   Расположение: 🗺
Услуги: 🔑   Цены: 💸 💲   Совет: 🦉 ❓   Информация: ℹ️
Смена языка: 🌐   Работа: 👫   Настройки: ⚙️ 🛠️

Разное:

Следует иметь в виду, что в различных операционных системах unicode-пиктограммы могут отличаться. Кроме этого, на телефонах (Android, iOS) некоторые пиктограммы не отображаются. Чтобы выяснить это, можно с телефона через браузер зайти на один из сайтов с пиктограммами. Те, что не видны, будут проблемными и в месенджере.


Что дальше

Следующий документ подробнее описывает работу со слотами (памятью бота) и со встроенными действиями, которые проводят с этими слотами различные операции.

Все примеры этого документа находятся в проекте http://127.0.0.1:5000/bot?bot=example_hello, входящем в архив bbot.zip.