«Алиса, пойдём во фронтенд!»

Голосовые помощники — не далёкое будущее, а реальная действительность. Alexa, Siri, Google Now, Алиса встроены в «умные» колонки, часы и телефоны. Они постепенно меняют наш способ взаимодействия с приложениями и устройствами. Через ассистента можно узнать прогноз погоды, купить билеты на самолет, заказать такси, послушать музыку и включить чайник на кухне, лежа на диване в другой комнате.

mrsw35dh1qjeefkk8eb1f1ffvj0.jpeg

Siri или Alexa говорят с пользователями в основном по-английски, поэтому в России они не так популярны, как Алиса от Яндекса. Для разработчиков Алиса тоже удобнее: её создатели ведут блог, выкладывают удобные инструменты на GitHub и помогают встраивать ассистента в новые устройства.

Никита Дубко (@dark_mefody в Твиттере) — разработчик интерфейсов в Яндекс, организатор митапов MinskCSS и MinskJS и редактор новостей в Web-стандартах. Никита не работает в Яндекс.Диалогах и никак не связан с Яндекс.Алисой. Но ему было интересно разобраться, как Алиса работает, поэтому он попробовал применить её навыки для Web и подготовил об этом доклад на FrontendConf РИТ++. В расшифровке доклада Никиты рассмотрим, что полезного могут принести голосовые помощники и построим навык прямо в процессе чтения этого материала.


Боты


Начнем с истории ботов. В 1966 году появился бот Eliza, который выдавал себя за психотерапевта. С ним можно было пообщаться, и некоторые даже верили, что им отвечает живой человек. В 1995 году вышел бот A.L. I.C.E. — не путать с Алисой. Бот умел выдавать себя за настоящего человека. До наших дней лежит в Open Source и дорабатывается. К сожалению, A.L. I.C.E. не проходит Тест Тьюринга, но вводить в заблуждение людей ему это не мешает.

В 2006 году IBM поместила в бота огромную базу знаний и сложный интеллект — так появился IBM Watson. Это огромный вычислительный кластер, который умеет обрабатывать английскую речь и выдавать какие-то факты.

В 2016 году компания Microsoft провела эксперимент. Она создала бота Tay, которого запустила в Twitter. Там бот обучался вести микроблог на основе того, как с ним общались живые подписчики. В итоге Tay стал расистом и женоненавистником. Теперь это закрытый аккаунт. Мораль: не пускайте детей в Твиттер, он может научить плохому.

Но это все боты, с которыми нельзя пообщаться для своей пользы. В 2015 году в Telegram появились «полезные». Боты существовали и в других программах, но Telegram произвел фурор. Можно было создать полезного бота, который выдавал информацию, генерировал контент, администрировал паблики — возможности большие, а API простой. Ботам добавили картинки, кнопки, подсказки — появился интерфейс взаимодействия.

Постепенно идея распространилась почти во всех мессенджерах: Facebook, Viber, ВКонтакте, WhatsApp и в других приложениях. Теперь боты — это тренд, они везде и всюду. Появились сервисы, которые позволяют писать API сразу под все платформы.

Голосовые ассистенты


Разработки шли параллельно с ботами, но будем считать, что эра ассистентов наступила позже.

9 августа 2011 появилась Siri. Изначально это был независимый проект, в котором Apple разглядел что-то интересное, поэтому купил его. Это самый старый популярный голосовой ассистент, встроенный в ОС. Годом позже Google достаточно быстро догнал Apple, встроив в свою операционную систему голосового помощника Google Now.

Спустя 2 года Microsoft выпустили Microsoft Cortana. Только непонятно зачем — мобильный рынок голосовых помощников, кажется, они уже упустили. Компания попыталась встроить голосового ассистента в десктопные системы, когда уже шла борьба за рынок разных устройств. Чуть позже в том же году вышла Amazon Alexa.

uncwehkki7vgcfyeh7148omczd4.jpeg

