Пора действовать в отношении действий Google (… каламбур 😀 )

На Google I / O 2017 компания продемонстрировала Actions on Google (или Google Action) и объявила Developer Challenge по созданию новых Actions для Google Assistant на Google Home.

С этого момента все еще относительно новая технология, я хотел бы поделиться с вами коротким руководством о том, как вы можете создавать свои собственные действия. Я буду использовать Dialogflow (также известный как api.ai), платформу взаимодействия с пользователем, чтобы помочь нам создать агента / бота, с которым мы можем общаться на человеческом языке (голосом или текстом), и Node.js для создания веб-перехватчика. / на стороне сервера.

Наше новое действие: тренер по математике!

Идея этого действия пришла ко мне, когда Google Home присоединился к нашей семье. Дети сразу же усыновили «ее» и стали ежедневно с ней общаться. Поскольку мы обучаем своих детей на дому, многие запросы к Google Home носили образовательный характер. Я подумал, что если бы я мог совместить развлечения, которые дети играли с Google Home, с возможностью для них практиковать то, что они изучали, это было бы идеально. Так началась работа с Math Trainer.

Я считаю, что у каждого продукта должна быть четкая цель, которая поможет определить его развитие. Итак, цель Math Trainer - создать действие Google, которое побуждает детей заниматься математикой.

Имея это в виду, мы можем начать:

Определение нашего действия

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

Поскольку мы имеем дело с беседой, давайте начнем с определения потоков и сценариев беседы:

  • Приветственное приветствие: когда пользователь просит Google Assistant поговорить с агентом (нашим «ботом действий»), агент должен будет начать разговор. Было бы хорошо дать пользователю краткое введение, объяснить, что такое агент, и начать разговор. Наш тренер по математике начнет с «Привет» и задаст первый вопрос.
  • Прощальное приветствие: пользователи могут разговаривать только с одним агентом за раз, поэтому, когда пользователь хочет завершить разговор, он вернется в Google Assistant. Было бы неплохо попрощаться с пользователем и, возможно, поощрить его вернуться в будущем.
  • Пользователь дает ответ: сейчас я разделю этот сценарий на 2 варианта:
    1. Правильный ответ: агент ответит положительно и задаст следующий вопрос, чтобы можно было продолжить разговор.
    2. Неправильный ответ: агент ответит отрицательно и попросит пользователя повторить попытку, чтобы продолжить разговор.
  • Резервное намерение по умолчанию: когда Dialogflow не может сопоставить ввод пользователя ни с одним из сценариев, он отправит его на сервер в качестве резервного по умолчанию. Это позволит справиться с ситуацией, когда пользователь потерялся в разговоре и его нужно вернуть в нужное русло.

Позже я конвертирую эти сценарии в намерения, чтобы настроить их в Dialogflow.

Теперь, когда мы определили наш продукт, давайте настроим его и напишем код.

Создание тренера по математике

Мы начнем с создания двух учетных записей, одну на https://console.actions.google.com, а другую на https://console.dialogflow.com. Учетная запись Google позволит нам создавать действия, тестировать и отправлять их в Google Assistant, а учетная запись Dialogflow позволит нам создать агента, который будет переводить человеческий язык в структуру данных, которую может обрабатывать наш сервер.

После того, как мы настроили учетные записи, мы можем начать процесс:

  1. В actions.google создайте новый проект. Дайте ему имя и нажмите кнопку Создать проект.
  2. В разделе «Добавить действия в приложение» выберите Dialogflow.
  3. Нажмите кнопку Создать действия в Dialogflow, чтобы открыть новую вкладку с консолью Dialogflow.
  4. Там вы можете добавить детали, если хотите, и нажмите Сохранить.

Создание намерений

Как только действие будет создано в Dialogflow, консоль перенесет вас в окно намерений. Это сердце вашего приложения (душа будет в коде JS). Намерения будут собирать вводимые пользователем данные на человеческом языке и переводить их в действия, которые может обрабатывать код.

Начните с удаления намерения «Добро пожаловать по умолчанию» и создайте новое с именем «math_trainer» (вы можете увидеть кнопку «Создать намерение» вверху).

  • В Контекстах добавьте «викторину» к выходным контекстам. Контексты помогают нам понять, в каком состоянии мы находимся в разговоре.
  • В поле Пользователь говорит мы добавим текст, который ожидаем получить от пользователя, чтобы начать действие. А пока просто добавьте предложение: «Тренер по математике». Я объясню больше о том, что говорит пользователь, в следующем намерении.
  • В События добавьте «GOOGLE_ASSISTANT_WELCOME», чтобы помощник Google знал, что это первое намерение, которое запускается при обращении пользователя к приложению.
  • В Действие напишите «generate_question». Это будет действие, которое код будет использовать для этого намерения.

