Главная » Хабрахабр » From zero to “Actions on Google” hero: начало

From zero to “Actions on Google” hero: начало

image

Хакатон Google, и все, что нужно, чтобы начать разрабатывать свои приложения для ассистента.

Это хорошая возможность получить опыт и подумать, как начать делать conversation user interface (CUI) для наших приложений. Google организовал хакатон, посвященный технологии Actions On Google. Поэтому мы собрали команду из двух Android-разработчиков: shipa_o, raenardev и дизайнера comradeguest и отправились участвовать.

Что такое Actions On Google?

Actions On Google (AoG) — это способ добавить свое действие в ассистент.
Сделать это можно с помощью 4 инструментов:

На хакатоне мы делали навык — приложение, расширяющее возможности ассистента, поэтому на нем и остановимся.

Я хочу поговорить с $”, ассистент открывает навык, с которым пользователь и ведет диалог: После обращения «Окей, гугл.

image

Как написать навык?

Вам понадобятся два скилла:
— понимание работы Conversational User Interface (CUI), умение их проектировать;
— умение работать с Natural Language Processing (NLP), например, Dialogflow.

Этап 1: Проектирование

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

Если на графическом можно показать всю форму оформления заказа, а человек сам будет выбирать, на что посмотреть сначала, а на что потом, то в голосовом задавать вопросы можно только один за другим. Голосовой интерфейс последовательный. Чтобы придумать востребованное и удобное приложение, найдите пересечение между потребностями пользователя и возможностью использования голосового интерфейса (или невозможностью использовать другие).

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

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

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

Обращение (Invocation) 1.

Вызов может быть явным (Explicit Invocation) и косвенным (Implicit Invocation). Для начала помощника надо позвать. Косвенное нужно, чтобы Google Assistant мог порекомендовать подходящее приложение в определенной ситуации. Явное обращение люди будут использовать, когда уже знают приложение. Правильно подобранные варианты косвенного обращения — как правильные ключевые слова в контекстной рекламе, только более «человеческие».

Тип обращения

Описание

Пример

Явное (Explicit Invocation)

С упоминанием названия помощника

Окей, Гугл, я хочу поговорить с Красным страстным кинороботом.
Позови мне киноробота.
Где там красный и страстный?

 
Косвенное (Implicit Invocation)

 
В контексте, когда нужен помощник

 
Окей, Гугл, посоветуй мне какой-нибудь фильм.
Хочу посмотреть смешную комедию.
Какое кино посмотреть с девушкой?

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

Например, обычно наш киноробот начинает общение с того, что предлагает человеку выбрать какой-нибудь жанр. Вызовы могут содержать deep link к отдельным функциям голосового помощника. Но если его вызовут по косвенному обращению «Хочу посмотреть смешную комедию», логично начать диалог с предложения гарантированно хорошего фильма упомянутого жанра.

Первое приветствие 2.

Первое приветствие — это то, что говорит человеку приложение сразу после вызова.
Сначала нужно дать пользователю понять, что помощник уже тут:

Я Красный страстный киноробот. Привет, белковая форма жизни. Цель моего существования — советовать биологическим организмам хорошие фильмы.

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

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

Первый раз

Повторно

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

Приветствую, человек! Какой жанр тебя интересует?

Разговор по-людски 3.

Самый простой способ сделать это — ещё до начала разработки пообщаться с людьми из целевой аудитории. Учите помощника понимать естественную речь и поддерживать беседу. Сыграйте роль робота, а собеседника попросите представить, что он пользуется вашим будущим приложением. Причем желательно устно, а не письменно, потому что письменная разговорная речь более скудная, чем устная. Это поможет спроектировать схему типовой беседы и найти, где могут появиться ответвления. Запишите все диалоги на диктофон, а потом расшифруйте.

Этап 2: Разработка

Разрабатывать свой action для ассистента можно несколькими способами:

  • С Dialogflow.
  • С Actions on Google SDK.
  • Текст можно обрабатывать самостоятельно — например, если у вас есть свое решение для обработки естественного языка (NLP — Natural Language Processing).

