Яндекс Алиса

Библиотека для создания голосовых навыков для ассистента Яндекс Алиса на Kotlin.

Шаблон проекта

Здесь доступен готовый шаблон голосового навыка на JAICF, который можно запустить на сервере Heroku в один клик, а затем изменять код навыка как вам нужно, чтобы он выполнял нужные вам функции.

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

Яндекс Облако

Сервис Яндекс.Облако предоставляет бесплатный хостинг для ваших навыков Алисы. Чтобы запустить ваш навык на JAICF, воспользуйтесь готовым шаблоном.

Подключение библиотеки

Чтобы создать проект с нуля, вам нужно создать Kotlin проект и добавить в ваш файл build.gradle.kts следующие зависимости:

repositories {
    mavenCentral()
    jcenter()
}
dependencies {
    implementation(kotlin("stdlib-jdk8"))
    implementation("io.ktor:ktor-server-netty:1.3.1")

    implementation("com.just-ai.jaicf:core:$jaicfVersion")
    implementation("com.just-ai.jaicf:yandex-alice:$jaicfVersion")
}

Замените $jaicfVersion последней версией

Создание логики навыка

JAICF дает возможность описывать логику чатбота или голосового приложения в виде сценариев на Kotlin. В каждом сценарии вы можете оперировать состояниями и контекстами диалога, а также способами процессинга языка (интенты, события и регулярные выражения). Простой пример:

val MainScenario = Scenario {
    state("main") {
        activators {
            event(AliceEvent.START)
        }

        action {
            reactions.say("Майор на связи. Докладывайте.")
            reactions.alice?.image(
                url = "https://i.imgur.com/YOnWzLM.jpg",
                title = "Майор на связи",
                description = "Начните сообщение со слова \"Докладываю\""
            )
        }
    }

    state("report") {
        activators {
            regex("докладываю .+")
        }

        action {
            reactions.run {
                say("Спасибо.")
                sayRandom(
                    "Ваш донос зарегистрирован под номером ${random(1000, 9000)}.",
                    "Оставайтесь на месте. Не трогайте вещественные доказательства."
                )
                say("У вас есть еще какая-нибудь информация?")
                buttons("Да", "Нет")
            }
        }

        state("yes") {
            activators {
                regex("да")
            }

            action {
                reactions.say("Докладывайте.")
            }
        }

        state("no") {
            activators {
                regex("нет")
                regex("отбой")
            }

            action {
                reactions.sayRandom("Отбой.", "До связи.")
                reactions.alice?.endSession()
            }
        }
    }
}

Здесь показан простой пример без использования NLU. Можно использовать NLU движки (Dialogflow, Rasa и другие).

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

Создание вебхука

Создать вебхук для навыка можно, например, с помощью Ktor

val skill = BotEngine(
    scenario = MainScenario,
    activators = arrayOf(
        RegexActivator,
        BaseEventActivator,
        CatchAllActivator
    )
)

fun main() {
    embeddedServer(Netty, 8000) {
        routing {
            httpBotRouting("/" to AliceChannel(skill, "ваш OAuth token здесь"))
        }
    }.start(wait = true)
}

Если вы используете картики в вашем навыке, укажите ваш OAuth token, который можно получить здесь.

Чтобы получить URL, который доступен извне, можно использовать ngrok - ngrok http 8000. А затем использовать этот вебхук для создания навыка в консоли Яндекс Диалогов.

API Алисы

JAICF позволяет использовать API любой платформы напрямую, не ограничивая разработчика только функционалом самого фреймворка. Далее описаны методы API Алисы, которые можно использовать для использования спцифичных для этой платформы функций.

Специфичные для Алисы реакции доступны через reactions.alice?

Простые ответы

Простые текстовые и голосовые ответы можно формировать функциями reactions.say(...).

action {
    // Единый текст для отображения и синтеза
    reactions.say("Майор на связи. Докладывайте.")

    // Текст для отображения и для синтеза могут быть разными
    reactions.alice?.say(
        text = "Майор на связи. Докладывайте.",
        tts = "докл+адывайте")
}

Подробнее про текстовые ответы читайте в документации Яндекс Диалогов

Случайные ответы

Чтобы разнообразить ответы, можно возвращать случайные ответы:

action {
    reactions.sayRandom(
        "Ваш донос зарегистрирован под номером ${random(1000, 9000)}.",
        "Оставайтесь на месте. Не трогайте вещественные доказательства."
    )
}

Кнопки и ссылки

Для добавления кнопок используйте методы reactions.buttons(...)

action {
    // Добавит простые кнопки, которые пропадут после нажатия
    reactions.buttons("Да", "Нет")

    // Добавит кнопки-сслыки, которые не пропадают после нажатия
    reactions.alice?.links(
        "Сайт" to "https://framework.just-ai.com",
        "Документация" to "https://github.com/just-ai/jaicf-kotlin/wiki"
    )

    // Добавит кнопку с произвольной конфигурацией
    reactions.alice?.buttons(
        Button(
            title = "...",
            payload = JsonObject(),
            url = "...",
            hide = false
        )
    )
}

Подробнее про кнопки читайте в документации Яндекс Диалогов

Звуки

Чтобы Алиса проиграла нужные вам звуки, сперва их нужно загрузить в консоль Яндекс Дислогов. Прдробнее об этом в документации Яндекс Диалогов.

Затем можно использовать идентификаторы звуков при создании ответа навыка:

