O blog da AWS

Como construir Chatbots complexos encadeando intents no Amazon Lex (Chain Intents)

Por Giovanna Chiaratti, Arquiteta de Soluções AWS Brasil

 

Introdução

Digamos que você abriu uma pizzaria. A fim de automatizar o atendimento e baixar custos. Você pensa em construir um chatbot para automatizar a realização do pedido e, com isso agilizar o atendimento, dando mais autonomia aos clientes e ainda reduzindo custos. E seria interessante incluir a possibilidade de pedir uma entrada e uma sobremesa, além da pizza aumentando seu ticket médio por pedido. O Amazon Lex é uma solução para sua necessidade. Mas como segmentar isso no Amazon Lex de forma que o usuário passe por todas as possibilidades [entrada, pizza e sobremesa]?

Nesse blogpost vou explicar como criar um chatbot na AWS utilizando o Amazon Lex, serviço para a criação de interfaces de conversa em qualquer aplicativo usando voz e texto, encadeando intents e criando árvores de decisão para que o usuário tenha uma melhor experiência e você possa vender mais.

 

Visão Geral da Solução

Amazon Lex é o organizador do seu ChatBot. No Amazon Lex você irá configurar quais são as possíveis intenções (Intents) de um usuário. O “intent” é a ação principal que um usuário pode ter. No nosso exemplo, o usuário pode querer pedir uma entrada, uma pizza, uma sobremesa ou os três. Consideramos que: pedir uma entrada é um intent, pedir uma pizza seria o segundo intent e pedir uma sobremesa é o terceiro intent. Nosso objetivo é guiar o usuário a percorrer os três intents, na ordem lógica para o melhor atendimento e melhor funcionalidade da nossa aplicação.

O primeiro ponto é decidir ou desenhar a lógica que deve ter o seu chatbot, ou seja, qual intent deve ser apresentado primeiro ao usuário, o segundo e o terceiro. No nosso exemplo, parece que existe uma ordem lógica. Vale a pena essa reflexão quando pensamos em chatbots mais complexos antes de desenhar o chatbot em si. Nessa reflexão você deve levar em consideração a lógica da sua aplicação, formas de respostas adequadas e a intersecção de intents.

Vamos entender os componentes do chatbot. Como vimos antes, os intents são as intenções gerais que um usuário pode escolher no seu chatbot, então seriam: pedir uma entrada, pedir uma pizza, pedir uma sobremesa ou pedir os três. Os intents são construídos por slots. Os slots são parâmetros, são as características dos intents. Os slots são apresentados aos usuários, normalmente em forma de pergunta.

Pensando no nosso exemplo, o intent é “pedir uma pizza” e o slot seria “pizza com borda recheada ou não”, “pizza com massa fina ou grossa”, “pizza com queijo ou não”, assim por diante. No intent “pedir uma sobremesa”, os slots poderiam ser: “calda de chocolate?”, “gostaria de 01 ou 02 unidades?”, etc. Ou seja, os slots são parâmetros dos intents.

Spoiler: os slots coletam detalhes, informações ou parâmetros para que as AWS Lambdas possam fazer a query ou enviar o pedido. As AWS Lambdas retêm a lógica do chatbot.

 

 

Assim a conversa vai até o usuário preencher os slots do intent “pedirPizza”. Importante lembrar que cada intent pode ter cinco slots, ou seja, são cinco especificações que podem ser feitas dentro do intent. Caso você queira colocar mais especificações, deve adicionar mais intents.

Agora, seguindo o fluxo do nosso chatbot da pizzaria, após completar o intent “pedirPizza” o usuário será direcionado ao intent “pedirSobremesa”. Há duas opções para encadear esses intents. A primeira é: no momento em que o usuário responde todos os slots do intent “pedirPizza”, o chatbot retorna uma resposta intuitiva, como exemplo:

“Seu pedido foi feito com sucesso! Gostaria de incluir:

a. uma entrada;

b. ou uma sobremesa?”.

No momento em que o usuário coloca a palavra “sobremesa”, caso essas sejam palavras-chave (Utterance) do intent “pedirSobremesa”, o Lex direcionará o usuário ao intent “pedirSobremesa” e o direcionará a responder todos os slots desse intent.

A segunda forma de direcionar o usuário a outro intent é por meio da Lambda.

 

