Перейти к основному содержимому

Разработка сценария

Это часть серии статей, посвященной созданию бота с обращением к API сервиса OpenWeatherMap.

  1. Настройка конфигурационного файла
  2. Работа с HTTP-запросом к OpenWeatherMap API
  3. Разработка сценария (вы находитесь здесь)
  4. Тестирование

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

Этапы работы сценария:

  1. Бот посылает приветственное сообщение и просит клиента ввести название города.
  2. Клиент вводит название города. Например, Москва или Какая сегодня погода в Москве.
  3. Посредством сущности город будет выделено название города, которое затем будет приведено к начальной форме.
  4. Выделенный город будет передан в качестве аргумента в функцию обращения к API-сервису, написанную на предыдущем шаге.
  5. API-сервис вернет ответ. Полученные данные будут использованы для вывода сообщения о текущей погоде с рекомендациями.

Создание сценария

Создайте файл main.sc в папке src. В нем будет сценарий работы бота:

require: functions.js

theme: /

state: Start
q!: $regex</start>
a: Привет! Я электронный помощник. Я могу сообщить вам текущую погоду в любом городе. Напишите город.

state: GetWeather
intent!: /geo
script:
var city = $caila.inflect($parseTree._geo, ["nomn"]);
openWeatherMapCurrent("metric", "ru", city).then(function (res) {
if (res && res.weather) {
$reactions.answer("Сегодня в городе " + capitalize(city) + " " + res.weather[0].description + ", " + Math.round(res.main.temp) + "°C" );
if(res.weather[0].main == 'Rain' || res.weather[0].main == 'Drizzle') {
$reactions.answer("Советую захватить с собой зонтик!")
} else if (Math.round(res.main.temp) < 0) {
$reactions.answer("Бррррр ну и мороз")
}
} else {
$reactions.answer("Что-то сервер барахлит. Не могу узнать погоду.");
}
}).catch(function (err) {
$reactions.answer("Что-то сервер барахлит. Не могу узнать погоду.");
});

state: CatchAll || noContext=true
event!: noMatch
a: Извините, я вас не понимаю, зато могу рассказать о погоде. Введите название города
go: /GetWeather

В начале сценария под тегом require подключаем дополнительный файл functions.js, который мы создали ранее в разделе HTTP-запрос.

Сценарий работы бота состоит из следующих стейтов:

  • Start — начало работы. Здесь бот приветствует клиента и сообщает, чем он будет полезен.
  • GetWeather — вывод информации о текущей погоде в запрашиваемом городе.
  • CatchAll — стейт, предусмотренный для случаев, когда сообщение клиента не подходит ни под один описанный стейт.

Структура сценария

Приветствие

state: Start
q!: $regex</start>
a: Привет! Я электронный помощник. Я могу сообщить вам текущую погоду в любом городе. Напишите город.

В стейте Start запускается сценарий. Бот посылает приветственное сообщение и предлагает назвать город, в котором интересно узнать погоду.

Текущая погода

state: GetWeather
intent!: /geo
script:
var city = $caila.inflect($parseTree._geo, ["nomn"]);
openWeatherMapCurrent("metric", "ru", city).then(function (res) {
if (res && res.weather) {
$reactions.answer("Сегодня в городе " + capitalize(city) + " " + res.weather[0].description + ", " + Math.round(res.main.temp) + "°C" );
if(res.weather[0].main == 'Rain' || res.weather[0].main == 'Drizzle' || res.weather[0].main == 'Clouds') {
$reactions.answer("Советую захватить с собой зонтик!")
} else if (Math.round(res.main.temp) < 0) {
$reactions.answer("Бррррр ну и мороз! Одевайтесь потеплее!")
}
} else {
$reactions.answer("Что-то сервер барахлит. Не могу узнать погоду.");
}
}).catch(function (err) {
$reactions.answer("Что-то сервер барахлит. Не могу узнать погоду.");
});

Настройка слот-филлинга

Чтобы обработать название города, который ввел клиент, создадим интент /geo. Перейдите на вкладку NLU > Интенты, расположенную в боковом меню, и создайте интент.

Для получения информации о текущей погоде нам важно получить от клиента название города. Если он не введет его, то бот отправит сообщение с просьбой ввести город. Для этого воспользуемся слот-филлингом и создадим слот geo.

Каждый слот имеет поле Сущность. Оно определяет тип данных, которые попадут в слот. Будем использовать системную сущность @mystem.geo, которая распознает географические названия. Перейдите на вкладку NLU > Сущности > Системные, расположенную в боковом меню. Найдите в списке доступных сущностей @mystem.geo и переведите переключатель в активное положение.

Подключение системной сущности

Вернитесь к полю Слоты в интенте /geo. В поле Сущность укажите сущность @mystem.geo. Переведите переключатель Обязательно в активное положение и заполните поле Вопросы уточняющими вопросами:

Введите город.
В каком городе вы хотели бы узнать прогноз погоды?