action {
    reactions.alice?.audio("идентификатор звука")
}

Картинки

Алиса позволяет показывать картинки в ответе от вашего навыка. Но перед этим требуется загрузить картинки в консоли разработчика и затем использовать полученные индентификаторы.

Эта библиотека загружает картинки автоматически, чтобы вам не приходилось делать это каждый раз.

action {
    // Картинка без подписи
    reactions.image("https://i.imgur.com/YOnWzLM.jpg")

    // Картинка с заголовком, описанием и кнопкой
    reactions.alice?.image(
        url = "https://i.imgur.com/YOnWzLM.jpg",
        title = "Майор на связи",
        description = "Начните сообщение со слова \"Докладываю\"",
        button = Button(...)
    )
}

Как видите, вы можете использовать URL картинки, не загружая ее заранее в консоли Яндекс Диалогов. Это позволяет создавать навыки, которые отображают дианмические картинки.

Если же вы хотите использовать идентификаторы картинок вместо URL, то можете делать это так:

action {
    reactions.alice?.image(
        Image(
            imageId = "идентификатор картинки",
            title = "Майор на связи",
            description = "Начните сообщение со слова \"Докладываю\"",
            button = Button(...)
        )
    )
}

Если есть небходимость получить идентификатор картинки, предварительно загрузив ее при необходимости, можно использовать API:

reactions.alice?.api?.getImageId("https://i.imgur.com/YOnWzLM.jpg")

Подробнее про картинки читайте в документации Яндекс Диалогов.

Списки

Списки с картинками можно добавить так

action {
    // Создание списка
    reactions.alice?.itemsList(
        header = "Заголовок",
        footer = ItemsList.Footer(text = "...", button = Button(...))
    )
    .addImage(Image(...))  // Добавление элемента списка
    .addImage(Image(...))  // Добавление элемента списка
}

Завершение сессии

Чтобы Алиса завершила диалог с пользователем

action {
    reactions.sayRandom("Отбой.", "До связи.")
    reactions.alice?.endSession()
}

Детали запроса

Алиса с каждым запросом передает дополнительные данные о пользователе и о самом запросе.

Подробно об этом написано в документации Яндекс Диалогов.

Чтобы получить эти данные, вы можете использовать request.alice? в вашем сценарии:

action {
    request.alice?.meta // Метаинформация (локаль, таймзона и тд)
    request.alice?.session // Данные сессии
    request.alice?.request // Данные запроса (NLU, payload от кнопки и тд)
}

Авторизация пользователя

API Алисы поддерживает OAuth авторизацию пользователей. Вы можете запустить процесс авторизации так

action {
    reactions.alice?.startAccountLinking()
}

После завершения авторизации сценарий получит событие AliceEvent.ACCOUNT_LINKING_COMPLETE, на который нужно отреагировать соответственно

state("auth") {
    activators {
        event(AliceEvent.ACCOUNT_LINKING_COMPLETE)
    }
    action {
        ...
    }
}

Бибилиотека автоматически записывает заголовок с токеном авторизации в поле request.alice?.accessToken.

NLP в Алисе

Яндекс Диалоги предоставляют возможность] обрабатываеть запросы пользователя на естественном языке. Подробнее читайте в документации Яндекс Диалогов.

Чтобы использовать этот функционал в вашем сценарии, добавьте в настройки вашего агента активатор AliceIntentActivator, например:

val skill = BotEngine(
    scenario = MainScenario,
    activators = arrayOf(
        AliceIntentActivator,
        BaseEventActivator,
        CatchAllActivator
    )
)

После этого вы сможете обрабатывать интенты Алисы и ваши собственные интенты в вашем сценарии:

state("repeat") {
    activators {
        intent(AliceIntent.REPEAT)
    }

    action {
        reactions.say("Повторяю...")
    }
}

Слоты доступны с помощью activator.alice?.slots:

action {
    activator.alice?.run {
        val firstAmount = slots["first_amount"]
        val secondAmount = slots["second_amount"]
    }
}

Хранение состояния

JAICF уже осуществляет автоматическое хранение состояния с помощью различных БД. Но вы также можете использовать механизм хранения состояния, доступный в Яндекс Дислогах.

Это позволяет обойтись без развертывания дополнительной БД, если она вам не нужна. Функциональность JAICF остается прежней.

Чтобы использовать хранение состояния средствами Яндекс Диалогов, нужно лишь указать это при создании канала AliceChannel:

fun main() {
    embeddedServer(Netty, System.getenv("PORT")?.toInt() ?: 8080) {
        routing {
            httpBotRouting("/" to AliceChannel(skill, useDataStorage = true))
        }
    }.start(wait = true)
}

Сам код сценария при этом остается прежним - вы все так же можете сохранять и читать данные из context.client и context.session для клиенстких и сессионных данных соответственно.

action {
    context.session["first"] = Product(...)
    context.client["last_reply"] = "Предыдущий ответ"

    val second = context.session["second"] as? Product
}

Вы можете хранить любые POJO, так как JAICF автоматически сериализует и десериализует ваши объекты, поэтому не нужно делать этого отдельно без необходимости.

Вопросы и предложения

Если вы нашли баг, то можете написать о нем в issues или предложить вашу реализацию и отправить pull request.

Также вы можете присоединиться к сообществу разработчиков в Slack, чтобы задавать вопросы по данной библиотеке.