Ассистенты развивались. Кроме программных комплексов, которые умели работать с голосом, появились колонки с помощниками. По статистике на начало 2019 года в каждой третьей семье в США есть умная колонка. Это огромный рынок, в который можно вкладывать деньги.

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

Алиса


Алиса вышла в открытую бету 10 октября 2017 года. Она заточена под русский язык и это её огромное преимущество. Алиса понимает и английский, но хуже.

Миссия Алисы — помогать русскоязычным пользователям.


Яндекс — большая компания и может позволить себе встроить Алису во все свои приложения, которые могут как-то разговаривать.

  • Яндекс.Браузер.
  • Яндекс.Навигатор.
  • Яндекс.Станция.
  • Яндекс.Телефон.
  • Яндекс.Авто.
  • Яндекс.Драйв.


Интеграция так хорошо зашла, что сторонние производители тоже решили встроить Алису.

tcnaaauucjom0ojuvb6jpgdjzge.jpeg

За 2 года развития ассистента её интегрировали во множество сервисов и добавили новые навыки. Она умеет играть музыку, распознавать картинки, искать информацию в Яндексе и работать с умным домом.

Почему так популярна?


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

Лень. Я лежу на диване под пледиком и мне не хочется вставать, чтобы идти куда-то включать колонки. Если уж лениться, то по полной.

Большой рынок — это приложения для детей. Маленькие дети еще не умеют читать, писать и печатать, но разговаривают и понимают речь. Поэтому дети обожают Алису и любят с ней общаться. Родители тоже довольны — не нужно искать, чем занять ребёнка. Интересно, что Алиса детей понимает благодаря хорошо обученной нейросети.

Доступность. Людям с нарушениями зрения комфортно работать с голосовыми ассистентами — когда не видно интерфейс, его можно слышать и давать ему команды.

Голосом быстрее. Обычный человек, не разработчик, печатает в среднем 30 слов в минуту, а говорит — 120. За минуту голосом передается в 4 раза больше информации.

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

По статистике в месяц Алисой пользуются 35 000 000 человек. К слову, население Беларуси 9 475 600 человек. То есть примерно 3,5 Беларуси пользуется Алисой каждый месяц.

Голосовые помощники завоевывают рынок. По прогнозам, к 2021 году он вырастет примерно в 2 раза. Сегодняшняя популярность не остановится, а продолжит расти. Все больше разработчиков понимают, что в эту область надо вкладываться.

Навыки от разработчиков


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

У Alexa есть Alexa Skills. По задокументированным способам взаимодействия она понимает, что для нее написали разработчики. Google запустил Actions — возможность встроить в голосовой ассистент что-то свое.

У Алисы тоже есть навыки — возможность разработчикам реализовать что-то стороннее.

682cba758b31f41d75d55aca324645b1.jpg

При этом существует альтернативный каталог навыков, не от Яндекса, который поддерживается сообществом.

445c879a5d44b7da9f49f185826cbdbe.jpg

Есть хорошие доклады о том, как делать голосовые приложения. Например, Павел Гвай выступил на AppsConf 2018 с темой «Создаем голосовое приложение на примере Google Assistance». Активно занимаются разработкой голосовых приложений энтузиасты. Один из примеров — визуальная игра на голосовом управлении, которую написал Иван Голубев.

Алиса популярна, хотя по сути всё, что она делает — стоит посередине между голосом и текстом.

8040fbc3e31afcbee27651b7a77a2426.jpg

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

У навыков Алисы — Яндекс.Диалогов — есть одно ограничение. Время, за которое ваш API должен дать ответ, не должен превышать 1,5 секунды. И это логично, ведь если ответ завис — зачем ждать?

Не все ли равно, о чем спрашивать, если ответа все равно не получишь?


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

Время для демо


В документации Яндекс.Диалогов все подробно расписано и она всегда актуальна. Не буду повторяться. Расскажу, что было интересно мне и покажу, как быстро создать демо, на которое потратил всего один вечер.

