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 Алисы.
Есть два типа Мидлвари
-
Внешняя (outer) область - вызывается перед обработкой фильтров (
innerMiddleware
). -
Внутренняя (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 = "Вот что мне удалось узнатьnnИмя-${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)
}
}
}
Существует три типа альбомов
-
Card Items List.
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"
}
}
}
}
-
Card Big Image.
response {
cardBigImage {
imageId = IMAGE_ID
title = "CARD BIG IMAGE"
mediaButton {
text = "Open url"
url = "https://ya.ru"
}
}
}
-
Card Image Gallery.
response {
cardImageGallery {
repeat(10) { index ->
item {
imageId = IMAGE_ID
title = "#${index + 1}"
}
}
}
}
Полезные ссылки
Автор: danbel