26 ноября в Москве прошла третья в своей истории Conversations – конференция по разговорному искусственному интеллекту для разработчиков и бизнеса, на которой был представлен новый доклад компании «Аэроклуб ИТ». В прошлый раз речь шла об одном из наших исследовательских проектов, теперь же рассказ был сосредоточен вокруг инструмента, который мы применяем для чат-ботов. Сперва я планировал просто написать статью по мотивам доклада, но получился целый tutorial, так что под катом вас ждёт довольно подробное описание некоторых возможностей Dialogflow, и даже попадутся неочевидные «хаки».
Вообще, чат-боты и навыки для голосовых ассистентов не являются основной специализацией компании «Аэроклуб ИТ», в которой мы занимаемся разработкой программных решений в сфере B2B-travel.
Однако в 2018 году мы проводили исследования применимости навыков Алисы в нашем нелёгком деле, и в какой-то момент стали использовать Dialogflow.
Когда я был на первой Conversations в качестве слушателя, меня, как участника тех исследований, в большей степени интересовал доклад про Dialogflow. Он был отличный, но, к сожалению, достаточно поверхностный.
Спустя год и несколько практических кейсов его применения, пришло время поделиться нашим опытом. Итак, будем двигаться от простого к интересному.
Если вы уже знакомы с этим сервисом и не хотите читать всё подряд, для вас есть
Оглавление
Что такое Dialogflow?
Это облачный сервис распознавания естественного языка от Google, который поддерживает различные языки, в том числе русский. У него есть бесплатные лимиты использования, а для работы с API можно воспользоваться библиотеками для разных языков, потому его достаточно легко интегрировать в свои проекты. Также в Dialogflow (сокращённо — «DF») «из коробки» есть интеграции с различными мессенджерами, так что для простых сценариев написание своего кода может даже не понадобиться.
Агент
Перво-наперво нужно создать агента, который будет выполнять основную работу по общению с пользователем. Как сказано в документации, для понимания сути «агента» DF можно провести параллель с сотрудником call-центра, который обрабатывает запросы клиента (пользователя).
Вам нужно придумать его название, выбрать основной язык общения (позже можно будет выбрать дополнительные), и часовой пояс, в котором он будет работать:
JSON-ключ
Если планируете обращение к сервису через API, то вам понадобится JSON-ключ, необходимый для авторизации. Для этого в настройках агента (шестерёнка возле названия) нужно кликнуть по идентификатору Project ID. В новом окне найти в меню слева пункт «IAM & Admin», а в нём — «Service Accounts». Там надо нажать «Create service account», в разделе «Service account details» ввести любое значение, нажать «Create» (не «Done»!), и в «Grant this service account access to project» указать Role «Dialogflow API Client». И вот теперь нажать «Done»:
Если всё-таки не удалось указать роль при создании аккаунта, тогда нужно в меню слева найти
Iam — Add — выбрать созданный аккаунт — указать Role «Dialogflow API Client» — Save:
В «Service Accounts» возле созданного аккаунта надо найти меню «Actions», а в нём — пункт «Manage keys». Далее — Add key — Create new key — JSON — Create, и вам будет предложено скачать файл с ключом:
Как именно применять этот ключ, вы сможете найти в документации к библиотеке, которую используете в своём проекте.
Намерения (Intents)
Чтобы агент начал обрабатывать запросы пользователя, нужно добавить в него Intents (намерения, цели). Можно сказать, что они должны соответствовать намерениям пользователя, который «общается» с чат-ботом. Например, купить что-то, получить какую-то информацию, и так далее.
Как правило, после создания агента в нём уже присутствуют сразу две цели: одна — для реакции на приветствие и начала диалога (Default Welcome Intent), и другая — специальная, на тот случай, если не удалось ничего распознать (Default Fallback Intent).
В любом из намерений можно настроить «признаки», по которым будет происходить переход именно в него. Самое простое — это добавить тренировочные фразы (Training phrases), на основе которых Dialogflow определяет то или иное намерение пользователя.
Также можно указывать события (Events), используя стандартные, или придумав что-то своё. Тогда переход в Intent можно будет форсировать, передав в запросе к DF нужное название:
Теперь агент «научится» отвечать на приветствие! При разработке навыков для голосовых ассистентов часто рекомендуют, чтобы в одной и той же ситуации они не выдавали подряд одинаковые ответы. Используя Dialogflow, можно не беспокоиться об этом, потому что сервис случайным образом выбирает одну из фраз, указанных в разделе Responses. Проверить это можно в «тестовой консоли» которая находится справа:
Цветная точка возле названия намерения — это индикация его приоритета. Для его изменения достаточно «кликнуть» на неё и выбрать нужный уровень.
Это может понадобиться, когда у нескольких целей одинаковые тренировочные фразы, но отличаются контексты. Пример такой ситуации приведён почти в самом конце, где рассказывается про вопросы на уточнение.
Даже с этими минимальными настройками уже можно сделать какую-нибудь «болталку»: чат-бота, который может поприветствовать пользователя, ответить на простые вопросы, и переспросить, если ему «не понятно».
Сущности (Entities)
Одной из полезных возможностей Dialogflow с уверенностью можно назвать распознавание сущностей. Он хорошо справляется с большинством общих объектов: датами, городами, даже музыкальными группами, и прочим. Каждую тренировочную фразу намерения можно разметить и таким образом указать, какой объект в ней искать и в какой параметр записывать распознанное значение:
В сервисе есть множество системных сущностей, которые могут использоваться в большинстве сценариев. Здесь распознаются диапазоны дат (sys.date-period), «цельные» даты (sys.date), полные названия городов (sys.geo-city), и группы (music-artist):
В этом примере видно, что «Питер» правильно распознан без каких-то дополнительных настроек. Если же Dialogflow сам не справляется с распознаванием этих или других общих объектов, то некоторые системные сущности можно дополнить самостоятельно. Это делается в разделе Entities — System, где отображён список используемых системных объектов. На те из них, которые доступны для дополнения, можно «кликнуть», и в левой колонке указать полное название объекта, а справа — его синонимы:
Для работы с объектами из какой-нибудь специфической предметной области можно создать собственные сущности в разделе Entities — Custom, а при разметке фраз указывать соответствующее название.
Теперь, используя распознавание объектов, можно реализовывать простые сценарии диалога, в которых чат-бот или навык ассистента «понимает», о чём идет речь, и как-то использует информацию, полученную от Dialogflow. Например осуществляет поиск билетов в определённые города на нужные даты.
Параметры
В ответе
На этом сфера применения параметров не ограничивается. Как вы могли заметить в примерах выше, информацию из параметров текущего намерения можно встраивать в ответ. Для этого необходимо прямо в тексте написать нужное название через знак «доллара»:
Это можно использовать, например, для уточнения у пользователя, все ли названные им параметры распознаны верно.
Кстати, если при добавлении блока ответов выбрать «Custom Payload», то, вместе с обычным текстовым ответом, сервис будет передавать и доп-информацию в виде JSON. В неё тоже можно встраивать значения параметров.
Посмотреть итоговое содержимое Payload можно в окне «Diagnostic info» под тестовой консолью, справа. Там, вообще, много всякой информации, в том числе полезной:
В событии
Если вы указываете название события для перехода в то или иное намерение, то вместе с ним можно передавать и параметры. Возьмём, например, намерение, в котором указано событие PersonalGreeting, и один-единственный параметр «name», используемый в ответе:
Если в запросе к Dialogflow задать значение этого параметра («Степан» в данном случае), оно будет использовано в ответе. Запрос к API из библиотеки для .net и ответ выглядят примерно так:
Как видно, указанное значение теперь содержится в ответе, и получилось приветствие по имени!
Уточнение
Теперь возьмём более сложный пример: чат-бот ориентируется на значения параметров, которые приходят из Dialogflow, и выполняет на их основе какие-то действия. Что делать, если сервису не удалось что-то распознать, или пользователь не назвал какую-то важную информацию (например, адрес доставки)? К счастью, Dialogflow умеет задавать уточняющие вопросы для обязательных параметров. Если слева от нужного параметра поставить галку в колонке «Required», то справа появится колонка «Prompts»:
Если нажать на неё — откроется окно для добавления уточняющих фраз:
Теперь, пока информация для обязательного параметра не будет распознана, сервис будет передавать в ответ одну из фраз, указанных в этом окне. Если в намерении несколько обязательных параметров, то сервис будет задавать уточняющие фразы для каждого из них, по мере заполнения предыдущего, в соответствии с их порядком в намерении.
В ответе на запрос к Dialgoflow присутствует поле «AllRequiredParamsPresent». На него можно ориентироваться в своём коде чтобы понять, что все необходимые параметры заполнены, и тогда выполнять какое-нибудь действие.
Action
Кстати, насчёт действий. Внимательные читатели наверняка заметили текстовое поле над блоком с параметрами. Ему можно задать любое значение, даже на русском языке. Оно приходит в ответе от сервиса в поле «Action». Как сказано в документации, это просто текстовое поле, которое помогает выполнять какую-либо логику в вашем сервисе. То есть, через него можно указать, например, какое действие должно быть выполнено в вашем коде, который обрабатывает ответ от Dialogflow.
С помощью описанных выше возможностей ваш чат-бот сможет вести более «продвинутые» диалоги: спрашивать у пользователя недостающие данные, переспрашивать параметры заказа, и выполнять указанные действия, если все параметры заполнены.
Контексты (Contexts)
«Ветки» диалога
И вот мы подошли к одной из интересных возможностей сервиса. При помощи Dialogflow можно построить такой сценарий, в котором разговор пойдёт по той или иной «ветке», в зависимости от выбора пользователя: например, доставка или самовывоз. От этого зависит, что агент спросит дальше: адрес доставки или подходящую точку вывоза.
Самый простой способ сделать «ветку» — это создать связанный Intent с помощью кнопки «Add follow-up intent», которая появляется при наведении на то или иное намерение. После этого под «родительским» элементом будут отображаться «дочерние», для которых также можно создать связанные намерения:
Их взаимосвязь основана на указанных контекстах: у родительского появляется «выходящий» контекст (нижнее поле), а у дочернего — «входящий» (верхнее поле):
Например, пользователь выбрал доставку, тогда у текущего диалога будет контекст «Delivery-followup», поскольку такое название написано в поле «Output context». После этого пользователь может спросить про стоимость. Для ответа на подобный вопрос заведено намерение «Delivery — cost» с соответствующими тренировочными фразами и ответами, где поясняется стоимость доставки:
Если же пользователь выберет самовывоз и спросит про стоимость, то разговор попадёт в намерение «Pickup — cost», в котором тренировочные фразы точно такие же, но ответы немного другие:
Как вы наверняка уже поняли, диалог пойдёт по этой ветке, потому что после выбора самовывоза будет активен контекст «Pickup-followup». За этим легко проследить с помощью консоли тестирования:
Условиями попадания в одно из этих намерений являются входной контекст и тренировочные фразы. Если пользователь скажет то же самое, но диалог не будет ни в одном из этих контекстов, то он попадёт в то намерение, которое предназначено для любых непонятных ситуаций (по умолчанию — Default Fallback Intent). Кстати, подобную цель можно создавать специально для отдельных контекстов.
Обратите внимание, что «выходящий» контекст намерения присваивается диалогу только тогда, когда заполнены все необходимые параметры и разговор переходит в следующее намерение.
Параметры в контексте
Контексты удобны ещё и тем, что через них можно передавать параметры между намерениями. Как вы уже знаете, их значения можно встраивать в ответ в том же Intent'е, но если всё правильно сделать, то это можно сделать и в «дочерней» цели.
Например, для выполнения заказа диалог с пользователем проходит через несколько этапов, на каждом из которых распознаются различные объекты. В самом конце необходимо переспросить, все ли параметры заказа верные.
В намерении «Order», с которого начинается заказ, в параметр «any» записывается название товара, и задаётся выходящий контекст «Order-followup». Для того, чтобы «достать» этот параметр дальше по цепочке, текст ответа следует дополнить конструкцией «#Order-followup.any». При этом «Order-followup», конечно же должен быть указан в качестве «входящего»:
В результате этого примера получается вот такой ответ:
Полный список параметров, которые можно достать из того или иного контекста, можно увидеть в «Diagnostic info». Обратите внимание, что «входящий» контекст также дополняется параметрами, которые были распознаны в текущем намерении:
Вопрос на уточнение
Как вы помните, если обязательный параметр не заполнен, то Dialogflow попытается уточнить нужную информацию (если есть соответствующая настройка). Когда сервис задаёт наводящий вопрос по одному из параметров, то к диалогу добавляется несколько технических контекстов, имена которых генерируются автоматически, что можно увидеть на скриншоте слева. Допустим, в случае доставки или самовывоза спрашивается номер телефона пользователя. Он запросто может ответить вопросом на это уточнение: например, зачем ему называть ту или иную информацию. Причём вопрос может звучать достаточно просто: «А зачем», и без контекста по этой фразе будет трудно догадаться, о чём идёт речь. В зависимости от ситуации, он должен получить пояснение, что курьер позвонит и договорится о времени доставки, или придёт сообщение о прибытии покупки в пункт самовывоза. Как же это сделать?
На картинке выше показан пример уточнения телефона для доставки, и этому соответствует контекст «delivery_dialog_params_phone-number». Можно создать специальное намерение, для которого указать его в качестве «входящего», и сделать аналогичное для самовывоза, но с другим названием контекста, конечно же.
Теперь пользователь получит правильный ответ:
Это происходит за счёт того, что название одного из контекстов диалога и пользовательская фраза совпадают с теми же параметрами в этих намерениях.
Однако, если в вашем сценарии уже предусмотрен «общий» Intent без входного контекста, отвечающий на подобные вопросы, то диалог может перейти именно в него. Чтобы этого избежать можно понизить его приоритет, нажав на цветную точку возле названия и выбрав нужный уровень.
Срок жизни
В настройках намерений вы могли заметить число возле названия «выходящего» контекста — это, как вы уже могли догадаться, его «срок жизни». Оно указывает кол-во высказываний пользователя, на протяжении которого контекст будет «держаться». Например, «2» означает, что он будет существовать на протяжении 2х фраз, где первая — та, в которой он появился. То есть на третьей он уже пропадёт.
Для того, чтобы контекст поддерживался как можно дольше, можно задать ему какое-нибудь большое число (например, 90 000, почему нет). Но в этом случае, в соответствии с документацией, такой контекст будет «жить» 20 минут. Однако, если разговор попадёт в намерение, где он указан в качестве «выходящего», то отсчёт его срока начинается заново. Таким образом, контекст может поддерживаться «вечно».
Для того, чтоб DF мог учитывать контекст, ему нужно передавать один и тот же идентификатор сессии на протяжении разговора. Он должен быть, конечно же, уникальным для каждого диалога.
С помощью этого функционала можно построить достаточно сложные сценарии диалога, которые смогут покрыть множество задач. Как я уже писал ранее, общение между пользователем и ботом, особенно голосовым ассистентом, должно быть естественным. Данный сервис поможет в этом, по крайней мере для разговора на не очень сложные темы.
История изменений
При разработке диалога, как и любого другого проекта, легко сделать изменения, которые, возможно, понадобится «откатить». Обычно в этом помогает история изменений, или, на худой конец, система контроля версий. Первого, к сожалению в Dialogflow нет, но настройки агента можно экспортировать в json-файлы и «положить» их в SVN.
В настройках агента есть вкладка «Export and Import», в которой можно выгрузить агента в виде zip-архива. В нём содержатся файлы, которые можно отправить в github, например, как я и поступил с агентом, на примере которого писал этот материал. Благодаря этому легко отследить внесённые изменения, и, при необходимости, восстановить какую-либо версию: эти файлы можно снова собрать в архив и воссоздать агента, или дополнить его.
Выводы
Используя Dialogflow при разработке чат-ботов и навыков ассистентов, вы получаете широкий спектр возможностей: ведение диалога, получение нужной информации, гибкое поведение в зависимости от контекста, а также переиспользование одного и того же сценария диалога в чат-ботах на различных платформах, без необходимости его дублирования или портирования.
Однако, не обойдётся без минусов: ваш проект будет очень сильно зависеть от Dialogflow. Если вы пользуетесь бесплатной версией, то при превышении её лимитов (более 180 текстовых запросов в минуту) сервис, а значит — и ваш чат бот, не будут корректно отвечать на запросы пользователя.
С другой стороны, если через ваш проект проходит столько трафика (3 запроса в секунду!), значит, скорее всего, он пользуется популярностью. Если так, то его наверняка можно монетизировать. Например, в Яндекс-диалогах не так давно запустили рекламу, а ещё можно выиграть премию Алисы.
За счёт монетизации, в таком случае, можно воспользоваться платной версией сервиса. Понимаю, совет не самый лучший, но и количество запросов к нашим чат-ботам пока не выходило из предоставленных лимитов, а это значит, что их хватает для небольшого проекта.
Заключение
Здесь описаны далеко не все приёмы работы с сервисом, а лишь те, которые мы использовали в своей практике. Помимо сказанного, в нём есть fulfillment, мега-агенты, генерация голоса, и ещё много всего. Если вы используете Dialogflow, расскажите об этом, пожалуйста, в комментариях, а лучше — в своей статье.
Когда мы только начинали работу с ним, нам очень помог другой tutorial, за что спасибо его автору.
Спасибо, что дочитали до конца! Не прощаюсь, встретимся на хабре!
P.S.
UPD 15.04.2021: дополнил процесс получения JSON-ключа