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}"
            }
        }
    }
}

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

Комментарии (2)


  1. Hinedes
    07.08.2024 04:14
    +1

    Крутая тема! Спасибо автор, обязательно попробую.


  1. ArtyomOchkin
    07.08.2024 04:14

    Благодарю! Очень полезная информация. Как раз есть планы создать навык для Алисы, чтобы использовать умные лампы, доступные по Bluetooth. Жаль, но не хватает кармы, чтобы нажать "Поднять репутацию" вам.