La última vez utilizamos la aplicación de seguimiento de hábitos de código abierto Habitica y demostramos cómo agregar un asistente de voz y crear un escenario básico, el pronóstico del tiempo, listo para usar.
Ahora hay que pasar al siguiente nivel. Aprenderás cómo invocar determinadas pantallas, crear consultas complejas con NLU y completar un formulario con voz.
La primera parte
Habitica es una aplicación de seguimiento de hábitos con algunos elementos de gamificación que ayudan a formar buenos hábitos. Mantiene tus objetivos vitales como hábitos diarios y te anima a cumplirlos.
Así que ahora vamos a enseñarle a nuestro asistente de voz, que vive dentro de la aplicación, a crear y completar las tareas con la voz en lugar de con las manos.
Tabla de contenidos
La lógica de una interfaz de voz
Empezaremos con facilidad; veamos cómo funciona la lógica. Digamos que queremos usar comandos de voz para abrir ventanas de configuración o características. Abra AndroidManifest y busca las actividades relevantes. Encontrarás PrefsActivity que es responsable de la configuración; y FixCharacterValuesActivity responsable de las características del personaje. Y para colmo, busca la actividad que abre el perfil y la información de las aplicaciones: FullProfileActivity y AboutActivity.
Según la documentación, conseguimos introducir la lógica del cliente en la clase, heredada de CustomSkill. En primer lugar, especifica que solo reaccionamos a la respuesta del bot que contiene “changeView” en una response.action. En response.action transferirás el comando a dónde ir exactamente, y dependiendo de esto, invocamos la actividad. Pero no olvides buscar el contexto de la aplicación antes de esa fecha:
class ChangeViewSkill(private val context: Context): CustomSkill<AimyboxRequest, AimyboxResponse> { override fun canHandle(response: AimyboxResponse) = response.action == «changeView» override suspend fun onResponse( response: AimyboxResponse, aimybox: Aimybox, defaultHandler: suspend (Response) -> Unit ) val intent = when (response.intent) { «settings» -> Intent(context, PrefsActivity::class.java) «characteristics» -> Intent(context, FixCharacterValuesActivity::class.java)// «profile» -> Intent(context, FullProfileActivity::class.java)// «about» -> Intent(context, AboutActivity::class.java) else -> Intent(context, MainActivity::class.java) } intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) aimybox.standby() context.startActivity(intent) } {
{Esta habilidad se agrega al asistente de la siguiente manera:
val dialogApi = AimyboxDialogApi(
«YOUR KEY HERE», unitId,
customSkills = linkedSetOf(ChangeView()))
{
Habilidad e intenciones
Usa JAICF para crear una parte de la habilidad en la nube (es un marco de trabajo basado en Kotlin de código abierto para crear aplicaciones de voz de Just AI). Comienza con la bifurcación del proyecto https://github.com/just-ai/jaicf-jaicp-caila-template
Lo necesitarás para comprender las consultas del usuario y luego enviar json al dispositivo para que inicie la actividad correspondiente.
Desafortunadamente, en el momento de escribir este artículo no había integración de JAICP (Just AI Conversational Platform) con Aimybox (SDK de asistente de voz en la aplicación para crear interfaces de voz), y es lamentable porque la vinculación sería mucho más fácil; simplemente podríamos agregar una línea de código en uno de los dos archivos de configuración en una carpeta de conexiones. Pero ahora crearemos un nuevo archivo de configuración que usaremos para las pruebas.
{Crea un archivo AimyboxConnection package com.justai.jaicf.template.connections import com.justai.jaicf.channel.http.httpBotRouting import com.justai.jaicf.channel.aimybox.AimyboxChannel import io.ktor.routing.routing import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty import com.justai.jaicf.template.templateBot fun main() { embeddedServer(Netty, System.getenv(«PORT»)?.toInt() ?: 8080) { routing { httpBotRouting(«/» to AimyboxChannel(templateBot)) } }.start(wait = true) }{
Para utilizar la funcionalidad NLU, agrega el servicio Caila NLU. Entonces, te registras en app.jaicp.com, buscas la clave API en la configuración y la ingresas en conf / jaicp.properties. Ahora invoca las intenciones que has ingresado en app.jaicp.com justo en el escenario.
En realidad, puedes usar cualquier otro proveedor de NLU o simplemente usar algunas expresiones regulares, pero si deseas que sea agradable y útil para el usuario, es mejor que emplees NLU.
En primer lugar, configura las intenciones. Necesitas saber cuándo nuestro usuario necesita ir a una sección específica de una aplicación. Entonces, configura entidades, agregando una entidad concreta para cada sección de una aplicación; agrega sinónimos e ingresa cómo lo vas a reconocer en el nivel de la aplicación (configuración, características, etc. de las líneas de código anteriores).
Lo tengo así:
Ahora escribe la forma en que esperamos encontrarnos con esta entidad en el discurso de un usuario. Para hacerlo, crea una intención y anota variaciones de frases. Además, realmente necesitas saber a dónde debes ir exactamente, por lo tanto, tienes que marcar la entidad de vistas como Requerida.
Así es como se ve:
Utiliza el nombre para referirte a una entidad en un código JAICF. Para asegurarte de que todas las intenciones se reconocen como deberían, puedes ingresar algunas frases de prueba usando el botón de prueba.
Aparece así:
Escenario: invocar una habilidad
Como precaución, borra todos los estados estándar, pero deja catchAll only: esto es algo que el bot dice cuando no te entiende. Crea un estado changeView e ingresa la intención que acabas de crear en JAICP en los activadores. Además, escribe la lógica en las acciones: debes agregar toda la información en la respuesta del bot y en las reacciones estándar del canal Aimybox para hacer una transición.
Ahora obten ese espacio de vistas de lo que Caila reconoció, pon las cosas que ingresaste antes en acción para que Aimybox sepa qué habilidad tiene que invocar. Luego pon la ranura reconocida a la intención. Para hacerlo más ordenado, agrega «Wait a sec…».
{
state(«changeView») { activators { intent(«changeView») } action { reactions.say(«Wait a sec…» ) var slot = «» activator.caila?.run {slot = slots[«views»].toString()} reactions.aimybox?.response?.action = «changeView» reactions.aimybox?.response?.intent = slot } }{
Será mejor que lleves las habilidades a un paquete de habilidades separado con un archivo de clase para cada habilidad. Ahora tienes algunas opciones. Puedes implementar el bot en el dispositivo a través de ngrok, o puedes usar Heroku. A continuación, toma el enlace que obtuviste y lo colocas en app.aimybox.com a través del desarrollo de habilidades de voz personalizado, en un cuadro de URL de webhook de Aimylogic. Escribe algunas muestras de habilidades: ve a la configuración, ve a la información.
Después de haber agregado un canal, puedes probar la devolución para obtener los errores directamente en una consola, usando el botón Probar en acción.
O puedes habilitar la habilidad sin consola ni habilidades adicionales, como se describe aquí. Ahora solo tienes que ejecutarlo y ver si todo está bien.
¡Está!
Bien, ahora la parte complicada.
Llenando las tareas con voz
Quiero completar la tarea, asegurarme de que todo esté bien y corregir algunos pequeños errores con un comando de voz. Y solo después de eso, finalmente lo crearé.
Para lograr esto, crea una segunda habilidad. Diferente al primero y este a través de response.action == «createTask. Y especifica el tipo de tarea a través de response.intent.
Cuando exploras todas las fuentes de la aplicación, ves que los premios, los diarios, los hábitos y las tareas se crean a través de TaskFormActivity, pero usando diferentes tipos. Déjame mostrarte esa lógica.
class CreateTaskSkill(private val context: Context): CustomSkill<AimyboxRequest, AimyboxResponse> {
override fun canHandle(response: AimyboxResponse) = response.action == «createTask»
override suspend fun onResponse(
response: AimyboxResponse,
aimybox: Aimybox,
defaultHandler: suspend (Response) -> Unit
) {
val intent = Intent(context, TaskFormActivity::class.java)
val additionalData = HashMap<String, Any>()
val type = response.intent
additionalData[«viewed task type»] = when (type) {
«habit» -> Task.TYPE_HABIT
«daily» -> Task.TYPE_DAILY
«todo» -> Task.TYPE_TODO
«reward» -> Task.TYPE_REWARD
else -> «»
}
Cada tarea, incluidos los premios, tiene un nombre y una descripción. Además, las tareas tienen complejidad y sentimiento. Veamos cómo enviar los datos allí. Lo harás a través de response.data, y tendrás algunos valores predeterminados en caso de que esté vacío. Luego, agrupa los datos y crea una tarea con ese paquete. También necesitarás manejar los datos empaquetados en la función onCreate de TaskFormActivity.
{
// Inserted code for voice activation textEditText.setText(bundle.getString(«activity_name»)) // presetting task name notesEditText.setText(bundle.getString(«activity_description»)) //presetting task description if (bundle.getBoolean(«sentiment»)) { // presetting task sentiment habitScoringButtons.isPositive = true habitScoringButtons.isNegative = false } else { habitScoringButtons.isNegative = true habitScoringButtons.isPositive = false } when (bundle.getString(«activity_difficulty»).toString()) { // presetting task difficulty «trivial» -> taskDifficultyButtons.selectedDifficulty = 0.1f «easy» -> taskDifficultyButtons.selectedDifficulty = 1f «medium» -> taskDifficultyButtons.selectedDifficulty = 1.5f «hard» -> taskDifficultyButtons.selectedDifficulty = 2f else -> taskDifficultyButtons.selectedDifficulty = 1f }
Ahora configura el reconocimiento y la habilidad JAICF con Caila NLU.
Preparando a Caila: haz que una entidad reconozca el tipo de tareas, la dificultad y el sentimiento (para dar un ejemplo, lo obtuve con patrones; puedes elegir Patrones en lugar de sinónimos en el lado izquierdo del formulario)
No olvides ingresar los datos que procesaremos en el lado del cliente: hábito, patrón, etc. Puede haber cualquier nombre y descripción, por lo que crea entidades Nombre y Descripción, donde escribas en una expresión regular, haciendo coincidir cualquier palabra . Por ahora, tu nombre y descripción tendrán una sola palabra.
Crea una intención:
Indica que necesitamos task_type y complejidad. Puedes establecer en Requerido tanto el nombre como la descripción, por lo que cuando un usuario no menciona ninguno de estos, un bot especificará el espacio.
Ahora escribe diferentes variaciones que se pueden usar como nombre o descripción con un tipo (su orden, sin nombre o descripción). Y la perfección no conoce límites, pero para hacer el mínimo unas cuantas plantillas de arriba es suficiente.
Además, para mostrar un ejemplo aquí, utilizo el lenguaje de patrones, que se puede cambiar presionando un botón (a la izquierda desde Enter).
@ — patterns and regexp, “ — examples and semantic similarity.
{
Now the JAICF scenario: state(«createTask») { activators { intent(«createTask») } action { val taskType = activator.getCailaSlot(«taskType»).asJsonLiteralOr(«») reactions.say(«Wait a sec…») reactions.aimybox?.response?.action = «createTask» reactions.aimybox?.response?.intent = taskType.content reactions.aimybox?.response?.run { data[«taskName»] = activator.getCailaSlot(«taskName»).asJsonLiteralOr(«») data[«taskDescription»] = activator.getCailaSlot(«taskDescription»).asJsonLiteralOr(«») data[«taskSentiment»] = activator.getCailaSlotBool(«taskSentiment»).asJsonLiteralOr(true) data[«taskDifficulty»] = activator.getCailaSlot(«taskDifficulty»).asJsonLiteralOr(«easy») } } } private fun ActivatorContext.getCailaRequiredSlot(k: String): String = getCailaSlot(k) ?: error(«Missing Caila slot for key: $k») private fun ActivatorContext.getCailaSlot(k: String): String? = caila?.slots?.get(k) private fun ActivatorContext.getCailaSlotBool(k: String): Boolean? = caila?.slots?.get(k)?.toBoolean() private fun String?.asJsonLiteralOr(other: String) = this?.let { JsonLiteral(this) } ?: JsonLiteral(other) private fun Boolean?.asJsonLiteralOr(other: Boolean) = this?.let { JsonLiteral(this) } ?: JsonLiteral(other)
Conecta la intención a través del activador, obten el tipo de las ranuras y colócalo en la intención. Pon el nombre y la descripción en los datos, y no olvide marcar la acción, para que Aimybox del lado del cliente sepa qué habilidad debe elegir.
Ahora asegurate de que funcione. ¡Y lo hace! Enciende el volumen y pruébalo.
Por supuesto, esta es una demostración técnica, y seguro, en términos de producto, puedes pensar en escenarios más útiles.
¡Lo discutiremos en nuestros próximos artículos!