Alice en Kotlin: convirtiendo el código en Yandex.Station



En junio, Yandex organizó un hackathon en línea entre desarrolladores de habilidades de voz. En Just AI estábamos actualizando nuestro marco de código abierto en Kotlin para admitir nuevas funciones geniales de Alice. Y fue necesario encontrar algún tipo de ejemplo simple para el README ...



sobre cómo un par de cientos de líneas de código en Kotlin se convirtieron en Yandex.



Alice + Kotlin = JAICF



Just AI tiene un marco de código abierto y completamente gratuito para desarrollar aplicaciones de voz y chatbots de texto: JAICF . Está escrito en Kotlin , un lenguaje de programación de JetBrains, que es bien conocido por todos los androides y servidores que escriben una maldita empresa (bueno, o lo reescriben desde Java). El marco tiene como objetivo facilitar la creación de aplicaciones precisamente conversacionales para varios asistentes de voz, texto e incluso telefónicos.



Yandex tiene a Alice, una asistente de voz con una voz agradable y una API abierta para desarrolladores externos. Es decir, cualquier desarrollador puede ampliar la funcionalidad de Alice para millones de usuarios e incluso obtener dinero de Yandex para ello .



Nosotros por supuestoJAICF se hizo oficialmente amigo de Alice , así que ahora puedes escribir habilidades en Kotlin. Y esto es lo que parece.



Script -> Webhook -> Diálogo





Cualquier habilidad de Alicia es un diálogo de voz entre un usuario y un asistente digital. El diálogo se describe en JAICF en forma de scripts, que luego se ejecutan en el servidor webhook, que está registrado en Yandex.Dialogues.



Guión



Tomemos una habilidad que se nos ocurrió para un hackathon. Ayuda a ahorrar dinero al comprar en tiendas. Primero, vea cómo funciona.





Aquí puede ver cómo el usuario le pregunta a Alice: "Dime qué es más rentable, ¿tantos rublos por tal y tal cantidad o tanto por eso?"



Alice lanza inmediatamente nuestra habilidad (porque se llama "Qué es más rentable") y le transfiere toda la información necesaria: la intención del usuario y los datos de su solicitud .



La habilidad, a su vez, reacciona a la intención, procesa los datos y devuelve una respuesta útil. Alice dice la respuesta y se apaga, porque la habilidad finaliza la sesión (ellos llaman a esta "habilidad de un solo paso").



Este es un escenario tan simple que, sin embargo, le permite calcular rápidamente cuánto un producto es más rentable que otro. Y al mismo tiempo gana una columna de oradores de Yandex.




¿Cómo se ve en Kotlin?
object MainScenario: Scenario() {
    init {
        state("profit") {
            activators {
                intent("CALCULATE.PROFIT")
            }

            action {
                activator.alice?.run {
                    val a1 = slots["first_amount"]
                    val a2 = slots["second_amount"]
                    val p1 = slots["first_price"]
                    val p2 = slots["second_price"]
                    val u1 = slots["first_unit"]
                    val u2 = slots["second_unit"] ?: firstUnit

                    context.session["first"] = Product(a1?.value?.double ?: 1.0, p1!!.value.int, u1!!.value.content)
                    context.session["second"] = p2?.let {
                        Product(a2?.value?.double ?: 1.0, p2.value.int, u2!!.value.content)
                    }

                    reactions.go("calculate")
                }
            }

            state("calculate") {
                action {
                    val first = context.session["first"] as? Product
                    val second = context.session["second"] as? Product

                    if (second == null) {
                        reactions.say("   ?")
                    } else {
                        val profit = try {
                            ProfitCalculator.calculateProfit(first!!, second)
                        } catch (e: Exception) {
                            reactions.say("   , .   .")
                            return@action
                        }

                        if (profit == null || profit.percent == 0) {
                            reactions.say("     .")
                        } else {
                            val variant = when {
                                profit.product === first -> ""
                                else -> ""
                            }

                            var reply = "$variant   "

                            reply += when {
                                profit.percent < 10 -> "   ${profit.percent}%."
                                profit.percent < 100 -> " ${profit.percent}%."
                                else -> "  ${profit.percent}%."
                            }

                            context.client["last_reply"] = reply
                            reactions.say(reply)
                            reactions.alice?.endSession()
                        }
                    }
                }
            }

            state("second") {
                activators {
                    intent("SECOND.PRODUCT")
                }

                action {
                    activator.alice?.run {
                        val a2 = slots["second_amount"]
                        val p2 = slots["second_price"]
                        val u2 = slots["second_unit"]

                        val first = context.session["first"] as Product
                        context.session["second"] = Product(
                            a2?.value?.double ?: 1.0,
                            p2!!.value.int,
                            u2?.value?.content ?: first.unit
                        )

                        reactions.go("../calculate")
                    }
                }
            }
        }

        fallback {
            reactions.say(",   . " +
                    "  :  , 2   230   3   400.")
        }
    }
}




El guión completo está disponible en Github .



Como puede ver, este es un objeto regular que extiende la clase Scenario de la biblioteca JAICF. Básicamente, el script es una máquina de estado, donde cada nodo es un posible estado de la conversación. Así es como implementamos el trabajo con el contexto, ya que el contexto del diálogo es un componente muy importante de cualquier aplicación de voz.



Digamos que la misma frase se puede interpretar de manera diferente según el contexto del diálogo. Por cierto, esta es una de las razones por las que elegimos Kotlin para nuestro marco: le permite crear un DSL lacónico , en el que es conveniente administrar dichos contextos anidados y transiciones entre ellos.