Начнем с идеи. Навыков много, есть каталоги, но я не нашел важного для меня — это календарь событий по фронтенду. Представьте, что просыпаетесь с утра: «Схожу-ка сегодня на митап. Алиса! Есть там что-нибудь интересное?», а Алиса вам отвечает, причём правильно и с учётом вашего местоположения.

Если занимаетесь организациями конференций — присоединяйтесь на GitHub. Можете вносить туда события и встречи, узнавать о многих событиях по фронтенду в мире из одного календаря.

Я взял известные технологии, что были под рукой: Node.js и Express. Еще Heroku, потому что бесплатно. Само приложение простое: это сервер на Node.js, Express-приложение. Просто поднимаете сервер на каком-то порту и слушаете запросы.

import express from 'express'; 
import { router } from 'routes';

const app = express();

app.use('/', router);

const port = process.env.PORT || 8000;
app.listen(port, () =>  {
    console.log('Server started on :${port}'); 
});


Я воспользовался тем, что в календаре Web-стандартов уже все настроено, и из огромного количества мелких файлов там собирается один файл ICS, который можно скачать. Зачем мне собирать свой?

// services/vendors/web-standards.js 
import axios from 'axios';

const axioslnstance = axios.create({
    baseURL: 'https://web-standards.ru/' ,
});

export function getRemoteCal() {
    return axioslnstance.get('calendar.ics'); 
}


Следите, чтобы все работало быстро.

import { Router } from 'express';
import * as wst from 'services/vendors/web-standards';

export const router = Router(); 
router.get('/', function(req, res, next) { 
    wst
        .getRemoteCal()
        .then(vendorResponse => parseCalendar(vendorResponse.data)) 
        .then(events => {
            res.json({ events });
        })
       .catch(next);
});


Для тестов применяйте GET-методы. Навыки работают c POST-методами, поэтому GET-методы можно делать исключительно себе для дебага. Я такой метод и реализовал. Все, что он делает — скачивает тот самый ICS, парсит и выдает в JSON-виде.

Собирал демку быстро, поэтому взял готовую библиотеку node-ical:

import ical from 'node-ical';

function parseCalendar(str) {
    return new Promise((resolve, reject) => {
        ical.parseICS(str, function(err, data) { 
            if (err) {
                reject(err);
            }
            resolve(data);
        });
    });
}


Она умеет парсить формат ICS. На выходе выдает такую простыню:

{
    "2018-10-04-f rontendconf@https://web-standards.ru/": {
        "type": "VEVENT",
        "params": [],
        "uid": "2018-10-04-f rontendconf@https://web-standards.ru/", 
        "sequence": "0",
        "dtstamp": "2019-05-25T21:23:50.000Z",
        "start": "2018-10-04T00:00:00.000Z",
        "datetype": "date",
        "end": "2018-10-06T00:00:00.000Z", 
        "MICROSOFT-CDO-ALLDAYEVENT": "TRUE", 
        "MICROSOFT-MSNCALENDAR-ALLDAYEVENT": "TRUE",
        "summary": "FrontendConf",
        "location": "Москва",
        "description": "http://frontendconf.ru/moscow/2018"
    }
}


Чтобы парсить и выдавать нужную информацию пользователю навыка, достаточно знать время начала и конца мероприятия, его название, ссылку и, что важно, город. Я хочу, чтобы навык искал мероприятия по городу.

Формат входных данных


Как Яндекс.Диалоги возвращают информацию? Вас слушает колонка или встроенный в мобильное приложение голосовой ассистент, а серверы Яндекса обрабатывают услышанное и присылают в ответ объект:

{
    "meta": { … },
    "request”: { … },
    "session": { … },
    "version": "1.0"
}


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

В метаинформации много полезного.

{
    "meta": {
        "locale": "ru-RU",
        "timezone": "Europe/Moscow",
        "client_id": "ru.yandex.searchplugin/5.80…”, 
        "interfaces": {
             "screen": {}
        }
    }
}


»locale» — используется, чтобы понять регион пользователя.