Mão na Massa

 O Amazon Lex é integrado de forma nativa com as Funções Lambda. A Lambda é a lógica por traz do chatbot: as funções lambda recebem as respostas dos usuários aos slots (parâmetros) e as processam. A Lambda permite que utilizemos o Amazon Lex para desenvolvimento de chatbots complexos, como para queries, transformação e atualização de banco de dados ou de qualquer outro sistema, integrando com os mais de 175 serviços da AWS.  A partir da sua Lambda, você pode chamar qualquer outro serviço da AWS ou outras Lambdas e podemos pensar de forma abrangente, como mandar mensagens para filas do SQS, atualizar um banco SQL, entre outros.

 

No nosso exemplo, o usuário será direcionado ao próximo intent “pedirSobremesa”.

Perceba que, conforme falamos antes, cada intent pode ter até cinco slots (parâmetros). Para o encadeamento de intents feito por meio de funções lambda utilizaremos um dos slots do próximo intent para receber a resposta do intent anterior. Abaixo você pode ver uma explicação gráfica:

 

 

A resposta do usuário “brigadeiro” irá popular o primeiro slot “tipo” [tipo de sobremesa, brigadeiro ou beijinho] do intent “pedirSobremesa”. Como isso acontece?

 

 

Vamos ver como as funções lambda estão estruturadas para receber a resposta dos slots e também como encadear intents por meio de lambda.

Se lembre que sempre que mencionarmos usuário aqui, estaremos falando do o cliente da pizzaria.

O Amazon Lex é responsável por preencher os slots de um intent perguntando e obtendo respostas do usuário. Uma vez que o intent esteja completo, o Amazon Lex envia um “payload” json com as informações coletadas a uma Lambda que processa, guarda ou faz o que quer que seja necessário com elas e retorna uma resposta ao Amazon Lex indicando o que fazer em seguida.

 

RequestID: XXXXXXXX

{

  "alternativeIntents": [

    {

      "intentName": "AMAZON.FallbackIntent",

      "nluIntentConfidence": null,

      "slots": {}

    }

  ],

  "botVersion": "$LATEST",

  "dialogState": "ReadyForFulfillment",

  "intentName": "pedirPizza",

  "message": null,

  "messageFormat": null,

  "nluIntentConfidence": {

    "score": 1

  },

  "responseCard": null,                #caso houvesse um ResponseCard configurado

  "sentimentResponse": null,           # o Amazon Lex enviaria outra resposta.

  "sessionAttributes": {},

  "sessionId": "2021-02-03TXXXXXXXXXX ",

  "slotToElicit": null,

  "slots": {

    "borda": "sim",

    "queijo": "mozarela",

    "sabor": "peperoni",

    "slotFour": "fina",

    "tamanho": "8"

  }

}

 

Como dissemos, o json acima é preenchido pelo Amazon Lex com todos os slots que ele coletou do usuário para o intent pedirPizza e enviado como parâmetro de entrada para Lambda que é definida no campo “Fulfillment – AWS Lambda Function”.

Esse json de entrada da Lambda reflete a configuração feita na figura 1.

 

 

Essa função Lambda deve retornar como resposta outro json no formato que o Amazon Lex espera. Esse formato é definido aqui.

Dica: se você adicionasse um ResponseCard, ele apareceria na posição apontada pela seta na figura acima.

Abaixo mostramos o código da função Lambda em Python 3.7 que processa o intent pedirPizza.

Essa Lambda recebe do Amazon Lex o json acima [def lambda_handler] e processa as informações coletadas nos slots de acordo com a lógica que você escreve [def c_pizza], como por exemplo, guardar a escolha do cliente num item de pedido num meio persistente (ex, um BD) para posteriormente enviar o pedido a cozinha. Após esse processamento, a Lambda responde as informações ao Amazon Lex no formato requerido aqui como retorno do método [def respostaPizza]:

 

def respostaPizza(pedidoPizza):
    if pedidoPizza["success"]:
        return {
            "dialogAction": {
                "type": "ElicitSlot",
                "message": {
                    "contentType": "PlainText",
                    "content": "Seu pedido foi feito com sucesso. Gostaria de pedir um brigadeiro ou beijinho?."
                },
                "intentName": "pedirSobremesa",
                "slotToElicit": "tipo"
            }
        }
    else:
        return {
            "dialogAction": {
                "type": "Close",
                "fulfillmentState": "Fulfilled",
                "message": {
                    "contentType": "PlainText",
                    "content": "Houve um erro"
                },

            }
        }



def c_pizza(sabor, tamanho, borda, massa, queijo):
    pedidoPizza = []       #sua lógica do código...
   
    return pedidoPizza