Ниже нарисовано взаимодействие ассистента с вашим навыком.
Диалог выглядит примерно так:

  1. Ассистент переводит речь в текст и отправляет его в ваш action.

  2. На этой схеме — через Dialogflow. Текст обрабатывается одним из указанных выше способов.

  3. Dialogflow определяет intent (конкретное намерение пользователя) и получает
    из него entities (параметры).

  4. (Опционально) Dialogflow может вызвать соответствующий webhook, обработать данные на backend и получить ответ.

  5. Dialogflow формирует ответ.

  6. Ассистент озвучивает ответ, включает микрофон и слушает, что скажет пользователь.

image

Схема устройства action для ассистента

Dialogflow

Не будем подробно расписывать основы Dialogflow — Google выпустили хорошие обучающие видео.

  1. Intents — про распознавание intent, как именно Dialogflow понимает что спрашивает пользователь или какое действие он хочет совершить.
  2. Entities — про распознавание параметров внутри фразы. Например, в случае с рекомендацией фильмов это конкретный жанр.
  3. Dialog Control — про механизм контекстов (о нем чуть ниже) и fulfillment: о том, как обработать сам запрос пользователя путем обращения к вашему бекенду, и о том, как вернуть что-то более интересное, чем текстовый ответ.

Давайте разберем вопросы, которые возникали у нас по каждой из частей в процессе реализации, и что интересного можно отметить. Будем считать, что вы уже посмотрели видео и разобрались с консолью Dialogflow.

Помните также о правилах построения хорошего диалога, когда будете переходить к реализации — это повлияет на связку intents, набор entities и использование их в ответах, на использование контекстов и все остальное.

Intents

Как это реализовать? Есть рекомендации — сделать более подробное приветствие нового пользователя, а для остальных делать его более кратким.

Это можно делать внутри fulfillment для welcome intent. В консоли Dialogflow определить такую логику не получится. Иначе говоря, сделать это нужно будет руками.

Например, в первый раз можно просто переспросить, а во второй — рассказать, какого ответа вы ждете от пользователя. Это касается и обработки ошибок.

Можно сделать через fulfillment или чуть хитрей, завязав на контекст (об этом ниже). Через responses это не сделать — будет выбран случайный ответ.

Entities

Any "Allow automated expansion" и sys.

Если фраза похожа по структуре, то при включенном "Allow automated expansion" в качестве распознанной сущности может попасть что-то, что все равно обработать с пользой не получится.

Если сказать "Посоветуй мне что-нибудь из еды", то в качестве параметра "жанр" вы получите не то, что ожидали бы. Например — ваше приложение распознает фразу "Посоветуй мне что-нибудь из <жанр>" для советов по фильмам. Это имеет смысл, если набор жанров у вас постоянно динамически обновляется на бекенде, и там же обрабатывается ответ пользователя, но вы не успеваете обновлять entity.

Any. Другое решение — использование sys. Это дает более широкие возможности чем automated expansion, но и недостатков больше. В этом случае он передаст все, что было после ожидаемой конструкции, что может быть целым предложением.

Единственное и множественное число, падежи, род

Мы будем говорить про любимые фрукты, а приложение будет отвечать какие они классные. Давайте придумаем простой пример. В примерах далее П — пользователь, А — наш action.

П: Ананасы самые вкусные.
A: Да, ананасы огонь!

Мы можем добавить синоним. В этом примере у нас есть entity: Ананасы
Но что если пользователь говорит «Ананас»?

П: Мне понравился ананас.
A: Да, ананасы огонь!

именно исходная форма, не синонимы. По умолчанию используется reference form — т.е. Именно поэтому приложение не говорит «Ананасы огонь!»

Если бы у нас был ответ «Да, $фрукт самые вкусные», то мы бы получили следующее: Стоит это учитывать при написании ответов.

