Este es un chatbot basado en Lex que calculará las calorías generadas por los viajes a diferentes restaurantes de comida rápida. Se habilita desde un Chatbot de FB Messenger al que se puede acceder desde la página de Facebook o mediante la aplicación Messenger de su teléfono.
Tabla de contenido
Este bot utiliza AWS Lex, un servicio que contiene la inteligencia necesaria para poder descifrar las solicitudes de los usuarios y activar intenciones en función de los datos proporcionados en los modelos. Luego, los intents invocan funciones lambda que contienen lógica empresarial específica del intent.
Actualmente hay muchas intenciones diferentes entre las que se clasifica el proceso NLU. Estas son las "funciones principales" del bot.
También hay intenciones que complementan las funciones principales.
Luego están las intenciones que forman la "personalidad" del robot. Estos se crearon en función del uso real del usuario y evitan que se utilice el mensaje de error genérico para responder.
Dentro de cada una de las intenciones, se proporcionan ejemplos de expresiones que construyen las oraciones potenciales que un usuario puede proporcionar. El valor de la ranura (es decir, Large Fry) se pasa a la función lambda como un atributo único.
Puede obtener la información resumida de la AWS CLI ejecutando el siguiente comando.
aws lex-models get-bot --name FastFoodChecker --version-or-alias PROD
Es una combinación de expresiones y espacios de muestra que determinan qué intención invocarán los modelos NLU. Estos se mantienen en Lex y se utilizan para entrenar los modelos.
Actualmente, aquí están las ranuras personalizadas que utilizan los intents.
No es necesario especificar un elemento en la ranura para que la NLU coloque un valor en él. Sin embargo, si los datos son escasos, puede degradar la forma en que la NLU interpreta las solicitudes de los usuarios.
La usabilidad de un chatbot requiere que se produzca una interacción natural con un usuario. Un concepto clave es cómo incorporar múltiples espacios en una sola intención. Por ejemplo, un usuario podría preguntar "¿Cuántas calorías tiene un Big Mac, papas fritas y una Coca-Cola?". Son tres elementos diferentes que deben analizarse. Dentro de este chatbot, el procesamiento principal tiene muchas ranuras diferentes que se asignan a intenciones. Por ejemplo, aquí están los espacios que se asignan a la intención GetCalories.
Hay un par de elementos a tener en cuenta en esto.
En la solicitud de ejemplo anterior, los modelos NLU analizarían los datos del enunciado en tres espacios diferentes (Comida, Extra y Bebida).
El orden de los espacios no importa para el análisis, pero sí determina cuál sería la siguiente respuesta (espacio 1: ¿en qué restaurante se encuentra?)
Hay dos espacios que no son necesarios en este intento: Ketchup y PacketsKetchup. Esta información opcional se solicita si se solicitan papas fritas como acompañamiento. Esto está impulsado por el código de la función Lambda que se invoca en el enlace del código de validación.
Toda la lógica para formular respuestas a diferentes intenciones se procesa en una serie de funciones lambda. La función lambda que se invoca se gestiona dentro de Lex y se establece en el nivel de intención. Esto permite construir modularidad dentro de la aplicación, manteniendo las funciones livianas.
Hay dos puntos diferentes dentro de Lex que pueden invocar una función lambda. El primero es mediante validación básica, y el nombre del atributo que lo identifica se llama invocationSource. Hay dos valores potenciales para esto: DialogCodeHook y FulfilmentCodeHook. Aquí es donde se especifican estas funciones Lambda en Lex Bot.
El primer menú desplegable es Validación y llama a la función lambda cada vez que se llama al bot. El atributo que pasa se llama DialogCodeHook. El segundo menú desplegable es Cumplimiento y solo se llama una vez que se han completado los espacios obligatorios y se completa la validación de la llamada inicial. Esto permite que las funciones sean diferentes, lo que permite una mejor escalabilidad en la construcción del bot.
A continuación se ofrece una descripción general de cada función escrita actualmente.
lambda.js: la función principal que maneja la validación básica de las consultas, obtenida únicamente en el modo DialogCodeHook.
calcular.js: esta función maneja el cálculo de la respuesta para las calorías reales de una comida y se obtiene mediante un FulfilmentCodeHook.
pizza.js: maneja intenciones relacionadas con el cálculo de calorías en una pizza, incluida la intención: WhatPizzaTypes.
misc.js: maneja intenciones simples como ayuda, la introducción y más detalles sobre una comida.
Chinese.js: maneja intenciones relacionadas con la comida china y acopla las diferentes ranuras para formar una comida.
La funcionalidad principal de este robot es poder responder consultas sobre cuántas calorías hay en diferentes comidas. Si bien las ranuras que utiliza Lex son útiles para entrenar los modelos NLU, no tienen la capacidad de servir como archivos de búsqueda. Ahí es donde entran los objetos json que se almacenan en la carpeta /src/data/.
Aquí hay una muestra del formato.
[
{
" restaurant " : " Chipotle " ,
" foodItems " :[
{ " foodName " : " Chicken Burrito " , " foodType " : " Burrito " , " protein " : " chicken " , " calories " :975},
{ " foodName " : " Steak Burrito " , " foodType " : " Burrito " , " protein " : " steak " , " calories " :945},
{ " foodName " : " Carnitas Burrito " , " foodType " : " Burrito " , " protein " : " carnitas " , " calories " :1005},
Las funciones lambda hacen referencia a estos objetos para responder a diferentes consultas y calcular el consumo de calorías del usuario.
Cada alimento puede duplicarse para diferentes grafías y frases utilizadas para recuperar. Por ejemplo.
{ " foodName " : " Fries " , " calories " :340},
{ " foodName " : " Fry " , " calories " :340},
{ " foodName " : " Frys " , " calories " :340},
{ " foodName " : " French Fries " , " calories " :340},
{ " foodName " : " French Fry " , " calories " :340},
{ " foodName " : " Medium Fries " , " calories " :340},
{ " foodName " : " Medium Fry " , " calories " :340},
También hay tablas de búsqueda sobre salsas, aderezos y ajustes de artículos individuales. Por ejemplo.
[
{
" dressingName " : " Ranch " ,
" calories " :200,
" carbs " :11,
" restaurantNames " :[ " McDonalds " ]
},
{
" dressingName " : " French " ,
" calories " :300,
" carbs " :22,
" restaurantNames " :[ " McDonalds " ]
},
Dado que los modelos NLU no corrigen la ortografía proporcionada por el usuario, depende de las funciones Lambda manejar esta parte de la lógica.
Administrar grandes espacios personalizados puede resultar difícil, especialmente si los datos son dinámicos. La búsqueda principal de alimentos tiene varios cientos de valores únicos y crece según el uso del usuario. El proceso para crear este espacio se ha automatizado y los datos del espacio personalizado se toman del objeto de datos Foods.json. Esto se hace a través de la AWS CLI que puede cargarlos directamente desde la línea de comandos. Todos los archivos están contenidos en el directorio [slots}(https://github.com/terrenjpeterson/calorycounter/tree/master/src/slots) como referencia. Estos son los pasos utilizados para crear.
La sintaxis se ve así.
# foods.json is the data object that will be passed to the lambda function
request= $( < foods.json )
# invoke the lambda function from the command line and write the output to output.json
aws lambda invoke --function-name convertFoodsObjForSlot --payload " $request " output.json
data= $( < output.json )
# invoke lex to create a new version of the FoodEntreeNames custom slot using the data from output.json
aws lex-models put-slot-type --name FoodEntreeNames --checksum < enter latest checksum here > --enumeration-values " $data " >> sysout.txt
Además, el valor de la suma de comprobación proviene de la implementación anterior de la ranura personalizada. Puede encontrar la suma de comprobación actual de una ranura mediante el comando get-slot-type.
# find the latest information about a custom slot
aws lex-models get-slot-type --name FoodOptions --slot-type-version ' $LATEST '
La clave para conversaciones efectivas y duraderas entre un usuario y un bot es gestionar el contexto de la conversación. Por ejemplo, un diálogo podría durar varios minutos e invocar muchas intenciones.
Parte de facilitar esto es diseñar un flujo de conversación. Los mensajes de error no deben ser demasiado abruptos y deben llevar al usuario a una consulta alternativa. Los intents también deben pasar datos entre sí. Esto se puede lograr guardando los datos de la sesión al completar una intención. Esto permite que la siguiente intención recupere la información y no requiera que el usuario la repita con cada solicitud.
En el ejemplo anterior, la conversación comienza cuando el usuario indica en qué restaurante está comiendo. Esto persiste en la sesión mediante la intención FoodTypeOptions. El diálogo cambia a los detalles de la comida, pero el nombre del restaurante se guarda. Además, la respuesta inicial sobre el recuento de calorías es breve, pero ofrece una explicación más detallada si el usuario dice "más detalles". Una vez más, los datos se almacenan en los datos de la sesión y se devuelven como parte del marco Lex. Aquí hay un ejemplo de uno de los objetos.
{
" messageVersion " : " 1.0 " ,
" invocationSource " : " FulfillmentCodeHook " ,
" userId " : " 1712299768809980 " ,
" sessionAttributes " : {
" restaurantName " : " Burger King " ,
" foodName " : " Whopper " ,
" foodCalories " : " 660 " ,
" extraName " : " Onion Rings " ,
" extraCalories " : " 410 " ,
" drinkCalories " : " 310 " ,
" drinkName " : " 32 oz. Large Coke " ,
" totalCalories " : " 1380 "
},
" bot " : {
" name " : " FastFoodChecker " ,
" alias " : " PROD " ,
" version " : " 42 "
},
" outputDialogMode " : " Text " ,
" currentIntent " : {
" name " : " DailyIntakeAnalysis " ,
" slots " : {},
" slotDetails " : {},
" confirmationStatus " : " None "
},
" inputTranscript " : " Analyze my meal "
}
Las funciones lambda en este bot no tienen ningún estado, por lo que cualquier dato de invocaciones anteriores debe pasar a través del objeto de solicitud.
Una de las características de las principales interfaces de usuario de chatbot (Messenger, Slack, etc.) son los botones. Estos reducen el esfuerzo del usuario al proporcionar una serie de opciones como esta.
Cada plataforma de mensajería tiene su propia implementación de este patrón y esto es lo que utiliza Messenger. Lex maneja la traducción para que los botones tengan el formato correcto y, dentro de Lex, el atributo ResponseCard debe proporcionarse con los detalles del botón.
La modificación de Lex se realiza completamente a través de la consola. Las funciones lambda que sirven a la lógica empresarial están alojadas en AWS lambda y se implementan desde un host EC2.
El script de implementación completo es /src/build.sh, pero puede encontrar una descripción general rápida en las siguientes instrucciones.
# this creates the build package as a zip file containing the code and relevant data objects
zip -r foodbot.zip lambda.js data/restaurants.json data/foods.json data/drinks.json
# this CLI command copies the build package to an s3 bucket for staging
aws s3 cp foodbot.zip s3://fastfoodchatbot/binaries/
# this CLI command takes the package from the s3 bucket, and overlays the lambda function 'myCalorieCounterGreen'
aws lambda update-function-code --function-name myCalorieCounterGreen --s3-bucket fastfoodchatbot --s3-key binaries/foodbot.zip
# this CLI command invokes the lambda function with the data object read into request, and writes out a response to the testOutput data object.
aws lambda invoke --function-name myCalorieCalculatorGreen --payload " $request " testOutput.json
Este proceso se repite para cada una de las funciones lambda que llama Lex. Esto incluye tener al menos una condición de prueba para cada función lambda para garantizar que la implementación se realizó correctamente.
Uno de los temas en el diseño de bots es tener personalidad. Algo a considerar al diseñar los intents es cuáles son todas las posibles preguntas que puede hacer un usuario. Esto debe incluir preguntas fuera de tema, como "cómo te llamas" o respuestas emocionales como "oh-no" o "apestas". Estos son fáciles de codificar; generalmente son solo una simple solicitud-respuesta sin espacios involucrados y tienden a hacer que los diálogos sean más naturales.
Por ejemplo, aquí hay una breve respuesta codificada en la función misc.js que responde si alguien pregunta cuál es el nombre del bot. En los modelos, una expresión de "cómo te llamas" responde a esta intención.
if (intentName === ' MyName ' ) {
console.log( " user requested bot name " ) ;
return getBotName(intentRequest, callback) ;
}
...
function getBotName(intentRequest, callback) {
const sessionAttributes = intentRequest.sessionAttributes || {} ;
var botResponse = " My name is Chuck. I'm a chatbot that helps people sort out " +
" fast food options. Talking about food all day makes me hungry!!! " ;
callback(close(sessionAttributes, ' Fulfilled ' ,
{ contentType: ' PlainText ' , content: botResponse })) ;
}
Como parte del esfuerzo inicial, estaba intentando publicar este chatbot en la tienda Slack. Como parte de eso, necesitaba crear un sitio web para el apoyo público de la aplicación. Es un trabajo en progreso y se llama kalorycountbot.com. Está alojado en s3 y la fuente se encuentra en la carpeta/sitio web.