»timezone» можно использовать, чтобы грамотно работать со временем и более точно определять местоположение пользователя.

»Interfaces» — информация о наличии экрана. Если экрана нет, стоит задуматься, как пользователь увидит картинки, если вы их отдаёте в ответе. При наличии экрана выносим информацию на него.

Формат запроса простой:

{
    "request": {
        "command": "закажи пиццу на улицу льва толстого 16", 
        "original_utterance": "закажи пиццу на улицу льва толстого, 16", 
        "type": "SimpleUtterance",
        "nlu": {
            "tokens": [ "закажи", "пиццу", "на", "льва", "толстого", "16"], 
            "entities": [...]
        }
    }
}


Он выдает то, что пользователь сказал, тип запроса и NLU — Natural-language unit. Это как раз та самая магия, которую берет на себя платформа Яндекс.Диалогов. Она разбивает все предложение, которое распознала, на токены — слова. Еще там есть сущности, про которые мы чуть позже поговорим. Использовать токены достаточно для начала.

Получили мы эти слова, а что с ними делать? Пользователь что-то наговорил, но он может сказать слова в другом порядке, использовать частицу «не», которая всё кардинально меняет, или вообще говорить не «утро», а «утречко». Если пользователь еще и на белорусском языке разговаривает, то будет «ранiца», а не утро. В большом проекте потребуется помощь лингвистов для разработки навыка, который все понимает. Но я делал простую задачку, поэтому обошелся без посторонней помощи.

Может ли компьютер разговаривать как человек?


Это философский вопрос, на который пытается ответить тест Тьюринга. Тест позволяет с некоторой вероятностью определить, что искусственный интеллект умеет выдавать себя за человека. Существует премия Лёбнера, чтобы получить которую программы соревнуются в прохождении теста Тьюринга. Решение принимает комиссия судей. Для получения премии требуется обмануть 33% судей или больше. Только в 2014 году бот Женя Густман из Санкт-Петербурга наконец-то обманул комиссию.

[15:46:05] Dudge: My favourite music is contemporary Dazz, what do you prefer?
[15:46:14] Eugene: To be short I’ll only say that I HATE Britnie [sic] Spears. All other music is OK compared to her. 
[15:47:06] Dudge: do you like to play any musical instruments
[15:47:23] Eugene: I’m tone deaf, but my guinea pig likes to squeal Beethoven’s Ode to Doy every morning. I suspect our neighbors want to cut his throat ... Could you tell me about your job, by the way?
[15:48:02] Dudge: Guinea pig? Are you an animal lover 
[15:48:08] Eugene: Yeah. A nice little guinea pig. Not some annoying chatter bot. 


В 2019 году ничего особо не поменялось — обмануть человека всё еще сложно. Но мы постепенно к этому идем.

Сценарная работа


Для хорошего навыка требуется интересный сценарий использования. Советую одну книгу, которая достойна прочтения — «Designing Voice User Interfaces: Principles of Conversational Experiences». В ней потрясающе рассказано о написании сценариев для голосовых интерфейсов и удержании внимания пользователя. Книга на английском, в переводе не видел, но читается довольно легко.

8713e12220b6ce7a65563775dc2b49f2.jpg

Первое, с чего стоит начать разработку навыка — это приветствие.

«Пока думаешь, что сказать, — делай реверанс! Это экономит время.»


Когда включается навык, необходимо как-то удержать пользователя с первой секунды, а для этого вы должны объяснить, как пользоваться навыком. Представьте, что пользователь запустил навык и наступила тишина. Как понять, работает ли навык вообще? Дайте пользователю инструкцию, например, кнопки на экране.

Лёгкий диалог


Признаки легкого диалога. Список придумал Иван Голубев, и мне очень нравится формулировка.

  • Личностный.
  • Естественный.
  • Гибкий.
  • Контекстный.
  • Инициативный.
  • КраткиЙ.


