Делаем навык для Алисы. Alice-ktx

от автора

Alice-ktx — это библиотека на Kotlin, упрощающая разработку навыков Алисы из Яндекс.Диалогов. В этой статье мы рассмотрим основные возможности библиотеки.

Установка

Для начала, добавьте библиотеку в зависимости вашего проекта

dependencies {     implementation("io.github.danbeldev:alice-ktx:0.0.3") }

Первый навык, Эхо‑бот

fun main() {     skill {         id = "..."         webServer = ktorWebServer {             port = 8080             path = "/alice"         }          dispatch {             message({ message.session.new }) {                 response {                     text = "Привет!"                 }             }              message {                 response {                     text = message.request.command.toString()                 }             }         }     }.run() }
  • id — Уникальный идентификатор скилла, читайте здесь.

  • webServer — Конфигурация приложение с использованием Ktor.

  • port — Порт, на котором будет запущено приложение. В данном случае используется порт 8080.

  • path — Путь, по которому приложение будет доступен. В данном случае это /alice.

message({ message.session.new }) {     response {         text = "Привет!"     } }

Этот блок кода обрабатывает новые сессии. Если сессия новая message.session.new, то в ответ отправляется текст «Привет!».

message {     response {         text = message.request.command.toString()     } }

Этот блок кода обрабатывает все остальные сообщения. В ответ отправляется текст запроса пользователя.

Мидлвари

Мидлварь — это код, который активируется при каждом событии, полученном от API Алисы.

Есть два типа Мидлвари

  1. Внешняя (outer) область — вызывается перед обработкой фильтров (innerMiddleware).

  2. Внутренняя (inner) область — вызывается после обработки фильтров, но перед обработчиком (outerMiddleware).

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

Пример

dispatch {   // ...   outerMiddleware {     if(message.session.user?.userId == null)         response { text = "У вас нет аккаунта в Яндексе." }     else         null   }   // ... }

Обработка исключений

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

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

Пример

  • Обработка конкретного исключения.

responseFailure(ArithmeticException::class) {     response {         text = "Произошла арифметическая ошибка"     } }
  • Общий обработчик исключений.

responseFailure {     response {         text = "Произошла ошибка"     } }
  • Обработка ошибок по условию.

responseFailure({ message.session.new }) {     response {         text = "В начале сессии произошла ошибка"     } }

Dialog API

Чтобы получить, загрузить и удалить загруженные изображения и звуки, надо передать OAuth Token при создании DialogApi.

skill {     // ...     dialogApi = ktorYandexDialogApi {         oauthToken = "..."     }     // ... }.run()

Для каждого аккаунта Яндекса на Диалоги можно загрузить не больше 100 МБ картинок и 1 ГБ аудио. Чтобы узнать, сколько места уже занято, используйте этот метод.

dialogApi.getStatus()

Все доступные методы API.

interface DialogApi {     suspend fun getStatus(): Response<Status>      suspend fun uploadImage(url: String): Response<ImageUpload>     suspend fun uploadImage(file: File): Response<ImageUpload>     suspend fun getAllImages(): Response<Images>     suspend fun deleteImage(id: String): Response<Unit>      suspend fun uploadSound(file: File): Response<SoundUpload>     suspend fun getAllSounds(): Response<Sounds>     suspend fun deleteSound(id: String): Response<Unit> }

Все методы API возвращают обёртку Response<>.

sealed interface Response<T> {     data class Failed<T>(val message: String): Response<T>     data class Success<T>(val data: T): Response<T> }

Машина состояний

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

Для начала определите возможные состояния в вашем навыке

enum class InfoState {     SET_NAME,     SET_AGE,     SET_INFO }

Начальное Состояние, когда начинается новая сессия, установите начальное состояние

message({ message.session.new }) {     state.setState(InfoState.SET_NAME.name)     response {         text = "Добро пожаловать в навык, как вас зовут?"     } } 
  • Условие: Обрабатывается при начале новой сессии.

  • Действие: Устанавливает состояние в SET_NAME и запрашивает у пользователя имя.

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

message({ state == InfoState.SET_NAME.name }) {     val username = message.request.originalUtterance.toString()     state.updateData("name" to username)     state.setState(InfoState.SET_AGE.name)     response {         text = "Рад познакомиться $username, сколько вам лет?"     } } 
  • Условие: Обрабатывается, если текущее состояние SET_NAME.

  • Действие: Сохраняет имя в состоянии, устанавливает следующее состояние SET_AGE, и запрашивает возраст.

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

message({ state == InfoState.SET_AGE.name }) {     val age = message.request.originalUtterance.toString()     state.updateData("age" to age)     state.setState(InfoState.SET_INFO.name)     response {         text = "Супер, расскажите о себе"     } } 
  • Условие: Обрабатывается, если текущее состояние SET_AGE.

  • Действие: Сохраняет возраст в состоянии, устанавливает следующее состояние SET_INFO, и запрашивает дополнительную информацию.

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

message({state == InfoState.SET_INFO.name}) {     val info = message.request.originalUtterance.toString()     val data = state.getData()     state.clear()     response {         text = "Вот что мне удалось узнать\n\nИмя-${data["name"]}\nВозраст-${data["age"]}\nИнформация-$info"         endSession = true     } } 
  • Условие: Обрабатывается, если текущее состояние SET_INFO.

  • Действие: Формирует текст ответа на основе собранной информации, очищает состояние и завершает сессию.

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

    Кнопки и альбомы

    Для того чтобы добавить кнопку используйте метод button.

response {     text = "Выберите тип"     SchedulesType.entries.forEach {         button {             text = it.name             payload = mapOf("schedule_type" to it.name)         }     } }

Существует три типа альбомов

response {     text = "CARD ITEMS LIST"     cardItemsList {         header = "HEADER"         repeat(10) { index ->             item {                 imageId = IMAGE_ID                 title = "#${index + 1}"             }         }         footer {             text = "Footer text"             mediaButton {                 text = "Click"             }         }     } }
response {     cardBigImage {         imageId = IMAGE_ID         title = "CARD BIG IMAGE"         mediaButton {             text = "Open url"             url = "https://ya.ru"         }     } }
response {     cardImageGallery {         repeat(10) { index ->             item {                 imageId = IMAGE_ID                 title = "#${index + 1}"             }         }     } }

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


ссылка на оригинал статьи https://habr.com/ru/articles/834264/


Комментарии

Добавить комментарий

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