Este é um chatbot baseado em Lex que calculará as calorias obtidas em viagens a diferentes restaurantes de fast food. É habilitado a partir de um Chatbot do FB Messenger que pode ser acessado na página do Facebook ou através do aplicativo Messenger no seu telefone.
Índice
Este bot usa AWS Lex – um serviço que contém a inteligência necessária para decifrar solicitações de usuários e acionar intenções com base nos dados fornecidos nos modelos. As intenções então invocam funções lambda que contêm lógica de negócios específica para a intenção.
Atualmente, existem muitas intenções diferentes nas quais o processo NLU é classificado. Aqui estão as “funções principais” do bot.
Também existem intenções que complementam os recursos principais.
Depois, há intenções que formam a “personalidade” do bot. Eles foram criados com base no uso real do usuário e evitam que a mensagem de erro genérica seja usada para responder.
Dentro de cada uma das intenções, são fornecidos exemplos de declarações que constroem as frases potenciais que um usuário pode fornecer. O valor do slot (ou seja, Large Fry) é passado para a função lambda como um atributo exclusivo.
Você pode obter as informações resumidas da AWS CLI executando o comando a seguir.
aws lex-models get-bot --name FastFoodChecker --version-or-alias PROD
É uma combinação de expressões de amostra e slots que determinam qual intenção os modelos NLU invocarão. Eles são mantidos no Lex e usados para treinar os modelos.
Atualmente, aqui estão os slots personalizados usados pelas intenções.
Um item não precisa ser especificado no slot para que a NLU coloque um valor nele. No entanto, se os dados forem esparsos, isso poderá prejudicar a forma como a NLU interpreta as solicitações do usuário.
A usabilidade de um chatbot requer que ocorra interação natural com o usuário. Um conceito-chave é como incorporar vários slots em uma única intenção. Por exemplo, um usuário pode perguntar “Quantas calorias tem um Big Mac, batatas fritas e uma Coca?” São três itens diferentes que precisam ser analisados. Dentro deste chatbot, o processamento principal possui muitos slots diferentes que são mapeados em intenções. Por exemplo, aqui estão os slots mapeados na intenção GetCalories.
Há alguns itens a serem observados nisso.
Na solicitação de exemplo acima, os modelos NLU analisariam os dados da expressão em três slots diferentes (Comida, Extra e Bebida).
A ordem dos slots não importa para a análise, mas determina qual seria a próxima resposta (slot 1 - Em qual restaurante você está?)
Existem dois slots que não são necessários nesta intenção - Ketchup e PacketsKetchup. Esta informação opcional é solicitada se batatas fritas forem solicitadas como acompanhamento. Isso é orientado pelo código na função Lambda que é invocado no gancho do código de validação.
Toda a lógica na formulação de respostas para diferentes intenções é processada em uma série de funções lambda. Qual função lambda invocar é gerenciada no Lex e definida no nível da intenção. Isso permite que a modularidade seja construída dentro do aplicativo, mantendo as funções leves.
Existem dois pontos diferentes no Lex que podem invocar uma função lambda. A primeira é por meio da validação básica, e o nome do atributo que a identifica é denominado invocationSource. Existem dois valores potenciais para isso – DialogCodeHook e FulfillmentCodeHook. É aqui que essas funções Lambda são especificadas no Lex Bot.
O primeiro menu suspenso é Validação e chama a função lambda toda vez que o bot é chamado. O atributo que ele passa é chamado DialogCodeHook. O segundo menu suspenso é o Cumprimento, e é chamado apenas quando os slots obrigatórios são concluídos e a validação da chamada inicial é concluída. Isso permite que as funções sejam diferentes, possibilitando melhor escalabilidade na construção do bot.
Aqui está uma visão geral de cada função escrita atualmente.
lambda.js – a função principal que trata da validação básica para consultas, originadas apenas no modo DialogCodeHook.
calcular.js - o cálculo da resposta para as calorias reais em uma refeição é feito por esta função e é obtido por um FulfillmentCodeHook.
pizza.js - lida com intenções sobre o cálculo de calorias em uma pizza, incluindo a intenção - WhatPizzaTypes.
misc.js - lida com intenções simples como ajuda, introdução e mais detalhes sobre uma refeição.
chinese.js - lida com intenções em torno da comida chinesa e acopla os diferentes slots para formar uma refeição.
A principal funcionalidade deste bot é ser capaz de responder a perguntas sobre quantas calorias existem em diferentes refeições. Embora os slots usados por Lex sejam úteis no treinamento dos modelos NLU, eles não têm a capacidade de servir como arquivos de pesquisa. É aí que entram os objetos json armazenados na pasta /src/data/.
Aqui está um exemplo do 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},
As funções lambda referem-se a esses objetos para responder a diversas consultas e calcular o consumo de calorias do usuário.
Cada item alimentar pode ser duplicado para diferentes grafias e frases usadas para recuperação. Por exemplo.
{ " 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},
Também há tabelas de consulta sobre molhos, temperos e ajustes de itens individuais. Por exemplo.
[
{
" dressingName " : " Ranch " ,
" calories " :200,
" carbs " :11,
" restaurantNames " :[ " McDonalds " ]
},
{
" dressingName " : " French " ,
" calories " :300,
" carbs " :22,
" restaurantNames " :[ " McDonalds " ]
},
Dado que os modelos NLU não corrigem a ortografia fornecida pelo usuário, cabe às funções Lambda lidar com esta parte da lógica.
Gerenciar grandes slots personalizados pode ser difícil, principalmente se os dados forem dinâmicos. A pesquisa principal de alimentos contém centenas de valores exclusivos e cresce com base no uso do usuário. O processo de criação desse slot foi automatizado e os dados do slot personalizado são obtidos do objeto de dados food.json. Isso é feito por meio da AWS CLI, que pode carregá-los diretamente da linha de comando. Todos os arquivos estão contidos no diretório [slots}(https://github.com/terrenjpeterson/caloriecounter/tree/master/src/slots) para referência. Aqui estão as etapas usadas para criar.
A sintaxe é semelhante a esta.
# 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
Além disso, o valor da soma de verificação provém da implantação anterior do slot personalizado. Você pode encontrar a soma de verificação atual de um slot pelo comando get-slot-type.
# find the latest information about a custom slot
aws lex-models get-slot-type --name FoodOptions --slot-type-version ' $LATEST '
A chave para conversas eficazes de longa duração entre um usuário e um bot está no gerenciamento do contexto da conversa. Por exemplo, um diálogo pode durar vários minutos e invocar muitas intenções.
Parte da facilitação disso é criar um fluxo de conversa. As mensagens de erro não devem ser muito abruptas e devem levar o usuário a uma consulta alternativa. As intenções também devem passar dados entre si. Isso pode ser feito salvando os dados da sessão ao concluir uma intenção. Isso permite que a próxima intenção recupere as informações e não exija que o usuário as repita a cada solicitação.
No exemplo acima, a conversa começa com o usuário indicando em qual restaurante está comendo. Isso é persistido na sessão pela intenção FoodTypeOptions. A caixa de diálogo muda para detalhes da refeição, mas o nome do restaurante é salvo. Além disso, a resposta inicial sobre a contagem de calorias é breve, mas oferece uma explicação mais detalhada se o usuário disser “mais detalhes”. Mais uma vez, os dados são armazenados nos dados da sessão e retornados como parte da estrutura Lex. Aqui está um exemplo de um dos 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 "
}
As funções lambda neste bot são completamente sem estado, portanto, quaisquer dados de invocações anteriores devem passar pelo objeto de solicitação.
Um dos recursos das principais interfaces de usuário do chatbot (Messenger, Slack, etc.) são os botões. Estes reduzem o esforço do usuário, fornecendo uma série de opções como esta.
Cada plataforma de mensagens tem sua própria implementação desse padrão, e aqui está o que o Messenger usa. Lex cuida da tradução para colocar os botões no formato correto e, dentro do Lex, o atributo responseCard precisa ser fornecido com os detalhes do botão.
A modificação do Lex é feita completamente através do console. As funções lambda que atendem à lógica de negócios são hospedadas no AWS lambda e implantadas a partir de um host EC2.
O script de implantação completo é /src/build.sh mas uma visão geral rápida pode ser encontrada nas instruções a seguir.
# 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
Esse processo é repetido para cada uma das funções lambda chamadas pelo Lex. Isso inclui ter pelo menos uma condição de teste para cada função lambda para garantir que a implantação foi feita corretamente.
Um dos tópicos do design de bots é ter personalidade. Algo a considerar ao projetar as intenções é quais são todas as possíveis perguntas que um usuário pode fazer. Isso deve incluir perguntas fora do tópico, como “qual é o seu nome” ou respostas emocionais como “ah, não” ou “você é péssimo”. Eles são fáceis de codificar - geralmente apenas uma simples solicitação-resposta sem slots envolvidos e tendem a tornar os diálogos mais naturais.
Por exemplo, aqui está uma breve resposta codificada na função misc.js que responde se alguém perguntar qual é o nome do bot. Nos modelos, a expressão “qual é o seu nome” resolve essa intenção.
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 do esforço inicial, eu estava tentando publicar este chatbot na loja Slack. Como parte disso, precisei construir um site para suporte público do aplicativo. É um trabalho em andamento e se chama caloriecountbot.com. Ele é hospedado pelo s3 e a fonte está localizada na pasta/website.