Разработка сценария
Это часть серии статей, посвященной созданию бота с обращением к API сервиса OpenWeatherMap.
- Настройка конфигурационного файла
- Работа с HTTP-запросом к OpenWeatherMap API
- Разработка сценария (вы находитесь здесь)
- Тестирование
На этом шаге мы напишем сценарий для бота, который будет присылать текущую погоду в запрашиваемом городе.
Этапы работы сценария:
- Бот посылает приветственное сообщение и просит клиента ввести название города.
- Клиент вводит название города. Например, Москва или Какая сегодня погода в Москве.
- Посредством сущности
город
будет выделено название города, которое затем будет приведено к начальной форме. - Выделенный город будет передан в качестве аргумента в функцию обращения к API-сервису, написанную на предыдущем шаге.
- 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
.
Теперь добавьте в поле Тренировочные фразы фразу @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
.
Далее перейдем к тестированию бота.