def lambda_handler(event, context):
    pedidoPizza = c_pizza(event["currentIntent"]["slots"]["sabor"], event["currentIntent"]["slots"]["tamanho"], event["currentIntent"]["slots"]["borda"], event["currentIntent"]["slots"]["massa"], event["currentIntent"]["slots"]["queijo"])
    return respostaPizza(pedidoPizza) #aqui passamos os parâmetros dos slots para a                                                            #função respostaPizza.

 

O que foi descrito acima, completa o intent pedirPizza. Mas também queríamos encadear o intent pedirSobremesa para oferecer a oportunidade, ao usuário, de acrescentar a sobremesa ao seu pedido. Para direcionar o usuário ao intent pedirSobremesa, ou seja, para encadearmos esse próximo intent, adicionamos à resposta retornada por nossa Lambda o dialogAction.Type: ElicitSlot e informamos o próximo intent através do campo intentName:pedirSobremesa, como mostrado na figura abaixo.

Ao receber essa resposta, o Amazon Lex reassume o controle, orientando o usuário a negar ou responder aos slots desse intent, pedirSobremesa, de acordo com seu desejo.

Chegando ao final dessa árvore de decisão, ou seja, chegado ao final do atendimento ao usuário, onde todos os possíveis intents foram oferecidos a ele que pode ter aceitado ou não a “oferta” de cada um, a última Lambda chamada pode compor o pedido por agregar os dados coletados e previamente armazenados num meio persistente e mandá-lo para uma fila do Amazon SQS. Mas isso é assunto para um próximo blog.

#O Amazon Lex espera um formato específico de resposta. Esse é um dos formatos #possíveis. Esse exemplo é simples e cobre o necessário para o funcionamento do #código. Para mais formatos, revisar a página de formatos de resposta do Amazon Lex

 

def respostaPizza(ordem):
    if ordem["success"]:
        return {
            "dialogAction": {
                "type": "ElicitSlot",
                "message": {
                    "contentType": "PlainText",
                    "content": "Seu pedido foi feito com sucesso. Gostaria de pedir um brigadeiro ou beijinho?"                                                                                     #mensagem para o usuário
                },
                "intentName": "pedirSobremesa",         #próximo intent a ser mostrado
                "slotToElicit": "tipo"                  #próximo slot a ser mostrado
            }
        }
    else:
        return {
            "dialogAction": {
                "type": "Close",
                "fulfillmentState": "Fulfilled",
                "message": {
                    "contentType": "PlainText",
                    "content": "Houve um erro"
                },

            }
        }

 

Conclusão

Com esse passo a passo verificamos que podemos encadear intents no Amazon Lex e criar árvores de decisão elaboradas, guiar o usuário de forma granular, adaptar o chatbot a necessidade da nossa aplicação e integrar o chatbot com sistemas externos por meio das chamadas API feitas pelas funções Lambda.

Podemos ir além e integrar o Amazon Lex com serviços que se conectam com funções Lambda, ou seja, é possível coordenar serviços dentro e fora da AWS, deployar Amazon Lex em múltiplas plataformas como Slack, Facebook e Twilio, entre outros sistemas enterprise como Salesforce e Zendesk. Ah, e não se esqueça: deployar um chatbot complexo precisa ser acompanhado de uma boa pizza!

 

 

 


Sobre a autora

Giovanna Chiaratti é Arquiteta de Soluções para Parceiros na América Latina focada em países de língua espanhola. Admiradora e estudiosa de tecnologias como Machine Learning, Inteligência Artificial e Serverless. Trabalha para escalar parceiros de consultoria e tecnologia para que possam apoiar a AWS em criar soluções seguras, inovadoras e inteligentes.

 

 

 

Revisores

Fernando Batagin Junior atua como Arquiteto de soluções para parceiros, guiando-os na jornada de integração ou implementação de suas soluções de software na AWS. Profundo conhecedor de engenharia de software, foca em modernizar soluções existentes de parceiros no Brasil. Possui vinte anos de experiência em projeto e desenvolvimento de software, principalmente na área bancária, passando por várias linguagens, como C, C++, Java, JavaScript, VisualBasic e C# e metodologias de modelagem como UML.

 

 

 

Gerson Itiro Hidaka atualmente trabalha como Arquiteto de Soluções da AWS e atua no atendimento a parceiros mundiais chamados de Global System Integrators and Influencers (GSIIs) na região da América Latina. Entusiasta de tecnologias como Internet das Coisas (IOT), Drones, Devops e especialista em tecnologias como virtualização, serverless, container e Kubernetes. Trabalha com soluções de TI a mais de 24 anos, tendo experiência em inúmeros projetos de otimização de infraestrutura, redes, migração, disaster recovery e DevOps em seu portifólio.