П: Мне понравился ананас.
A: Да, ананасы самые вкусные.

И не забываем про падежи:

П: Я хочу драму
A: Вот классная драма…

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

Жанры на русском будем делать в именительном падеже, но в любом числе (драма, ужасы).

Когда дойдем до ответов — не будем писать "Да, $фрукт огонь!" и "Да, $фрукт самый вкусный" — фрукты у нас всегда во множественном числе.

В случае с русским языком стоит пойти еще дальше и подумать как написать согласованный ответ — у нас есть не только падежи (драма/драму), но и род (драма/вестерн).

П: Я хочу вестерн
A: Вот классная вестерн…

Хорошо, вот классный фильм... П: Я хочу вестерн
A: Вестерн?

Но как возвращать именно ту форму entity, которую сказал пользователь?

Тогда если бы пользователь говорил "инопланетяне", в качестве параметра возвращался "научная фантастика".
Если мы хотим получить entity в той форме, в которой сказал пользователь то стоит выбрать значение $entity.original В случае с жанрами, синонимом "научной фантастики" могло бы быть "инопланетяне".

image

Действительно ли это нужно? Но тогда возможны проблемы с несогласованностью численности и (особенно) несогласованностью падежей. Ответы также должны быть согласованы с формой entity, которая в них используется. Если да, создавайте entity для единственного, множественного числа и падежей.

Contexts

Пожалуй, с этим больше всего проблем.

Input context

На одну и ту же фразу могут реагировать несколько intent'ов, и скорее всего сработает тот, у которого активен входящий контекст.
Таким образом, можно, например, привязать ответ "да/нет" к конкретному вопросу, что и делается при использовании follow-up intent в Dialogflow Это контекст, к которому привязан конкретный intent.

Output context

Именно так активируются контексты в консоли Dialogflow (в fulfillment это тоже можно делать). Это контекст, который активируется при срабатывании intent. Это значит что данные внутри этого контекста станут больше недоступны и intent'ы, для которых он является входным не будут срабатывать. Мы указываем число витков диалога, в течении которых он будет активен, а после обнуления счетчика либо по истечению 20 минут он деактивируется.

На этом же завязан другой трюк: вы можете одним intent активировать контекст, а другим вручную его деактивировать, просто проставив его как output контекст для второго intent с числом ответов 0.

Если не хотите писать код в fulfillment, то таким образом можно реализовать интересную логику, например, используя контекст как счетчик, реализовать обработку ошибок, когда ассистент не понимает пользователя.

Советы по работе в dialogflow

  • Dialogflow можно рассматривать как backend, к которому обращается ассистент. Не нужно перезапускать страничку с assistant preview — когда вы внесли изменения в агент dialogflow, можете дождаться завершения его обучения и сразу же повторить нераспознанную фразу в симуляторе.

  • Пользуйтесь prebuilt agents — там вы сможете посмотреть, как реализовать типовой сценарий.

  • Его использование не выключает микрофон в конце беседы, и такие ответы обычно не содержат call-to-action. Будьте осторожны с разделом Small talk. С большой вероятностью из-за этого вы можете не пройти ревью. Вы не направляете пользователя к следующему витку диалога, и ему не совсем понятно, что следует сказать далее. Лучше сделать отдельные intents для этого, если вы сможете вписать их в диалог.

  • Сейчас одновременная работа нескольких человек не поддерживается — неизвестно, чьи изменения перезапишутся. Не стоит редактировать один и тот же intent вдвоем одновременно.

  • Также импорт и экспорт entities в json/xml и импорт/экспорт для intent. Если необходимо распараллелить работу с intent — ее можно вести в отдельных проектах, а затем просто выбрать нужные и перенести.

  • Написание ответов на русском языке имеет дополнительные нюансы. Сразу стоит учесть, что вы пишите action для конкретного языка. Так что локализация action выглядит более сложной задачей, чем в случае с GUI мобильных приложений.

  • Вы строите диалог, поэтому каждый ответ должен оставлять call to action, чтобы пользователь понимал, что сказать. Учитывайте правила дизайна голосовых интерфейсов — они влияют не только на набор реплик, но и на структуру в целом.

  • Возможно, на этапе тестирования вы поймете, как связать intents и чего не хватает для удобства использования. После того, как все будет готово, и вы начнете тестирование, не бойтесь отказываться от отдельных ветвей диалога или форм вопросов.