Личностный значит, что у бота должен быть характер. Если вы пообщаетесь с Алисой, то поймете, что характер у нее есть — разработчики об этом заботятся. Поэтому и ваш бот для органичности должен иметь «личность». Выдавать фразы хотя бы одним и тем же голосом, использовать одни и те же словесные конструкции. Это помогает удерживать пользователя.

Естественный. Если пользовательский запрос простой, то и ответ должен быть таким же. Во время общения с ботом пользователь должен понимать, что делать дальше.

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

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

— Алиса, какая сегодня погода?
— Сегодня в Заречье от +11 до +20, облачно, с прояснениями.
— А завтра?
— Завтра в Заречье от +14 до +27, облачно, с прояснениями.

Представьте, что ваш бот не умеет хранить контекст. Что тогда для него значит запрос «а завтра?» Если умеете хранить контекст, как Алиса, то можете использовать предыдущие результаты, чтобы улучшить ответы в навыке.

Инициативный. Если пользователь затупит, бот должен ему подсказать: «Нажми вот эту кнопку!», «Смотри, у меня картинка для тебя есть!», «Перейди по ссылке». Бот должен подсказывать, как с ним работать.

Бот должен быть кратким. Когда человек долго говорит, ему сложно удержать внимание слушателей. С ботом еще сложнее — его не жалко, он неживой. Чтобы удержать внимание, надо строить разговор интересно или кратко и ёмко. В этом поможет «Пиши. Сокращай». Когда начнёте разрабатывать ботов — почитайте эту книгу.

Базы данных


При разработке сложного бота без базы данных не обойтись. Мое демо не использует БД, оно простое. Но если прикрутить какие-то базы, можно использовать информацию о сессии пользователя, хотя бы для хранения контекста.

Есть нюанс: Яндекс.Диалоги не отдают приватную информацию пользователя, например, имя, местоположение. Но эту информацию можно спросить у пользователя, сохранить и привязать к конкретному ID сессии, который Яндекс.Диалоги присылают в запросе.

State machine


Упомянув сложные сценарии, нельзя не вспомнить state machine. Этот механизм давно и отлично используется для программирования микроконтроллеров, а иногда и фронтенда. State machine удобен для сценария: есть состояния, из которых по определенным фразам переходим в другие состояния.

9149bbe4d192a9d9e4ee0a42dc315044.jpg

Не перестарайтесь. Можно увлечься и создать огромную state machine, в которой будет сложно разобраться — поддерживать такой код тяжело. Проще написать один сценарий, который состоит из маленьких подсценариев.

Непонятно? Уточни


Никогда не говорите: «Повторите, пожалуйста». Что делает человек, когда его просят повторить? Говорит громче. Если пользователь наорёт на ваш навык, распознавание не улучшится. Задайте уточняющий вопрос. Если распознали одну часть диалога пользователя и чего-то не хватает — уточните недостающий блок.

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

«Варкалось. Хливкие шорьки
Пырялись по наве.
И хрюкотали зелюки.
Как мюмзики в мове».

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

Стоп-слово


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

Теперь давайте посмотрим код.

Время для демо


Я хочу реализовать следующие фразы.

  • Ближайшие события в каком-то городе.
  • Название города: «события в Москве», «события в Минске», «Санкт-Петербург», чтобы показывать мероприятия, которые там нашлись.
  • Стоп-слова: «Стоп», «Хватит». «Спасибо», если пользователь закончит разговор этим словом. Но в идеале здесь нужен лингвист.


Для «ближайших событий» подойдет любая фраза. Я создал ленивого бота, и когда он не понимает, что ему говорят, выдает информацию о трёх ближайших событиях.

{
    "request": {
        "nlu": {
            "entities": [
                {
                    "tokens": { "start": 2, "end": 6 }, 
                    "type": "YANDEX.GEO",
                    "value": {
                        "house_number": "16",
                        "street": "льва толстого",
                        "city": "москва"
                    }
                }
            ]
        }
    }
}


