Раз, Два, Три! Чат-бот из Google Таблицы на примере PvP-игры для Алисы


    Появившись, Алиса увлекла пользователей неожиданно качественными синтезом речи и чат-ботом. Сегодня от неё ждут полезных навыков и интересных игр с бекендом, способным учитывать контекст пользователя и реализовывать широкий спектр сценариев. В этой статье рассматривается создание навыка на базе Google Таблицы, хорошо знакомого многим инструмента с большим потенциалом для небольших чат-ботов.

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

    Раз! Диалог


    Игра начинается с правил. Я придумал такие: каждый из двух игроков сперва размещает за тремя дверями сокровище и ловушку, а затем открывает любую из дверей соперника. Открываете сокровище — получаете монеты соперника, открываете ловушку — отдаёте монеты ему. Количество монет, от 1 до 3, определяет сам игрок. За оставшейся дверью находится ящик Пандоры, открыв который можно найти/потерять случайное количество монет. Играть можно как с Алисой, так и против других пользователей.

    Интерфейс Алисы решён в виде диалога, и всё игровое взаимодействие должно быть реализовано через обмен сообщениями. Обработка каждого сообщения сервером игры представляется следующими шагами:

    1. восстановление контекста пользователя;
    2. интерпретация запроса в восстановленном контексте;
    3. формирование ответного сообщения;
    4. сохранение изменившегося контекста пользователя.

    Восстановление и сохранение контекста пользователя


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

    В каждом запросе, согласно протоколу, Алиса передаёт идентификатор пользователя. Этого достаточно для сохранения и последующего восстановления его контекста.

    В качестве хранилища данных возьмём Google Таблицы. К объективным преимуществам этого решения относятся бесплатное использование, наглядность и простота эксплуатации. Встроенный редактор скриптов позволяет описывать логику игры на Apps Script (базирующемся на JavaScript), обращаясь к API таблиц, и публиковать её в виде web-приложения.

    Создав таблицу с нужными заголовками, можно перейти к редактору скриптов:


    Логику игры можно описать в проекте на Apps Script, организовав её в виде набора gs-файлов, и перейти к публикации:


    При публикации нужно указать доступность приложения анонимным пользователям:


    На выходе вы получите URL опубликованного веб-приложения. Функции doGet() и doPost() в скрипте будут обрабатывать запросы соответствующих типов, чтобы получать и сохранять контексты пользователей.

    Ниже схема работы с API таблиц для оперирования данными:

        //получение листа
        var sheet = SpreadsheetApp.openById("<id таблицы>").getSheetByName("<название вкладки>");
       //получение диапазона
        var range = sheet.getRange(<нужные ячейки>);
       //извлечение данных диапазона
        var values = range.getValues();
       //сохранение данных в диапазон
        range.setValues(<нужные данные>);
    

    Интерпретация запроса в восстановленном контексте


    В общем случае корректная интерпретация текстовых запросов требует использования интеллектуальных NLU-алгоритмов. И хотя такие алгоритмы доступны в несложных инструментах вроде описанного мной Aimylogic, в этом случае от обработки естественного языка я решил отказаться в пользу простоты.

    В предложенной игре взаимодействие игрока с Алисой ограничивается десятком возможных состояний и может быть сведено к небольшому набору намерений. Для простоты я всегда предлагаю игроку три возможных действия: отправить “Раз”, “Два” или “Три”. На любой другой запрос Алиса просит уточнить действие.

    Сценарий в этом случае сводится к следующему коду на Apps Script:

    //для каждого состояния в игре
    switch(user.state){
        case "start":
          //прописываем реакцию на каждое из возможных действий
          switch(utterance){
            case “1️⃣ Раз”:
            case “2️⃣ Два”:
            case “3️⃣ Три”:
              //формируем ответ на ожидаемые действия
            default:
              //формируем ответ на неожиданное действие
          }
          break;
       //здесь все другие состояния
        default:
         //формируем ответ на случай непредвиденного состояния
    }
    

    Для пользователя решённое таким образом взаимодействие представляется как выбор одного из трёх вариантов, смысл которых явно определён в каждом ответе Алисы. Сами варианты представлены кнопками “Раз”, “Два”, “Три”, которые существенно ускоряют игровой процесс.

    Формирование ответного сообщения


    Ответ Алисы состоит из нескольких частей, каждую из которых нужно сформировать, в том числе:

    • текст для отображения на экране;
    • текст для синтеза речи;
    • набор кнопок-подсказок.

    Недавно я сформулировал концепцию ЛЁГКОГО диалога, описывающую принципы проектирования разговорного интерфейса в том числе для Алисы. Формат ответа Алисы позволяет реализовать эти принципы.

    Так разделение отображаемого и проговариваемого текста позволяет сделать ответы более краткими и естественными. Можно не заставлять Алису синтезировать длинный текст, с которым пользователь уже знаком, а также использовать emoji в тексте сообщения и названии кнопок.

    Кнопки-подсказки реализуют принцип инициативы: Алиса всегда обозначает и предлагает возможные действия для продолжения диалога. В сценарии предложенной игры список кнопок не зависит от контекста и добавляется к каждому сообщению.

    Итак, у нас есть Google Таблица с данными пользователей, которые сохраняются и получаются через URL веб-приложения. Приложение написано на Apps Script, оно интерпретирует действие пользователя в соответствии с его контекстом и формирует данные для ответного сообщения.

    Осталось подключиться к Алисе…

    Два! Интеграция


    Яндекс.Диалоги позволяют разработчикам добавлять в Алису свои навыки. Подключение навыка сводится к трём основным вещам:

    • активация;
    • оформление;
    • веб-хук.

    Активация и оформление


    Для активации важно выбрать хорошо распознаваемую Алисой фразу, соответствующую формальным требованиям Яндекса. Если в активационном имени присутствуют числа, отдельно проверяйте активацию при вводе с клавиатуры и голосом.

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

    Получение веб-хука


    Веб-хук — это адрес, по которому Алиса будет отправлять сообщения к вашему навыку и ждать JSON-ответ в описанном формате.



    Веб-приложение, созданное на Apps Script, по умолчанию возвращает ответ в виде html-странички, но с помощью ContentService его можно заставить возвращать и JSON:

    return ContentService.createTextOutput(JSON.stringify(<JSON ответа>))
      .setMimeType(ContentService.MimeType.JSON);

    Однако Google при использовании ContentService перенаправляет запрос пользователя на временный URL, к чему Яндекс.Диалоги не готовы. Поэтому адрес веб-приложения в качестве веб-хука не подходит.

    Существуют бесплатные сервисы, которые предлагают подходящий для Алисы веб-хук, например Zenbot. В Zenbot для предложенной игры можно написать короткий скрипт, обращающийся к гугловому веб-приложению и возвращающий ответ вместе с кнопками. Кстати, таким образом игру можно интегрировать не только с Алисой, но и с другими каналами.

    Ниже пример скрипта, который обеспечивает работу игры “Раз, Два, Три!” в Telegram-боте @RazDvaTriBot:

    <context>
    
        <input pattern="$Text">
            <var name="Utterance" value="$Text" scope="input"/>
            <get url="https://script.google.com/macros/s/<id веб-приложения>/exec" var="Result">
                <param name="userId" value="$req_telegram_chat"/>
                <param name="utterance" value="$Utterance"/>
                <param name="channel" value="telegram"/>                  
            </get>  
            <var name="Answer" value='javascript: $Result.text'/>
            <output value="$Answer"/>
            <sample>
                <item value="Раз"/>
                <item value="Два"/>
                <item value="Три"/>
            </sample>      
        </input>
    
    </context>
    

    Для большей гибкости в обработке запросов можно написать свой сервер, используя для этого, например, Google App Engine. Этим инструментом тоже можно пользоваться бесплатно.

    После создания проекта в Google App Engine интерфейс Cloud Shell позволяет на одной веб-странице писать код сервера и деплоить его на нужный URL вида https://<id проекта>.appspot.com, который и будет адресом веб-хука.

    Работа сервера состоит из следующих этапов:

    1. получение данных запроса из Алисы;
    2. взаимодействие с веб-приложением игры;
    3. отправка ответа в формате Алисы.

    Получение/отправка данных Алисы


    От Алисы важно получить идентификаторы сессии, пользователя, сообщения, а также текст запроса. Ниже пример на PHP:

    	$data = json_decode(file_get_contents("php://input"));
    
    	$session_id = $data->session->session_id;
    	$user_id = $data->session->user_id;
    	$utterance = $data->request->original_utterance;
    	$messageId = $data->session->message_id;
    

    В качестве ответа в игру возвращаются кнопки-подсказки и тексты для отображения и проговаривания:

    	$button1 = array('title' => '1️⃣ Раз', 'hide' => true);
    	$button2 = array('title' => '2️⃣ Два', 'hide' => true);
    	$button3 = array('title' => '3️⃣ Три', 'hide' => true);
    	$yaButtons = array($button1, $button2, $button3);
    	$yaResponse = array('text' => $text, 'tts' => $tts, 'buttons' => $yaButtons, 'end_session' => false);
    	$yaSession = array('session_id' => $session_id, 'message_id' => $messageId, 'user_id' => $user_id);
    	$yaResult = array('response' => $yaResponse, 'session' => $yaSession, 'version' => '1.0');
    	
    	echo json_encode($yaResult);
    

    Три! Синхронизация


    Отправка/получение данных от веб-приложения занимает время, а Алисе не терпится дать ответ пользователю, поэтому в дело вмешиваются вопросы синхронизации.

    Естественно, для больших проектов в качестве бекенда навыка Алисы Google Таблицы не подходят: время ответа увеличивается при большом количестве параллельных запросов. Тем не менее существуют рекомендации по оптимизации чат-бота, которые позволяют сделать небольшой проект жизнеспособным в условиях диалоговой системы реального времени.

    Таймаут синхронного протокола Алисы — 1,5 секунды на ответ. Если сервер не успевает выдать ответ за это время, пользователь видит грустное сообщение в духе “Извините, <имя диалога> не отвечает”. Никаких подсказок, что делать дальше, система не предлагает.

    Чтобы избежать такой ситуации, можно и нужно ускорять сервис, а также отслеживать и обрабатывать таймауты. Наиболее длительные операции при работе скрипта игры — чтение и запись данных таблицы. Поэтому, во-первых, количество эти операций нужно минимизировать, а во-вторых, желательно их распараллелить.

    Зачитать данные таблицы достаточно один раз. После исполнения логики скрипт готов выдать пользователю ответ сразу, до окончания записи результатов.

    На получение ответа следует отвести ограниченное время — например, 1100 мс:

    	$request_params = array(
    	  'userId' => $user_id, 
    	  'utterance' => $utterance,
    	  'channel' => 'alice'
    	); 
    	
    	$get_params = http_build_query($request_params);
    	
    	$ctx = stream_context_create(array('http'=>
    	    array(
    	        'timeout' => 1.1
    	    )
    	));
    	
    	$answer = file_get_contents('https://script.google.com/macros/s/<id веб-приложения>/exec?'. $get_params, false, $ctx);
    

    Если ответ GET-запросом получен вовремя, мы можем отдать его пользователю и инициировать сохранение результатов POST-запросом в фоновом режиме. В противном случае мы отдаём пользователю текст-заглушку с кнопками для продолжения, а результаты исполнения скрипта игнорируем, чтобы пользователь мог повторить свой запрос в текущем контексте.

    	if($answer === FALSE) {
    		$text = ' Помедленнее, я не успеваю.';
    		$tts = 'Помедленнее, я не успеваю.';
    	} else {
    		
    	    $answer = json_decode($answer);
                $text = $answer->text;
    	    $tts = $answer->tts;
    	
    	    //сохранение в таблице
    	    $data2store = $answer->data;
                $postdata = json_encode(array(
    	          'data' => $data2store
    	      ));
    	   $opts = array('http' =>
    	      array(
    	          'method'  => 'POST',
    	          'header'  => 'Content-Type: application/json',
    	          'content' => $postdata,
    	          'timeout' => 0.1
    	      )
    	  );
    	
              $context  = stream_context_create($opts);
    	  file_get_contents("https://script.google.com/macros/s/<id веб-приложения>/exec", false, $context);
    	}
    


    В многопользовательской игре для Алисы должны быть решены задачи синхронизации как между серверами игры и Яндекса, так и между игроками. Если игрок хочет играть против другого пользователя, игра сама подбирает соперника — из тех, кто хотел играть в последнее время. Пользователи должны подтвердить готовность играть друг с другом, чтобы игра началась.

    На текущий момент навык не может инициировать отправку сообщения пользователю Алисы. Поэтому в скрипте игры предусмотрена проверка готовности соперника и отведённой под раунд игры минуты. Если соперник задерживает игру, пользователю предлагается подождать его: “Подождём соперника ещё чуть-чуть?” Соглашаясь подождать, пользователь запускает очередную проверку. Если минута игры заканчивается, игра завершается.

    Заключение


    К плюсам Google Таблицы как бекенда для чат-бота, кроме бесплатности, можно отнести то, что при разработке она является инструментом отладки, а после — становится консолью администратора навыка со всеми прелестями совместного редактирования с любого устройства. К минусам — задержки при одновременной работе большого количества пользователей.

    Надеюсь, данная статья поможет энтузиастам и разработчикам быстрее создавать полезные навыки для Алисы или других каналов. Предложенная игра доступна в магазине навыков Яндекс.Диалогов под названием “Раз, Два, Три! Многопользовательская игра”.

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

    Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

    Какие инструменты для разработки чат-ботов вы пробовали?
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 9
    • +1

      Непонятно зачем использовать Google Docs совместно с AppEngine если с ним также бесплатно можно использовать Google Datastore. И никаких проблем с количеством пользователей.


      Я делаю бот-игры на Go с использованием https://github.com/strongo/bots-framework

      • 0
        бесплатно можно использовать Google Datastore

        Там триалка на год, далее стандартный пакет уже за денежку.
        cloud.google.com/terms/free-trial/?hl=ru
        4. Conclusion of the Free Trial.

        4.1 When the Free Trial ends, Customer will no longer have access to the Services and the Free Trial Terms and Conditions will no longer apply. During the 30 day period following the conclusion of the Free Trial, Customer may contact Google support (i) to migrate Customer Data from the Services (other than Google Compute Engine); or (ii) to upgrade to a standard Google Cloud Platform account.
        • 0
          Free tier вроде как не связан с Free Trial:
          cloud.google.com/free

          Еще посмотри через Firebase: там другие тарифы, вполне подходит для многих кейсов.
          firebase.google.com/pricing
          • 0
            Подскажите, пожалуйста, что значит выражение «Free tier»? Не получается найти нормальное определение.
            • 0
              Бесплатный уровень [сервиса] => бесплатное обслуживание (как вода в парковом фонтанчике)
        • 0
          Как хранилище Google Datastore в общем случае выигрывает у Google Docs по многим параметрам. Но если данных немного, а скорость доступа не играет ключевой роли, то преимуществом может быть удобство администрирования. Google Docs в этом смысле хороший вариант для всех, кто привык работать с данными в Google Таблицах.
        • 0
          Недавно тоже писал навык для Алисы, сейчас он на модерации) У меня вопрос по Вашему коду:
          зачем вы делаете вот так?
          'message_id' => $messageId + 1
          и вот так:
          'version' => '1.0'
          Ведь Яндекс сам прибавляет число к ID сообщения и не возникнут ли у Вас в дальнейшем проблемы с тем, что вы хардкорите версию? Ведь она также передается в запросе и вероятно, может измениться в дальнейшем, а у Вас она статичная) А за статью большое спасибо, узнал много нового для себя
          • 0
            Спасибо за положительный отзыв. Вы правы, идентификатор сообщения следует просто пробрасывать из запроса — исправил.

            Версия протокола, полагаю, должна прежде всего соответствовать формату ответа — если аналогично передавать её из запроса, может возникнуть несоответствие.
            • 0
              Я например у себя передаю вот так
              "version" => $apiRequestArray['version']
              а json_encode уже сам заботится о формате представления) Но практика покажет, правильно это или нет) Еще раз спасибо)

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое