Не так давно в Санкт-Петербурге прошла вторая конференция Conversations, посвящённая разговорному AI, на которой мне посчастливилось выступить в качестве докладчика. Темой была разработка прототипа B2B-навыка для крупной компании. В докладе рассказывалось о том, как удалось «подружить» навык с относительно медленными веб-сервисами и закрытой инфраструктурой компании. Об этом и пойдёт речь под катом.

Если вдруг вы не знаете, что такое навыки Алисы, загляните под спойлер: там кратко описано, что к чему.

Для непосвящённых
Что такое Кто такая Алиса, думаю, многие знают. Но на всякий случай – это голосовой помощник от Яндекса. Помимо того, что она многое умеет делать «из коробки», в распоряжении разработчиков есть платформа для расширения её функциональных возможностей – Яндекс.Диалоги (они же навыки Алисы).

С точки зрения пользователя, навык — это специальный режим Алисы, который вызывается определенными активационными фразами. В этом режиме Алиса передаёт реплики пользователя на сторонний веб-сервис, и отвечает переданным в ответ сообщением.

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

Идея


С чего же всё началось? 13го марта 2018 года объявили о бета-тестировании платформы Яндекс.Диалоги (навыки Алисы). В то время уже многие интересовались виртуальным помощником, а значит это была отличная возможность поработать с достаточно большой аудиторией. У меня в голове давно вертелась идея одного чат-бота, поэтому я решил, что будет интересно сделать по его мотивам какой-нибудь навык в свободное время. А если он сможет к тому же принести пользу на работе – будет вообще отлично.

Наша компания предоставляет полный спектр услуг в области делового туризма, а значит можно сделать навык, который поможет пользователю поехать в командировку.
Тогда я состоял в команде по разработке мобильного приложения, с помощью которого можно найти варианты рейсов и отелей для командировки, и оформить подходящие из них. Одним из ключевых показателей, за увеличение которого мы боролись, являлось количество скачиваний. Возникла идея того, что, если навык будет приводить пользователей в приложение, то он поможет нам увеличить эти показатели. Это и нужно было проверить с помощью данного проекта.

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

Получается, что навык должен работать примерно так: поприветствовать пользователя; найти его по ФИО; задать уточняющие вопросы и, таким образом, получить необходимые параметры поездки: города (откуда и куда) и даты. Далее показать распознанные параметры. Если всё правильно, запустить поиск, и дать ссылку на приложение.

Ресурсы и ограничения


Для выполнения своей задачи навыку надо взаимодействовать с нашими внутренними API, а ещё его веб-сервис надо где-то опубликовать. С одной стороны, его можно было бы разместить на работе, но, как уже упоминал, разработка велась в свободное время, потому не хотелось зависеть от каких-то специально выделенных ресурсов компании. Значит, нужно было воспользоваться тем, что доступно по умолчанию.

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

Веб-сервис навыка обязательно должен быть доступен извне (чтоб Алисе было куда отправлять запросы), поэтому его пришлось разместить на внешнем хостинге.

Чтобы навык мог выполнить свою задачу, нужен такой веб-сервис компании, который умел бы искать профили и города, и был бы доступен извне. Для этого подойдёт API мобильного приложения, хотя у него есть свои нюансы. Они заключаются в том, что подключиться к API можно от имени лишь одного определённого пользователя, а значит будет ограничен круг доступных для поиска профилей. А самое неприятное — результаты поиска, запущенного через API, будут приходить только этому пользователю. Тем не менее, оно обладает необходимым функционалом, а значит с ним можно работать.

Итак, навык на внешнем хостинге будет взаимодействовать с API. Оно конечно же достаточно быстрое, но иногда, по результатам тестов, ответ не успевает прийти за нужные 1500 мс (таково требованием платформы Яндекс.Диалоги). А для того, чтобы всё-таки прислать результаты нужному пользователю, надо от его имени запустить службу поиска, которая доступна только во внутренней сети. API, к сожалению, в этом не поможет, а значит надо каким-то образом передать запрос от навыка непосредственно во внутреннюю инфраструктуру.
Будем решать эти проблемы по мере поступления.

Этапы. Проблемы и решения


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

Выбор пал на Redis. Он хорошо показал себя в тестах на отклик, а также мы плотно используем его на работе, а значит, в случае успеха, этот проект можно будет легко перенести в компанию (и спойлер – мы его перенесли). В качестве ключа можно использовать идентификатор пользователя в навыке (указывается в запросе), а в значении хранить данные состояния в формате JSON. Бесплатный экземпляр Redis можно развернуть на Heroku, а с некоторых пор он поддерживается и в Яндекс.Облаке.

Теперь подробнее разберём этапы навыка. При самом первом запуске пользователь видит обычную приветственную фразу. Далее он должен назвать свои ФИО, по которым навык будет искать профиль.



Если он найдётся, то в состояние следует записать его имя, и, раз уж используется кэш, то и остальную необходимую информацию о профиле можно положить в него. Теперь, когда клиент снова вернётся в навык, он увидит персональное приветствие. Если этот же человек зайдёт с другого устройства и назовёт свои ФИО, его профиль также будет найден в кэше, а значит избегаем повторного поиска через API, что экономит время на обработку запроса.

Далее происходит получение параметров поездки. Я, как пользователь голосового навыка, хочу называть города и даты как мне хочется, например, «Питер», и «через неделю». Навык должен уметь распознавать такие фразы, чтобы передать полное название города в API и выполнить поиск на нужный день. Сейчас веб-сервис навыка сразу получает эту информацию непосредственно в запросе:



Но такая фича появилась примерно в октябре 2018 года, а навык разрабатывался чуть раньше, поэтому для понимания естественного языка был выбран Dialogflow. В нём есть отличная система разметки, и периодически можно приходить обучать его, указывая, что в той или иной фразе пользователь имел ввиду.

Итак, клиент по-своему называет город и дату, навык передаёт его слова в Dialogflow, и отправляет распознанное название города в API, откуда уже получает необходимый идентификатор. Цепочка длинная и потому снова велика вероятность не уложиться в требуемые 1500 мс.

Очевидный выход — кэшировать. Причём в качестве ключа можно указывать именно то, что сказал пользователь, а в значении хранить идентификатор города из нашей системы. Тогда в кэше может быть несколько записей для одного города, например для слов «Питер» и «Санкт-Петербург». Но это не критично, если в значении указано не слишком много информации. В любом случае, такой подход позволит наполнить кэш популярными городами, которые запрашивали другие пользователи, или «прогреть» его заранее. Это позволит в дальнейшем реже обращаться к Dialogflow и API, что снова сэкономит время.

Самый интересный этап – это запуск поиска. Все необходимые параметры есть, но, чтобы результаты пришли нужному человеку, надо каким-то образом «дернуть» внутреннюю службу поиска. Кроме того, сам по себе поиск выполняется достаточно долго, а длительные операции лучше выполнять не в том же веб-сервисе, а в отдельном приложении.

Пришла пора воспользоваться доступным сервером компании. На нём можно развернуть приложение, которое будет каким-то образом «забирать» информацию извне и выполнять длительные задачи, в том числе запускать поиск.

Таким приложением вполне может стать фоновая служба.

Из названия понятно, что это приложение без UI, которое должно начинать свою работу вместе с запуском сервера и выполнять запланированные действия, или действия по определённой команде (сообщению). Такую службу мы обычно организуем на фреймворке Topshelf, а команды она может получать, например, из очереди сообщений, основанной на протоколе AMQP.

Если кратко, то очередь работает примерно так: есть брокер, в который отправители добавляют сообщения определённого типа. И есть читатели, которые подключаются к брокеру и получают нужную информацию.
Более подробное описание можно найти, например, в этой статье.

В интернете нашлось хорошее облачное решение, предоставляющее очередь сообщений как сервис – CloudAMQP. У него есть бесплатный тариф, но работает стабильно. Ещё одним аргументом для его выбора является то, что данный сервис работает на основе RabbitMQ, кот��рый мы также плотно используем на работе.

Итак, взглянем на работу навыка в целом: веб-сервис навыка взаимодействует с API мобильного приложения и Dialogflow. Результаты обращений к ним кэшируются в Redis, и там же хранится состояние. После подтверждения параметров поездки навык передаёт брокеру сообщение со всей необходимой информацией. Фоновая служба на тестовом сервере подключается к нему, и при появлении сообщения запускает поиск, а результаты отправляются в мобильное приложение.


Когда клиент скачает и установит его, то найдёт их в своих запросах:



На этом работа навыка завершается.

Итоги


Что же было дальше? Данный навык был показан нескольким клиентам для получения обратной связи, и вот что мы выяснили: сами по себе пользователи неохотно переходят на мобильное приложение, каким бы классным оно ни было. Некоторым из них проще позвонить по телефону нашему агенту и попросить его поискать то, что нужно.

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

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

Хотелось бы сделать акцент на некоторых выводах. Очевидный: чтобы успевать в 1500 мс, избегайте выполнения лишних запросов к веб-сервисам, кэшируйте. Для одной и той же информации можно использовать разные ключи кэша. Это оправдано, если хотя бы один человек попадёт в кэш, сформированный другим пользователем. И самое главное: лучше выполнять длительные операции в отдельной фоновой службе: кроме того, что она даёт децентрализацию навыка, в ней будет меньше проблем с многопоточностью, и при необходимости её можно «развернуть» внутри закрытой сети компании и «забирать» сообщения извне.

Вместо эпилога


Чат-боты и навыки часто пишут на JavaScript и Python (судя по кол-ву репозиториев на GitHub по запросу «chatbot»). Это происходит в том числе из-за лёгкой публикации на сервера. Данный проект был написан на C# под .net core. В случае классического .net framework есть определённые трудности с публикацией (работает в основном под Windows, и т.п.), но с появлением .net core многое изменилось. Для каждого упомянутого выше сервиса или фреймворка есть библиотеки, которые полностью поддерживают данную технологию. Благодаря этому навык потенциально можно запустить на линуксовых серверах, и уж тем более на любом хостинге, поддерживающем Docker. Если вдруг вы находитесь в творческом поиске, рекомендую обратить внимание на этот фреймворк, он становится хорошей альтернативой для разработки чат-ботов.

P.S.
UPD 01.08.2019: с сегодняшнего дня таймаут для навыков — 3 секунды.