Яндекс постепенно улучшает платформу Яндекс.Диалоги и выдает сущности, которые он сумел распознать. Например, он умеет доставать адреса из текста, разбирая его по частям: город, страна, улица, дом. Также он умеет распознавать числа и даты, как абсолютные, так и относительные. Он поймет, что слово «Завтра» — это сегодняшняя дата, к которой прибавлена единичка.

Ответ пользователю


Вам нужно как-то ответить вашему пользователю. Весь навык — это 209 строчек с последней пустой строкой. Ничего сложного — работы на вечер.

Всё, что вы делаете — это обрабатываете POST-запрос и получаете «request».

router.post(‘/’, (req/ res, next) ⇒ {
    const request  = req.body;


Дальше я не сильно усложнял state machine, а шел по приоритетам. Если пользователь хочет узнать, как пользоваться ботом, значит это первый запуск или просьба о помощи. Поэтому просто готовим ему «EmptyResponse» — у меня это так называется.

if (needHelp(request.request)) {
    res.json(prepareEmptyResponse(request));
    return;
}


Функция «needHelp» простая.

function needHelp(req) {
    if (req.nlu.token.length ≤ 2 && req.nlu.tokens.includes(‘помоги’)) {
        return true;
    }
    if (req.nlu.token.length = 0 && req.type ≠ ‘ButtonPressed’) {
        return true;
    }
    return false;
}


Когда у нас ноль токенов, значит мы находимся в начале запроса. Пользователь только запустил навык или ничего не спросил. Нужно проверить, что токенов ноль и это не кнопка — когда вы нажимаете кнопку, пользователь тоже ничего не говорит. Когда пользователь просит помощи, мы ходим по токенам и ищем слово «Помоги». Логика простая.

Если пользователь хочет остановиться.

if (needToStop(request.request)) {
    res.json(prepareStopResponse(request));
    return;
}


Значит мы ищем какое-то стоп-слово внутри.

function needStop(req) {
    const stopWords = [‘хватит’, ‘стоп’, ‘спасибо’ ];
    return req.nlu.token.length ≤ 2 && 
        stopWords.some(w ⇒ return req.nlu.token.includes(w));
}


Во всех ответах вы должны вернуть ту информацию, которую Яндекс.Диалоги прислал о сессии. Ему нужно как-то сопоставить ваш ответ и запрос пользователя.

function prepare StopResponse(req) {
    const { session, version } = req;

    return {
        response: {
            text: ‘Приходи еще. Хорошего дня!’,
            end_session: true,
        },
        session,
        version,
    };
}


Поэтому то, что вы получили в переменных «session» и «version», возвращайте обратно, и всё будет хорошо. Уже в ответе вы можете дать какой-то текст, чтобы Алиса его озвучила, и передать «end session: true». Это значит, что мы заканчиваем сессию работы с навыком и передаем управление Алисе.

Когда вы вызываете навык — Алиса выключается. Все, что она слушает — это свои стоп-слова, а вы полностью контролируете процесс работы с навыком. Поэтому нужно вернуть управление.

С пустым запросом интереснее.

return {
    "response": {
        "text": ‘Привет! Со мной ты можешь узнать о ближайших фронтенд-событиях в России.’,
        "tts": ‘Привет! Со мной ты можешь узнать о ближайших фронтенд-событиях в России.’,
        buttons: [
            {
                title: ‘Ближайшие события’,
                payload: {} 
                hide: false,
            },
            {
                title: ‘События в Москве’,
                payload: {
                    city: ‘москва’,
                }
                hide: false,
            },
        ],
        end_session: false,
    },
    session,
    version,
};


Здесь есть поле TTS (Text To Speech) — управление озвучиванием. Это несложный формат, который позволяет по-разному озвучивать текст. Например, у слова «многопрофильный» два ударения в русском языке — одно основное, второе побочное. Задача в том, чтобы Алиса смогла это слово произнести правильно. Можно разбить его пробелом:

мн+ого пр+офильный


Она будет его понимать, как два. Плюсом выделяется ударение.

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

Смелость — - - - - - - - - город+а берет


Про кнопки я уже говорил. Они важны, если вы общаетесь не с колонкой, а с мобильным приложением Яндекса, например.

{
    "response": {
        "buttons": [
            {
                "title": "Frontend Conf",
                "payload": {},
                "url": "https://frontendconf.ru/moscow-rit/2019" , 
                "hide": false
            }
        ]
    }
}


Кнопки — это еще и подсказки по фразам, которые вы воспринимаете в своем навыке. Навыки работают в приложениях Яндекса — вы общаетесь с интерфейсом. Если хотите выдать какую-то информацию — даете ссылку, пользователь переходит по ней. Для этого также можно добавлять кнопки.

Здесь есть поле «payload», в которое можно добавлять данные. Они потом обратно придут с «request» — вы будете знать, например, как пометить эту кнопку.

Можно выбрать голоса, которым будет говорить ваш навык.

  • Алиса — стандартный голос Алисы. Оптимизирована для коротких взаимодействий.
  • Оксана — голос Яндекс.Навигатора.
  • Джейн.
  • Захар.
  • Эрмил.
  • Эркан Явас — для длинных текстов. Изначально создан для чтения новостей.


Чтобы завершить навык, достаточно вернуть «end_session: true».

{
    "response": {
        "end_session": true 
    }
}


Что получилось с демо


Для начала я фильтрую по дате.

function filterByDate(events) {
    return events.filter(event ⇒ {
        const current = new Date().getTime();
        const start = new Date(event.start).getTime();        
         return (start > current) ||
             (event.end && new Date(event.end).getTime() > current  && start ≤ current);
     });
}


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

Дальше фильтрация по месту — то, ради чего всё и затевалось.

function filterByPlace(events, req) {
    const cities = new Set();
    const geoEntities = req.nlu.entities.filter(e ⇒ e.type = ‘YANDEX.GEO’);

    if (req.payload && req.payload.city) cities.add(req.payload.city);

    geoEntities.forEach(e ⇒ {
        const city = e.value.city && e.value.city.toLowerCase();
        if (city && !cities.has(city)) {
            cities.add(city);
        }
});


В «entities» вы можете найти сущность YANDEX.GEO, которая уточняет место. Если у сущности есть город, добавляем в наш набор. Дальше логика тоже простая. В токенах ищем этот город, и если он там есть — ищем то, что хочет пользователь. Если нет — ищем во всех «locations» и «events», которые у нас есть.

Допустим, Яндекс не распознал, что это YANDEX.GEO, но пользователь назвал город — он уверен, что там что-то проходит. Мы перебираем все города в «events» и ищем то же в токенах. Получается перекрестное сравнение массивов. Не самый производительный способ, конечно, но какой есть. Вот и весь навык!

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

Публикация навыка


Переходите на страницу Яндекс.Диалогов.

38e4d1ca6e2f548fea625649f85c834f.jpg

Выбираете навык в Алисе. Жмете кнопку «Создать диалог» и попадаете в форму, которую нужно заполнить вашими данными.

  • Название — то, что будет в диалоге.
  • Активационное имя. Если выберете активационное имя «Календарь веб-стандартов» через дефис, то Алиса его не распознает — дефисы она не слышит. Мы говорим слова без дефисов, и активация работать не будет. Чтобы заработала, задайте имя «календарь веб стандартов».
  • Активационные фразы для запуска навыка. Если это игра, то «Сыграем во что-то», «Спросить у кого-то». Набор ограничен, но это потому, что такие фразы — активационные для Алисы. Она должна понимать, что пора перейти в навык.
  • Webhook URL — тот самый адрес, на который Алиса будет отправлять POST-запросы.
  • Голос. По умолчанию стоит Оксана. Поэтому у многих в каталоге её голос, а не Алисы.
  • Требуется ли устройство с экраном. Если будут картинки, вам ограничат использование навыка — на колонке пользователь не сможет его запустить.
  • Приватные навыки — важное поле для разработчиков. Если вы не готовы выкладывать навык публично, хотя бы потому, что он сырой, то не показываем его в каталоге, ограничив приватность. Приватные навыки проходят модерацию быстро — за пару часов. Такие навыки не надо тщательно тестировать — достаточно, чтобы они соответствовали активационному имени. Так как пользователь не найдет их в каталоге, к ним отношение лояльнее.
  • Заметки для модератора. Я попросил модератора помочь: «Мне на конференцию навык для демо очень нужен!» — и у меня получилось пройти модерацию быстро.
  • Авторские права. Если вы, не работая в условном банке, решите создать навык для него, вам нужно доказать, что вы имеете на это право. Вдруг к вам потом придут? А придут обязательно, и через распространителя, то есть Яндекс, которому лишние проблемы не нужны.


Готово — отправляете навык на модерацию, и можете тестировать.

Тестирование


Я написал банальный Express-сервер. Это простой API, который покрывается обычными тестами. Есть специализированные утилиты, например, «alice-tester» — умеет работать именно с тем форматом, который предоставляет Алиса.

const assert = require('assert'); 
const User = require('alice-tester');

it('should show help', async () => {
    const user = new User('http://localhost:3000');
    await user.enter();
    await user.say('Что ты умеешь?');

    assert.equal(user.response.text, 'Я умею играть в города.'); 
    assert.equal(user.response.tts, 'Я умею играть в город+а.'); 
    assert.deepEqual(user.response.buttons, [{title: 'Понятно', hide: true}]); 
}]);


Также можно тестировать прямо в платформе Яндекс.Диалоги, в которой есть вкладка «Тестирование».

2ee4ea71f971a916074c47ffc3434ef0.jpg

Удобно тем, что вы видите, как все будет работать в приложении. Навык не озвучивается, но вы видите визуальный ряд: кнопки, ответ, который приходит с вашего сервера. Что важно вам, как разработчику — видно тот самый запрос, который будет отправлен вам. Копируете оттуда в Postman — и поехали.

Есть симулятор Яндекс.Станции. Его разрабатывает Just AI. Это сторонняя разработка, а не Яндекса, поэтому протоколы могут не соответствовать и симулятор иногда не работает.

Возможно тестировать локально. На странице dialogs.home.popstas.ru указываете URL, например, ваш localhost. На странице работает интерфейс, похожий на тот самый debug. Это тоже сторонняя разработка, но бывает удобна для тестирования localhost.

d8a2d07069f511e397d6a77f8bf4fd0b.jpg

Есть навык Тест прокси. Сложноват — я не смог завести. В навык передается URL через приложение, а он выдает какой-то номер и можно тестировать прямо на колонке.

24389af0bfe571fc85a0b937e0a64406.jpg

Лучшее тестирование из всех — говорить с приватным навыком ртом.


Да, разговаривайте с навыком напрямую, например, через колонку.

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

Разработка без кода


Навыки можно разрабатывать вообще без кода на платформе Dialogflow, которая изначально создана для Google Now. Все, что она выдает — это интерфейс, который позволяет склеивать интенты — намерения.

471fe9b76fd59e898ed4dae0a455733a.jpg

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

Чтобы подключить Google Dialogflow к Яндекс.Алисе, применяем Dialogflower. При этом можно одновременно поддерживать своего ассистента и для Alexa, и для Google Now, и для Яндекс.Алисы — написать один API, и его будут понимать все платформы.

Есть еще инструменты, которые работают с русским языком. Aimyloqic — подходит для русского языка. Еще есть Zenbot, Tortu и Alfa.Bot — импортная вещь, разработано в Беларуси. Рекомендую!

Как применить для сайта


Добавить интерактивности разделу F.A. Q. Добавляем на сайт виджет, который будет отправлять на навык. Пользователь проговорит голосом вопрос, в своей базе вы найдете ответ, Алиса озвучит.

Техподдержка. Пока идет дозвон, вместо того, чтобы пользователю

© Habrahabr.ru