Подключение сервера

Для этого есть два варианта: Для подключения сервера нужно использовать fulfillment.

  • Webhook client. Поддерживается множество языков.
  • Inline Editor на Cloud Functions for Firebase (node.js).

Рассмотрим самый простой — Inline Editor.

На звание экспертов в node.js мы не претендуем, исправление ошибок в комментариях приветствуется.

Все, что написано для версии v1 с ней не работает.
Подробнее про миграцию можно почитать тут. Важно обращать внимание на версию API Dialogflow.
Последняя версия v2.

Полезные ссылки:

Разбираем стандартный шаблон

При открытии раздела Fulfillment, отображается следующий код в файле/вкладке `index.js`:

'use strict'; const functions = require('firebase-functions');
const {WebhookClient} = require('dialogflow-fulfillment');
const {Card, Suggestion} = require('dialogflow-fulfillment'); process.env.DEBUG = 'dialogflow:debug'; // enables lib debugging statements exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers)); console.log('Dialogflow Request body: ' + JSON.stringify(request.body)); function welcome(agent) { agent.add(`Welcome to my agent!`); } function fallback(agent) { agent.add(`I didn't understand`); agent.add(`I'm sorry, can you try again?`); } // // Uncomment and edit to make your own intent handler // // uncomment `intentMap.set('your intent name here', yourFunctionHandler);` // // below to get this function to be run when a Dialogflow intent is matched // function yourFunctionHandler(agent) { // agent.add(`This message is from Dialogflow's Cloud Functions for Firebase editor!`); // agent.add(new Card({ // title: `Title: this is a card title`, // imageUrl: 'https://developers.google.com/actions/images/badges/XPM_BADGING_GoogleAssistant_VER.png', // text: `This is the body text of a card. You can even use line\n breaks and emoji! `, // buttonText: 'This is a button', // buttonUrl: 'https://assistant.google.com/' // }) // ); // agent.add(new Suggestion(`Quick Reply`)); // agent.add(new Suggestion(`Suggestion`)); // agent.setContext({ name: 'weather', lifespan: 2, parameters: { city: 'Rome' }}); // } // // Uncomment and edit to make your own Google Assistant intent handler // // uncomment `intentMap.set('your intent name here', googleAssistantHandler);` // // below to get this function to be run when a Dialogflow intent is matched // function googleAssistantHandler(agent) { // let conv = agent.conv(); // Get Actions on Google library conv instance // conv.ask('Hello from the Actions on Google client library!') // Use Actions on Google library // agent.add(conv); // Add Actions on Google library responses to your agent's response // } // // See https://github.com/dialogflow/dialogflow-fulfillment-nodejs/tree/master/samples/actions-on-google // // for a complete Dialogflow fulfillment library Actions on Google client library v2 integration sample // Run the proper function handler based on the matched Dialogflow intent name let intentMap = new Map(); intentMap.set('Default Welcome Intent', welcome); intentMap.set('Default Fallback Intent', fallback); // intentMap.set('your intent name here', yourFunctionHandler); // intentMap.set('your intent name here', googleAssistantHandler); agent.handleRequest(intentMap);
});

И такие зависимости в файле/вкладке `package.json`:

