Появившись, Алиса увлекла пользователей неожиданно качественными синтезом речи и чат-ботом. Сегодня от неё ждут полезных навыков и интересных игр с бекендом, способным учитывать контекст пользователя и реализовывать широкий спектр сценариев. В этой статье рассматривается создание навыка на базе Google Таблицы, хорошо знакомого многим инструмента с большим потенциалом для небольших чат-ботов.
Играть с реальным человеком может быть интереснее, чем с виртуальным персонажем, поэтому в качестве примера разрабатывать будем многопользовательскую игру для Алисы.
Раз! Диалог
Игра начинается с правил. Я придумал такие: каждый из двух игроков сперва размещает за тремя дверями сокровище и ловушку, а затем открывает любую из дверей соперника. Открываете сокровище — получаете монеты соперника, открываете ловушку — отдаёте монеты ему. Количество монет, от 1 до 3, определяет сам игрок. За оставшейся дверью находится ящик Пандоры, открыв который можно найти/потерять случайное количество монет. Играть можно как с Алисой, так и против других пользователей.
Интерфейс Алисы решён в виде диалога, и всё игровое взаимодействие должно быть реализовано через обмен сообщениями. Обработка каждого сообщения сервером игры представляется следующими шагами:
- восстановление контекста пользователя;
- интерпретация запроса в восстановленном контексте;
- формирование ответного сообщения;
- сохранение изменившегося контекста пользователя.
Восстановление и сохранение контекста пользователя
Контекст пользователя включает его состояние в игре, в том числе предыдущие результаты, шаг внутри сценария и текущего соперника, а также имя пользователя и прочую информацию, необходимую для игровой логики.
В каждом запросе, согласно протоколу, Алиса передаёт идентификатор пользователя. Этого достаточно для сохранения и последующего восстановления его контекста.
В качестве хранилища данных возьмём 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, который и будет адресом веб-хука.
Работа сервера состоит из следующих этапов:
- получение данных запроса из Алисы;
- взаимодействие с веб-приложением игры;
- отправка ответа в формате Алисы.
Получение/отправка данных Алисы
От Алисы важно получить идентификаторы сессии, пользователя, сообщения, а также текст запроса. Ниже пример на 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 + 1, '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 Таблицы как бекенда для чат-бота, кроме бесплатности, можно отнести то, что при разработке она является инструментом отладки, а после — становится консолью администратора навыка со всеми прелестями совместного редактирования с любого устройства. К минусам — задержки при одновременной работе большого количества пользователей.
Надеюсь, данная статья поможет энтузиастам и разработчикам быстрее создавать полезные навыки для Алисы или других каналов. Предложенная игра доступна в магазине навыков Яндекс.Диалогов под названием “Раз, Два, Три! Многопользовательская игра”.
Наряду с инструментами общего назначения существуют и специализированные решения для разработки чат-ботов. Предлагаю читателям принять участие в небольшом опросе по известным мне в этой области сервисам. В список попали далеко не все существующие продукты — буду благодарен за комментарии с названиями и краткими отзывами, если вы использовали другие инструменты.
Автор: IvanGolubev