![](https://habrastorage.org/webt/ph/px/y4/phpxy4hejwhufi2t-uk7jsx-f4w.png)
Не так давно в Санкт-Петербурге прошла вторая конференция Conversations, посвящённая разговорному AI, на которой мне посчастливилось выступить в качестве докладчика. Темой была разработка прототипа B2B-навыка для крупной компании. В докладе рассказывалось о том, как удалось «подружить» навык с относительно медленными веб-сервисами и закрытой инфраструктурой компании. Об этом и пойдёт речь под катом.
Если вдруг вы не знаете, что такое навыки Алисы, загляните под спойлер: там кратко описано, что к чему.
Для непосвящённыхЧто такое Кто такая Алиса, думаю, многие знают. Но на всякий случай – это голосовой помощник от Яндекса. Помимо того, что она многое умеет делать «из коробки», в распоряжении разработчиков есть платформа для расширения её функциональных возможностей – Яндекс.Диалоги (они же навыки Алисы).
С точки зрения пользователя, навык — это специальный режим Алисы, который вызывается определенными активационными фразами. В этом режиме Алиса передаёт реплики пользователя на сторонний веб-сервис, и отвечает переданным в ответ сообщением.
С технической точки зрения, навык — это тот самый сторонний веб-сервис, который должен принимать запросы, содержащие реплики пользователей. Его ответы могут содержать текст, ссылки, картинки, звуки, и т.д.
С точки зрения пользователя, навык — это специальный режим Алисы, который вызывается определенными активационными фразами. В этом режиме Алиса передаёт реплики пользователя на сторонний веб-сервис, и отвечает переданным в ответ сообщением.
С технической точки зрения, навык — это тот самый сторонний веб-сервис, который должен принимать запросы, содержащие реплики пользователей. Его ответы могут содержать текст, ссылки, картинки, звуки, и т.д.
Идея
С чего же всё началось? 13го марта 2018 года объявили о бета-тестировании платформы Яндекс.Диалоги (навыки Алисы). В то время уже многие интересовались виртуальным помощником, а значит это была отличная возможность поработать с достаточно большой аудиторией. У меня в голове давно вертелась идея одного чат-бота, поэтому я решил, что будет интересно сделать по его мотивам какой-нибудь навык в свободное время. А если он сможет к тому же принести пользу на работе – будет вообще отлично.
Наша компания предоставляет полный спектр услуг в области делового туризма, а значит можно сделать навык, который поможет пользователю поехать в командировку.
Тогда я состоял в команде по разработке мобильного приложения, с помощью которого можно найти варианты рейсов и отелей для командировки, и оформить подходящие из них. Одним из ключевых показателей, за увеличение которого мы боролись, являлось количество скачиваний. Возникла идея того, что, если навык будет приводить пользователей в приложение, то он поможет нам увеличить эти показатели. Это и нужно было проверить с помощью данного проекта.
Чтобы навык был хоть кому-то полезен, он должен решать определённую задачу пользователя. В данном случае — осуществить поиск вариантов для командировки. То есть навык должен собрать информацию о том, куда надо поехать, а результаты показать в мобильном приложении. Таким образом, пользователь получит желаемые варианты с помощью интересного голосового интерактива и продолжит работу в нашем приложении, а значит разработчики получат желаемый прирост к показателям.
Получается, что навык должен работать примерно так: поприветствовать пользователя; найти его по ФИО; задать уточняющие вопросы и, таким образом, получить необходимые параметры поездки: города (откуда и куда) и даты. Далее показать распознанные параметры. Если всё правильно, запустить поиск, и дать ссылку на приложение.
Ресурсы и ограничения
Для выполнения своей задачи навыку надо взаимодействовать с нашими внутренними API, а ещё его веб-сервис надо где-то опубликовать. С одной стороны, его можно было бы разместить на работе, но, как уже упоминал, разработка велась в свободное время, потому не хотелось зависеть от каких-то специально выделенных ресурсов компании. Значит, нужно было воспользоваться тем, что доступно по умолчанию.
Например, тестовый сервер. У разработчиков хватает прав, чтобы развернуть на нём веб-приложение, но оно будет доступно только во внутренней сети компании, потому что сервер не «торчит» наружу. При этом у него есть доступ в интернет, а значит этим можно будет воспользоваться.
Веб-сервис навыка обязательно должен быть доступен извне (чтоб Алисе было куда отправлять запросы), поэтому его пришлось разместить на внешнем хостинге.
Чтобы навык мог выполнить свою задачу, нужен такой веб-сервис компании, который умел бы искать профили и города, и был бы доступен извне. Для этого подойдёт API мобильного приложения, хотя у него есть свои нюансы. Они заключаются в том, что подключиться к API можно от имени лишь одного определённого пользователя, а значит будет ограничен круг доступных для поиска профилей. А самое неприятное — результаты поиска, запущенного через API, будут приходить только этому пользователю. Тем не менее, оно обладает необходимым функционалом, а значит с ним можно работать.
Итак, навык на внешнем хостинге будет взаимодействовать с API. Оно конечно же достаточно быстрое, но иногда, по результатам тестов, ответ не успевает прийти за нужные 1500 мс (таково требованием платформы Яндекс.Диалоги). А для того, чтобы всё-таки прислать результаты нужному пользователю, надо от его имени запустить службу поиска, которая доступна только во внутренней сети. API, к сожалению, в этом не поможет, а значит надо каким-то образом передать запрос от навыка непосредственно во внутреннюю инфраструктуру.
Будем решать эти проблемы по мере поступления.
Этапы. Проблемы и решения
Для начала, чтобы вообще реализовать описанный сценарий, навыку надо где-то хранить состояние: этап, имя пользователя, города, и даты. Информации не так много, потому не стоит разворачивать под неё целую базу данных, тем более что с ней слишком много возни. Состояние вполне можно хранить в кэше.
Выбор пал на Redis. Он хорошо показал себя в тестах на отклик, а также мы плотно используем его на работе, а значит, в случае успеха, этот проект можно будет легко перенести в компанию (и спойлер – мы его перенесли). В качестве ключа можно использовать идентификатор пользователя в навыке (указывается в запросе), а в значении хранить данные состояния в формате JSON. Бесплатный экземпляр Redis можно развернуть на Heroku, а с некоторых пор он поддерживается и в Яндекс.Облаке.
Теперь подробнее разберём этапы навыка. При самом первом запуске пользователь видит обычную приветственную фразу. Далее он должен назвать свои ФИО, по которым навык будет искать профиль.
![](https://habrastorage.org/webt/vo/iv/0i/voiv0iw-ebuyx2tqmrbzdqh8l4s.png)
Если он найдётся, то в состояние следует записать его имя, и, раз уж используется кэш, то и остальную необходимую информацию о профиле можно положить в него. Теперь, когда клиент снова вернётся в навык, он увидит персональное приветствие. Если этот же человек зайдёт с другого устройства и назовёт свои ФИО, его профиль также будет найден в кэше, а значит избегаем повторного поиска через API, что экономит время на обработку запроса.
Далее происходит получение параметров поездки. Я, как пользователь голосового навыка, хочу называть города и даты как мне хочется, например, «Питер», и «через неделю». Навык должен уметь распознавать такие фразы, чтобы передать полное название города в API и выполнить поиск на нужный день. Сейчас веб-сервис навыка сразу получает эту информацию непосредственно в запросе:
![](https://habrastorage.org/webt/ra/mk/hz/ramkhziggoumdx1hijey3_xsbvy.png)
Но такая фича появилась примерно в октябре 2018 года, а навык разрабатывался чуть раньше, поэтому для понимания естественного языка был выбран Dialogflow. В нём есть отличная система разметки, и периодически можно приходить обучать его, указывая, что в той или иной фразе пользователь имел ввиду.
Итак, клиент по-своему называет город и дату, навык передаёт его слова в Dialogflow, и отправляет распознанное название города в API, откуда уже получает необходимый идентификатор. Цепочка длинная и потому снова велика вероятность не уложиться в требуемые 1500 мс.
Очевидный выход — кэшировать. Причём в качестве ключа можно указывать именно то, что сказал пользователь, а в значении хранить идентификатор города из нашей системы. Тогда в кэше может быть несколько записей для одного города, например для слов «Питер» и «Санкт-Петербург». Но это не критично, если в значении указано не слишком много информации. В любом случае, такой подход позволит наполнить кэш популярными городами, которые запрашивали другие пользователи, или «прогреть» его заранее. Это позволит в дальнейшем реже обращаться к Dialogflow и API, что снова сэкономит время.
Самый интересный этап – это запуск поиска. Все необходимые параметры есть, но, чтобы результаты пришли нужному человеку, надо каким-то образом «дернуть» внутреннюю службу поиска. Кроме того, сам по себе поиск выполняется достаточно долго, а длительные операции лучше выполнять не в том же веб-сервисе, а в отдельном приложении.
Пришла пора воспользоваться доступным сервером компании. На нём можно развернуть приложение, которое будет каким-то образом «забирать» информацию извне и выполнять длительные задачи, в том числе запускать поиск.
Таким приложением вполне может стать фоновая служба.
Из названия понятно, что это приложение без UI, которое должно начинать свою работу вместе с запуском сервера и выполнять запланированные действия, или действия по определённой команде (сообщению). Такую службу мы обычно организуем на фреймворке Topshelf, а команды она может получать, например, из очереди сообщений, основанной на протоколе AMQP.
Если кратко, то очередь работает примерно так: есть брокер, в который отправители добавляют сообщения определённого типа. И есть читатели, которые подключаются к брокеру и получают нужную информацию.
Более подробное описание можно найти, например, в этой статье.
В интернете нашлось хорошее облачное решение, предоставляющее очередь сообщений как сервис – CloudAMQP. У него есть бесплатный тариф, но работает стабильно. Ещё одним аргументом для его выбора является то, что данный сервис работает на основе RabbitMQ, который мы также плотно используем на работе.
Итак, взглянем на работу навыка в целом: веб-сервис навыка взаимодействует с API мобильного приложения и Dialogflow. Результаты обращений к ним кэшируются в Redis, и там же хранится состояние. После подтверждения параметров поездки навык передаёт брокеру сообщение со всей необходимой информацией. Фоновая служба на тестовом сервере подключается к нему, и при появлении сообщения запускает поиск, а результаты отправляются в мобильное приложение.
![](https://habrastorage.org/webt/mv/dm/og/mvdmogcqzppbtz4mq44wtqnrkfo.png)
Когда клиент скачает и установит его, то найдёт их в своих запросах:
![](https://habrastorage.org/webt/il/az/kf/ilazkfv6h8rit6vvnst7dj0lw8q.png)
На этом работа навыка завершается.
Итоги
Что же было дальше? Данный навык был показан нескольким клиентам для получения обратной связи, и вот что мы выяснили: сами по себе пользователи неохотно переходят на мобильное приложение, каким бы классным оно ни было. Некоторым из них проще позвонить по телефону нашему агенту и попросить его поискать то, что нужно.
Как показывает практика, в данном конкретном случае пользователям интереснее взаимодействовать с голосовым помощником. Он же в этом случае заменяет агента, позволяя немного сэкономить его время, и заодно мотивирует клиентов скачать приложение, чтобы продолжить в нём работу с вариантами.
Получается, что, благодаря навыку, можно сэкономить определённые ресурсы и повысить некоторые ключевые показатели, то есть предположение о пользе навыка для нашей компании подтвердилось.
Хотелось бы сделать акцент на некоторых выводах. Очевидный: чтобы успевать в 1500 мс, избегайте выполнения лишних запросов к веб-сервисам, кэшируйте. Для одной и той же информации можно использовать разные ключи кэша. Это оправдано, если хотя бы один человек попадёт в кэш, сформированный другим пользователем. И самое главное: лучше выполнять длительные операции в отдельной фоновой службе: кроме того, что она даёт децентрализацию навыка, в ней будет меньше проблем с многопоточностью, и при необходимости её можно «развернуть» внутри закрытой сети компании и «забирать» сообщения извне.
Вместо эпилога
Чат-боты и навыки часто пишут на JavaScript и Python (судя по кол-ву репозиториев на GitHub по запросу «chatbot»). Это происходит в том числе из-за лёгкой публикации на сервера. Данный проект был написан на C# под .net core. В случае классического .net framework есть определённые трудности с публикацией (работает в основном под Windows, и т.п.), но с появлением .net core многое изменилось. Для каждого упомянутого выше сервиса или фреймворка есть библиотеки, которые полностью поддерживают данную технологию. Благодаря этому навык потенциально можно запустить на линуксовых серверах, и уж тем более на любом хостинге, поддерживающем Docker. Если вдруг вы находитесь в творческом поиске, рекомендую обратить внимание на этот фреймворк, он становится хорошей альтернативой для разработки чат-ботов.
P.S.
UPD 01.08.2019: с сегодняшнего дня таймаут для навыков — 3 секунды.