Пришло время сохранить намерение, нажав кнопку Сохранить в правом верхнем углу. (Кнопка Сохранить - это то, что вам всегда нужно помнить при работе в Dialogflow).

Поздравляю! Вы только что сделали свое первое намерение! Теперь нам просто нужно создать еще несколько, чтобы обеспечить полноценный диалог.

Следующее намерение будет называться «quit_trainer», и вы можете догадаться, что оно будет делать.

  • В Контекстах добавьте «викторину» к входным и выходным контекстам.
  • Пользователь говорит, будет следующее:
    Я сдаюсь
    Стоп
    Выйти
    Здесь мы просто приводим несколько примеров. о том, что пользователь может сказать, если он / она хотел бы завершить разговор. Dialogflow будет использовать свое машинное обучение, чтобы «перевести» больше предложений в это намерение.
  • Действие будет «выйти».

Таким образом, мы позаботимся об этом случае (не забудьте нажать «Сохранить»!), Так что теперь нам нужно еще одно намерение, прежде чем мы сможем погрузиться в реальное кодирование.

Затем мы создадим намерение под названием «provide_answer».

  • Еще раз добавьте «викторину» во входные и выходные Контексты.
  • (Мы заполним Пользователь говорит через мгновение)
  • В разделе Действие напишите check_answer в названии действия и заполните первую строку в таблице ниже:
    ТРЕБУЕТСЯ: необходимо отметить. < br /> ИМЯ ПАРАМЕТРА: answer
    ENTITY: @sys .number
    VALUE: $ answer

Теперь вернемся к Пользователь говорит и добавим строку «это 45». Как только вы нажмете Enter, строка будет добавлена ​​в список, платформа отметит «45» желтым цветом, и появится новая всплывающая строка, которая покажет вам, что она понимает, что это значение 45.

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

Приведем еще несколько примеров. Добавьте следующие строки:
Думаю, это 12
27
Мой ответ 42


Примечание. Если по какой-то причине система не отметила номер, вы можете сделать это вручную, выделив номер мышью и выбрав @sys .number: answer из всплывающего списка.

Опять же, не забудьте сохранить новое намерение.

На самом деле у нас есть еще одно намерение в списке, «Резервное намерение по умолчанию», которое обрабатывает все события «потеряны в разговоре», но поскольку мы получаем это намерение от системы, нам не нужно с ним ничего делать ( теперь).

Написание «души» приложения (кодирование на JS)

Итак, теперь, когда у нас есть намерение входа, намерение выхода и намерение ответа, мы можем начать писать некоторый код.

Для Webhook я буду использовать Node.js всего с двумя файлами: index.js и package.json. Вы можете скачать их с github: bit.ly/MathTrainerV1.

(Примечание: объяснение о package.json на самом деле не входит в объем этой публикации, но если вы хотите, чтобы я объяснил это более подробно, дайте мне знать в комментариях.)

Теперь приступим к работе с нашим основным файлом: index.js. Начнем с некоторых объявлений среды:

'use strict';
process.env.DEBUG = 'actions-on-google:*';
let ApiAiAssistant = require('actions-on-google').ApiAiAssistant;
let sprintf = require('sprintf-js').sprintf;

Установите константы:

// The context and actions as we declare them in api.ai
const QUIZ_CONTEXT = 'quiz';
const GENERATE_QUESTION_ACTION = 'generate_question';
const CHECK_ANSWER_ACTION = 'check_answer';
const QUIT_ACTION = 'quit';
const DEFAULT_FALLBACK_ACTION = 'input.unknown';

А теперь давайте настроим несколько строк для обработки разговора:

const WRONG_ANSWER = 'No, it\'s not %s. Try again.';
const CORRECT_ANSWER = 'Correct!';
const WELCOME_MESSAGE = 'Welcome to the math trainer!';
const ASK_QUESTION = 'How much is %s';
const QUIT_MESSAGE = 'See you later.';

И, наконец, зададим логическую константу:

const min = 0;
const max = 10;

Затем давайте создадим функцию для подключения к Dialogflow:

exports.math_trainer = function (request, response) {
    // print the request to the log
    //console.log('headers: ' + JSON.stringify(request.headers));
    //console.log('body: ' + JSON.stringify(request.body));
const assistant = new ApiAiAssistant({request: request, response: response});

Давайте оставим эту функцию открытой и добавим некоторые другие функции, которые будут обрабатывать ввод / вывод нашего действия.

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

Чтобы Math Trainer мог вести правильный «разговор», мы создадим функцию для обработки каждого намерения и закроем функцию math_trainer:

function generateQuestion (assistant) {
        console.log('generateQuestion');
        let newQuestion = getNextQuestion();
        assistant.data.answer = newQuestion.answer;
        assistant.data.question = newQuestion.question;
        assistant.setContext(QUIZ_CONTEXT);
        assistant.ask(printf(WELCOME_MESSAGE + ' ' +
            ASK_QUESTION, newQuestion.question));
    }
function checkAnswer (assistant) {
        console.log('checkAnswer');
        let answer = assistant.data.answer;
        //let question = assistant.data.question;
        let userAnswer = assistant.getArgument("number")?  parseInt(assistant.getArgument("number")):  '';
if (answer! = userAnswer) {
            assistant.ask( printf(WRONG_ANSWER, userAnswer));
        } else {
            let newQuestion = getNextQuestion();
            assistant.data.answer = newQuestion.answer;
            assistant.data.question = newQuestion.question;
            assistant.ask(printf(CORRECT_ANSWER + ' ' + ASK_QUESTION, newQuestion.question));
        }
    }
function defaultFallback (assistant) {
        console.log('defaultFallback: ' + assistant.data.fallbackCount);
        if (assistant.data.fallbackCount === undefined) {
            assistant.data.fallbackCount = 0;
        }
        assistant.data.fallbackCount++;
        assistant.ask("WHAT? I asked how much is "+ assistant.data.question);
    }
/**
     * Use Tell to send goodbye message and close the mic
     * @param assistant
     */
    function quit (assistant) {
        console.log('quit');
        assistant.tell(QUIT_MESSAGE);
    }
/**
     * Use sprintf to reformat the string and add params to it
     * @param line
     * @returns {*}
     */
    function printf(line) {
        console.log('printf: ' + line);
        return sprintf.apply(this, arguments);
    }
// Map all the actions that create on api.ai to the function in this file
    let actionsMap = new Map();
    actionsMap.set(GENERATE_QUESTION_ACTION, generateQuestion);
    actionsMap.set(CHECK_ANSWER_ACTION, checkAnswer);
    actionsMap.set(DEFAULT_FALLBACK_ACTION, defaultFallback);
    actionsMap.set(QUIT_ACTION, quit);
    assistant.handleRequest(actionsMap);
};

Наконец, нам нужна еще одна функция для генерации вопросов:

/**
 * Randomize the next question
 * @returns {{answer: number, question: string}}
 */
function getNextQuestion (){
    let value1 = Math.floor(Math.random() * (max - min + 1)) + min;
    let value2 = Math.floor(Math.random() * (max - min + 1)) + min;
    let res = {
        answer: (value1 + value2),
        question: sprintf(PLUS_QUESTION, value1, value2)
    };
    console.log(JSON.stringify(res));
    return res;
}

Теперь мы готовы к запуску!

Запуск кода

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

Вам нужно будет запустить экземпляр node.js в общедоступном домене с SSL. Если у вас нет под рукой, вы можете использовать Google App Engine. Чтобы узнать, как это сделать, см .: https://cloud.google.com/functions/docs/tutorials/http.

Когда у вас есть рабочий URL-адрес с запущенным на нем кодом, перейдите в свой проект в Dialogflow и выберите Выполнение. Включите Webhook, установите URL-адрес в поле URL-адреса и сохраните его.

Теперь перейдите к намерениям и в каждом из них найдите раздел «Выполнение» внизу и включите Использовать веб-перехватчик.

Теперь мы установили соединение между Dialogflow и нашим сервером Node.js, поэтому мы можем перейти к настройке соединения между Dialogflow и Actions в Google.

Откройте раздел Интеграции в интерфейсе Dialogflow и нажмите Действия в Google. В окне настроек убедитесь, что для параметра «Добро пожаловать» установлено значение «math_trainer», и нажмите Обновить.

Используйте кнопку Посетить консоль, чтобы вернуться на console.actions.google.com.

Заполните всю информацию о приложении, которую запрашивает система. Затем в разделе Возможности поверхности задайте для всех вопросов ответ «Нет».

Нажмите Проверить, чтобы убедиться, что он выполняет то, что мы хотим, затем следуйте инструкциям в Симуляторе действий и протестируйте новое действие.

Поздравляем, вы только что создали новую акцию!

Но если вы немного поиграете с этим, вы согласитесь со мной, что он на самом деле не соответствует определению ״ Math Trainer - это действие Google, которое побуждает детей заниматься математикой. ״ Это скучно и не требует создать приятный опыт для разговора. Повторение одних и тех же предложений ни у кого не вызовет желания продолжить разговор, особенно у ребенка.

Итак, теперь нам нужно спроектировать личность агента и спроектировать разговор агента так, чтобы он соответствовал человеку, но мы поработаем над всем этим в следующем посте.

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