El estado se activa conactivador (por ejemplo, una intención ) y ejecuta el bloque de código anidado: la acción . Y dentro de la acción, puedes hacer lo que quieras, pero lo principal es devolver alguna respuesta útil al usuario o interrogar algo. Esto se hace mediante reacciones . Siga los enlaces para obtener una descripción detallada de cada una de estas entidades.



Intenciones y espacios







Una intención es una representación independiente del idioma de una solicitud de usuario. En realidad, es un identificador de lo que el usuario quiere obtener de su aplicación conversacional.



Alice aprendió recientemente cómo definir intenciones automáticamente para su habilidad si primero describe una gramática especial. Además, sabe cómo extraer los datos necesarios de la frase en forma de ranuras , por ejemplo, el precio y el volumen de los productos, como en nuestro ejemplo.



Para que todo funcione, debe describir dicha gramática y espacios . Esta es la gramática en nuestra habilidad, y estos son los espacioslo usamos en él. Esto permite que nuestra habilidad reciba en la entrada no solo una línea de la solicitud de un usuario en ruso, sino un identificador ya independiente del idioma y espacios convertidos además (el precio de cada producto y su volumen).



JAICF, por supuesto, es compatible con cualquier otro motor de UDE (por ejemplo, Caila o Dialogflow ), pero en nuestro ejemplo hemos querido utilizar esta función Alice particular, para mostrar cómo funciona.



Webhook



De acuerdo, tenemos el guión. ¿Cómo comprobamos que funciona?



Por supuesto, los seguidores del enfoque de desarrollo impulsado por pruebas apreciarán la presencia de un mecanismo de prueba automatizado incorporado para scripts interactivos en JAICF , que personalmente usamos constantemente, ya que hacemos grandes proyectos y es difícil verificar todos los cambios a mano. Pero nuestro ejemplo es bastante pequeño, así que será mejor que iniciemos el servidor de inmediato e intentemos hablar con Alice.



Para ejecutar el script, necesita un webhook , un servidor que acepta solicitudes entrantes de Yandex cuando el usuario comienza a hablar con su habilidad. El servidor no es difícil de iniciar en absoluto, solo necesita configurar su bot y colgar algún punto final en él.



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


Así es como se configura el bot: aquí describimos qué scripts se utilizan en él, dónde almacenar los datos del usuario y qué activadores necesitamos para que el script funcione (puede haber varios de ellos).



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


Pero así es como un servidor con un webhook se inicia así: solo necesita especificar qué canal en qué punto final debería funcionar. Ejecutamos el servidor JetBrains Ktor aquí, pero puede usar cualquier otro en JAICF .



Aquí hemos utilizado una característica más de Alice: almacenar datos del usuario en su base de datos interna (opción useDataStorage ). JAICF guardará y restaurará automáticamente el contexto desde allí y todo lo que nuestro script escriba allí. La serialización es transparente.



Diálogo



¡Finalmente podemos probarlo todo! El servidor se ejecuta localmente, por lo que necesitamos una URL pública temporal para que las solicitudes de Alice lleguen a nuestro webhook desde Internet. Para hacer esto, es conveniente usar la herramienta gratuita ngrok , simplemente ejecutando un comando en el terminal como ngrok http 8080







Todas las solicitudes llegarán en tiempo real a su PC, para que pueda depurar y editar el código.



Ahora puede tomar la URL https recibida y especificarla al crear un nuevo diálogo de Aliego en Yandex. Diálogos . Allí también puede probar el diálogo con texto. Pero si desea hablar con una habilidad con voz, ahora Alice puede publicar rápidamente habilidades privadas, que en el momento del desarrollo están disponibles solo para ti. Entonces, sin pasar por una larga moderación de Yandex, ya puede comenzar a hablar con su habilidad directamente desde la aplicación de Alice o desde un altavoz inteligente.







Publicación



¡Hemos probado todo y estamos listos para publicar la habilidad para todos los usuarios de Alice! Para hacer esto, nuestro webhook debe estar alojado en algún lugar de un servidor público con una URL constante. En principio, las aplicaciones en JAICF se pueden ejecutar en cualquier lugar donde se admita Java (incluso en un teléfono inteligente Android).



Ejecutamos nuestro ejemplo en Heroku . Acabamos de crear una nueva aplicación y registramos la dirección de nuestro repositorio de Github donde se almacena el código de la habilidad. Heroku construye y ejecuta todo desde la fuente. Solo necesitamos registrar la URL pública resultante en Yandex. Diálogos y envíalo todo para moderación .



Total



Este pequeño tutorial sigue los pasos del hackathon de Yandex , donde el escenario anterior " ¿Cuál es más rentable? " Ganó una de las tres estaciones de Yandex. Aquí, por cierto, puedes ver cómo fue .



El marco JAICF en Kotlin me ayudó a implementar y depurar rápidamente el script de diálogo sin molestarme en trabajar con la API, contextos y bases de datos de Alice, sin limitar las posibilidades (como suele ser el caso con bibliotecas similares).



Enlaces útiles



El documento completo de JAICF está aquí .

Las instrucciones para crear habilidades en él para Alice están aquí .

La fuente de la habilidad en sí se puede encontrar allí .



Y si te gustara



Siéntase libre de contribuir a JAICF , como ya lo están haciendo sus colegas de Yandex , o simplemente deje un asterisco en Github .



Y si tienes alguna pregunta, te la respondemos de inmediato en nuestro acogedor Slack .



All Articles