Уточняющий вопрос будет отправлен ботом в случае, когда в сообщении клиента не был распознан обязательный слот. Пока бот не получит название города, он будет продолжать отправлять по очереди уточняющие вопросы столько, сколько мы указали при настройке конфигурационного файла в параметре maxSlotRetries: 2.

Создание интента geo

Теперь добавьте в поле Тренировочные фразы фразу @mystem.geo, чтобы бот мог распознавать название городов.

подсказка
Подробнее о настройке интентов и слот-филлинга

Приведение к начальной форме

Заполненный слот будет храниться в поле $parseTree._geo. Присвоим это значение переменной city.

Клиент может ввести фразу как угодно, например, Москва или в Москве. Значение в $parseTree._geo будет записано именно так, как и ввел его клиент, но для выполнения запроса к API нам необходимо, чтобы значение было приведено к нормальной форме.

Для этого будем использовать функцию $caila.inflect, которая умеет склонять текст в любой падеж. Вызовем функцию с аргументом ["nomn"], чтобы названия городов были приведены к именительному падежу:

var city = $caila.inflect($parseTree._geo, ["nomn"]);

Вызов функции API

Перейдем к обработке запроса. Вызываем функцию openWeatherMapCurrent, которую мы создали ранее. Функция принимает 3 аргумента:

  • "metric" — параметр, использующийся для вывода температуры в градусах Цельсия;
  • "ru" — параметр, указывающий на язык вывода данных;
  • city — переменная, в которой хранится название города.

Выполним асинхронный HTTP-запрос. Функция openWeatherMapCurrent возвращает объект promise. Чтобы вывести результат, обрабатываем promise при помощи then(function (res) {}). Здесь res — переменная, содержащая ответ на запрос.

Получение ответа

Допустим, клиента интересует погода в Москве. Из сообщения будет извлечена сущность В Москве и приведена к начальной форме Москва с помощью $caila.inflect. Затем функция openWeatherMapCurrent посылает запрос серверу OpenWeatherMap с параметрами "metric", "ru", "Москва". В ответ сервер отправляет сообщение в формате JSON.

Пример ответа
{
"coord":{"lon":37.62,"lat":55.75},
"weather":[{
"id":804,
"main":"Clouds",
"description":"пасмурно",
"icon":"04d"
}],
"base":"stations",
"main":{
"temp":18,
"feels_like":16.09,
"temp_min":17,
"temp_max":18.89,
"pressure":1007,
"humidity":72},
"visibility":10000,
"wind":{
"speed":4,
"deg":180},
"clouds":{"all":100},
"dt":1600329166,
"sys":{
"type":1,
"id":9029,
"country":"RU",
"sunrise":1600311939,
"sunset":1600357345},
"timezone":10800,
"id":524901,
"name":"Москва",
"cod":200}
}

Просмотреть ответ сервера можно на вкладке Логи.

Логи сервера

Обработка ответа

Теперь из полученного ответа выведем только необходимые нам данные:

  • res.weather[0].description — выведет описание погоды, например, пасмурно;
  • Math.round(res.main.temp) — выведет температуру, округленную с помощью функции Math.round.

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

С помощью условного оператора if проверяем, есть ли ответ на запрос и есть ли в нем поле weather. Если ответ на оба вопроса положителен, клиент получает информацию о погоде с помощью функции $reactions.answer в следующем формате:

"Сегодня в городе  " + capitalize(city) + " " + res.weather[0].description + ", " + Math.round(res.main.temp) + "°C"
Дополнительные советы

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

  • В ответе сервера есть параметр, который отвечает за статус погоды res.weather[0].main. Если этот параметр будет равен значениям Rain, Clouds или Drizzle, то бот выведет сообщение:
Советую захватить с собой зонтик!
  • Если на улице температура окажется ниже нуля Math.round(res.main.temp) < 0, то бот выведет сообщение:
Бррррр ну и мороз! Одевайтесь потеплее!

Таким образом, на запрос клиента о текущей температуре в Москве бот выведет следующее сообщение:

Сегодня в городе Москва пасмурно, 18°C. Советую захватить с собой зонтик!
Обработка ошибок

Если ответ на запрос не был получен или поле weather в нем оказалось пустым, то будем выводить сообщение об ошибке с помощью условного оператора else.

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

Что-то сервер барахлит. Не могу узнать погоду.
предупреждение
Результат внешнего вызова недоступен за пределами функции-обработчика, переданной в then.

Неожиданные фразы клиента

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

state: CatchAll || noContext=true
event!: noMatch
a: Извините, я вас не понимаю, зато могу рассказать о погоде. Введите название города
go: /GetWeather

Предположим, что клиент ввел следующее сообщение: Не хочу знать твою погоду. Такое сообщение не попадет ни под один интент, поэтому активируется событие noMatch, указанное в теге event!. Бот отправит сообщение:

Извините, я вас не понимаю, зато могу рассказать о погоде. Введите название города

Затем с помощью тега go осуществит переход в стейт /GetWeather.

Далее перейдем к тестированию бота.