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 = "Вот что мне удалось узнать\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)
}
}
}
Существует три типа альбомов
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}"
}
}
}
}
Полезные ссылки
Комментарии (2)
ArtyomOchkin
07.08.2024 04:14Благодарю! Очень полезная информация. Как раз есть планы создать навык для Алисы, чтобы использовать умные лампы, доступные по Bluetooth. Жаль, но не хватает кармы, чтобы нажать "Поднять репутацию" вам.
Hinedes
Крутая тема! Спасибо автор, обязательно попробую.