{ "name": "dialogflowFirebaseFulfillment", "description": "This is the default fulfillment for a Dialogflow agents using Cloud Functions for Firebase", "version": "0.0.1", "private": true, "license": "Apache Version 2.0", "author": "Google Inc.", "engines": { "node": "~6.0" }, "scripts": { "start": "firebase serve --only functions:dialogflowFirebaseFulfillment", "deploy": "firebase deploy --only functions:dialogflowFirebaseFulfillment" }, "dependencies": { "actions-on-google": "2.0.0-alpha.4", "firebase-admin": "^4.2.1", "firebase-functions": "^0.5.7", "dialogflow": "^0.1.0", "dialogflow-fulfillment": "0.3.0-beta.3" }
}

Первым делом, обновите зависимости alpha и beta версий, до последних стабильных.

Вот последние версии на данный момент

{ "dependencies": { "actions-on-google": "^2.2.0", "firebase-admin": "^5.2.1", "firebase-functions": "^0.6.2", "dialogflow": "^0.6.0", "dialogflow-fulfillment": "^0.5.0" }
}

А теперь давайте разберемся подробнее с кодом.

Сверху делается импорт зависимостей

// Cloud Functions для Firebase library
const functions = require('firebase-functions');
// Компонент для работы с вашим агентом
const {WebhookClient} = require('dialogflow-fulfillment');
// Компоненты для вывода информации на экран
const {Card, Suggestion} = require('dialogflow-fulfillment');

Вся суть fulfillment заключается в переопределении callback-a `dialogflowFirebaseFulfillment`

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { console.log('Dialogflow Request headers: ' + JSON.stringify(request.headers)); console.log('Dialogflow Request body: ' + JSON.stringify(request.body)); // Создаем инстанс агента. const agent = new WebhookClient({ request, response }); // Полезные данные let result = request.body.queryResult; // Получение action и entities https://dialogflow.com/docs/actions-and-parameters let action = result.action; let parameters = result.parameters; // Работа с контекстом https://dialogflow.com/docs/contexts let outputContexts = result.outputContexts; // Информацию об устройстве можно получить тут let intentRequest = request.body.originalDetectIntentRequest;
});

Этот callback будет вызываться для тех intent, у которых Вы активируете fullfilment.

Теперь переопределим ответ на intent

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); function welcome(agent) { // Вывод фразы agent.add(`Welcome to my agent!`); } function fallback(agent) { agent.add(`I didn't understand`); agent.add(`I'm sorry, can you try again?`); } // Создаём ассоциативный массив, в котором: // key - точное название intent-а. // value - функция с кодом, который надо выполнить. let intentMap = new Map(); intentMap.set('Default Welcome Intent', welcome); intentMap.set('Default Fallback Intent', fallback); agent.handleRequest(intentMap);
});

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

Вынесем функции обработки intent-а из callback.
Функции welcome и fallback находятся в замыкании.

Чтобы их вынести из callback, придется добавить передачу контекста функции и параметров через `bind`

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => { const agent = new WebhookClient({ request, response }); let intentMap = new Map(); // Метод set возвращает Map. Поэтому их можно вызывать последовательно intentMap .set('Default Welcome Intent', welcome.bind(this, agent)) .set('Default Fallback Intent', fallback.bind(this, agent)); agent.handleRequest(intentMap);
}); function welcome(agent) { agent.add(`Welcome to my agent!`);
} function fallback(agent) { // Можно объединить 2 вызова метода add в массив фраз agent.add([ `I didn't understand`, `I'm sorry, can you try again?` ]);
}

База есть, а к хардкору перейдем в следующей части. Итак, теперь вы готовы к тому, чтобы написать свой первый навык для Google Assistant.


Оставить комментарий

Ваш email нигде не будет показан
Обязательные для заполнения поля помечены *

*

x

Ещё Hi-Tech Интересное!

[Перевод] Почему учёные считают, что Девятой планеты не существует

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

[Перевод] [ конкурс ] Топ-25 игровых консолей (тряхнём стариной)

Каждый помнит свою первую игровую консоль — тот момент, когда её принесли в дом, и тот невероятный миг, когда началась его первая игра. Сегодня мы хотим представить вашему вниманию топ-25 игровых консолей, составленный ресурсом ign.com. Начнём с 25-го места. А ...