Este chatbot é uma tentativa de criar uma realidade à imagem do projeto que está a decorrer no curso BotCamp na LetsBot.
O desafio lançado no curso HelloBot (curso inicial de uma semana) era o de criar um chatbot, usando o Dialogflow, com criação de intents que respondessem a um conjunto de questões mais frequentes lançadas por milhares de pais em relação a uma ferramenta de gestão de entradas e saídas dos filhos das escolas.
A ferramenta pertence à School Guardian e esta empresa em parceria com a LetsBot entregou um caso real para ser resolvido no ambito do curso. Desta forma, todos os matriculados vão ter a possibilidade de resolver um caso real utilizando todas as técnicas aprendidas ao longo de várias semanas.
O desafio a que me propus não era trazer para aqui o resultado do bot criado em Dialogflow, mas replicar os conhecimentos utilizando programação em Python.
Segue uma breve descrição dos passos dados na construção do chatbot realizado no curso inicial HelloBot
-
A School Guadian entregou um conjunto de top questões recebidas pelos encarregados de educação onde tivemos numa primeira fase encaixar em intenções (intents).
-
Depois tivemos que enriquecer cada intenção com perguntas similares e respostas alternativas, para que tivessemos uma fonte de dados para treinar um modelo de Processamento de Linguagem Natural (NLP). Como resultado, tivemos um ficheiro de excel com a seguinte estrutura:
- Terminado a primeira fase de Behavior/Interaction partimos para a fase de Architect/Build, utilizando o Dialogflow como ferramenta, adicionando todas as intents, com as potenciais questões dos clientes e respectivas respostas. Nesta fase o nosso conjunto de dados foi treinado com Inteligencia Artificial para que fosse possível identificar a intenção da questão do cliente, mesmo com outras construções de sintaxe.
- A fase seguinte foi a criação de um site integrado com o nosso bot e que estivesse na internet para acesso de todos. Para a realização desta etapa utilizamos o Google Sites como ferramenta. Após publicação, bastou partilhar com os colegas para que podessem testar.
O resultado foi este: School Guardian Bot
-
A última fase do HelloBot foi de Training, onde todos fomos convidados a quebrar o bot dos colegas e assim poder fazer o Curate dos nossos bots. Confesso que esta foi uma das partes bem divertidas do curso.
-
Dando continuidade aos conteúdos do BotCamp, foi introduzido um novo mecanismo, chamado de webhook, onde é permitido uma interação mais dinâmica, que não limita às resposta previamente programadas. Isto é feito recorrendo a chamadas de APIs externas ao bot. Até aqui tudo pacífico, não fosse a necessidade de poder haver chamadas que necessitam de parâmetros que têm que ser recolhidos das frases dos utilizadores. Segue um pequeno exemplo que ajuda a perceber melhor:
No exemplo anterior, podem ver 3 conversas (a seta indica a ordem das entradas). Em todas as conversas a chamada cai na intent "Obter_nome_responsavel" que necessita de identificar o nome do responsável da criança. Neste caso, não podemos prever uma resposta certa, uma vez que depende do nome da criança. Aqui entra a utilização do webhook, ou seja, uma chamada de uma API exterior que irá validar dentro de alguma base de dados da escola a resposta à questão. Para o bot é indiferente ao que é feito dentro da API, apenas lhe interessa o resultado. Este webhook está preparado para responder apenas a dois nomes de crianças "João" e "Júlia", a título de demonstração. Na 1ª conversa, o bot identificou o nome do aluno e a api respondeu qual o responsável correspondente. Na 2ª conversa não foi colocado o nome da criança e o bot como identificou a falta desta informação perguntou "Qual o nome da criança?". Na 3ª conversa, apesar do bot ter identificado o nome da criança, quando fez a chamada, a api não devolveu nada o que originou a resposta de não existir aquela criança. A grande diferença desta solução, comparativamente ao Dialogflow é que o processo está preparado para usar diferentes webhook para diferentes Intents e quando pede o nome da criança, o que for respondido é tratado como se do nome se tratasse, terminando essa iteração. Mas nem tudo são rosa, pois o bot apenas foi preparado para ir até máximo de um parâmetro. Esta configuração encontra-se no ficheiro "job_intents.json" explicado mais abaixo.
A aprendizagem anterior foi essencial para o desenho e construção do chatbot. A ideia não foi replicar o conjunto completo de todas as intents identificadas, mas montar uma estrutura que permita fazer algo semelhante.
Quero deixar bem claro que uma parte do código foi extraido da net e não é de minha autoria. Deixo mais abaixo as referências. O verdadeiro desafio foi colar as várias peças encontradas na net e montar uma solução identica à da formação, com desenvolvimentos para várias features.
Fase do projeto e funcionalidades ainda por desenvolver ou melhorar:
Quando | Descrição |
---|---|
06.mar.2022 | Criado o primeiro protótipo apenas com identificação das intents com base nos diálogos conversacionais desenhados |
27.mar.2022 | Chamada a webhook para interações externas. ao contrário do Dialogflow, cada intent pode recorrer a um webhook diferente |
10.abr.2022 | Acrescentado o mecanismo de desambiguação recorrendo a um novo elemento das entidades. Também foi desenvolvido um mecanismo de debug mais detalhado |
Passar alguns parâmetros para o ficheiro json (graus de confiança, flag debug, etc) | |
Fluxos conversacionais com IA | |
Passar a aceitar lista de parâmetros de entrada para as APIs | |
Criar módulo de registo de logs para curadoria | |
Melhorar o design do frontend de ambas as versões |
Segue os passos realizados para conseguir executar o projeto no seu computador.
Em primeiro lugar deve ter o motor de Python instalado na sua máquina (https://www.python.org/).
Depois de instalado, recomendo como boa prática criar um environment para o projeto, por forma a ter um ambiente isolado de bibliotecas.
Para isso (supondo que está num ambiente windows), entre numa Command Prompt (digitar cmd após carregar no botão do windows)
Criar um env indicando a pasta que terá o projeto (a path pode ser à sua escolha):
python -m venv C:\Meu\chatbot
cd C:\Meu\chatbot
Ativar o environment:
Scripts\activate.bat
Dentro do environment, no meu caso chamei de chatbot, temos que instalar as seguintes livrarias necessárias para rodar o projecto:
pip install nltk
pip install numpy
pip install keras
pip install tensorflow
pip install flask
pip install spacy
Download do modelo para interpretar a lingua portuguesa:
python -m spacy download pt_core_news_md
Neste momento temos o ambiente pronto. Apenas uma nota, a versão que eu usei de python é 3.7.9. Também deverá funcionar com versões mais recentes.
Para criar um webhook para usar no chatbot, recorri ao Heroku (https://www.heroku.com/). Se não conhece, este site permite fazer deploy de web ou a exposição de API de forma gratuita (com limitação até 5 projetos).
Não é o meu propósito explicar os passos necessários para a criação das APIs, uma vez que há muitos videos na web a ensinar. Caso pretenda experimentar, pode usar a minha API criada para este projeto e que se encontra no código fonte. Se tiver curiosidade pode também ver o código compactado no ficheiro api-py.zip e usar como bem entender.
O projeto está dividido em duas parte, uma de IA para treinar o modelo e outra para executar o chatbot utilizado o modelo pré treinado.
Ficheiro | Descrição |
---|---|
job_intents.json | O ficheiro com as intents com as perguntas e respostas |
chatbot.py | Neste ficheiro temos o algoritmo de NLP para treinar e criar o modelo a usar no chatbot |
words.pkl | ficheiro pickle no qual fica armazenado as palavras do nosso vocabulário |
classes.pkl | ficheiro pickle com a lista das intents |
chatbot_model.h5 | temos aqui o resultado do modelo treinado |
app.py | código do frontend do chatbot em versão browser |
chatgui.py | código do frontend do chatbot em versão janela de conversão |
processor.py | ficheiro com toda a funcionalidades tal como a previsão da intent, validação da acurrancy e escolha aleatória da resposta (sempre que há mais do que uma resposta |
Numa primeira fase é necessário criar o modelo de NLP (h5). Segue o exemplo do ficheiro json com as intents:
Na linha de Command Prompt, dentro do environment que tiver criado, (garanta que está na pasta onde descarregou os ficheiros do projeto antes de executar o comando):
python chatbot.py
Se não aparecer nenhum erro durante o treinamento e terminar com "model created" é porque o modelo foi criado com sucesso.
Neste momento estamos em condições de executar o aplicativo do chatbot. Temos dois frontends diferentes:
python app.py
O resultado deverá terminar com o link a copiar para o seu browser:
Ao copiar o link para um browser, teremos um resultado deste tipo:
Em formato popup:
Neste caso, é aberto uma janela de chat e o resultado mostrado é semelhante ao exemplo anterior, até porque ambos partilham das mesmas funções do file "processor.py".
Toda a estrutura do código do motor do chatbot está preparada para registar informação que permitem fazer um debug de baixo nível. No ficheiro processor.py existe uma variável global com o nome debug e que está por default a true. Caso não seja pretendido fazer debug basta colocar a false. O debug não é mais que um conjunto de prints com informação da função executada, os valores de algumas variáveis que fazem sentido e alguns comentários para melhor situar o significado do mesmo.
Exemplo simples:
Neste exemplo, quando o user submete uma frase é escrito "------" a delimitar o inicio e o fim do tratamento dessa frase. Na imagem prévia podemos ver:
- function: chatbot_response | msg: informa a mensagem inserida pelo user
- function: getResponse(0) | StatusBot: contém o estado do bot entre interações de frases. Importante para chamadas externas de api ou envolvimento de entidades
- function: getResponse(0) | ints: diz qual a intenção identificada, assim como a probabilidade [0..1] e o threshould (grau de confiança mínimo) para aceitar a intenção como válida.
- function: chatbot_response | resposta: mostra a resposta resultante que vai devolver e ainda tem mais à frente o resultado que indica que é uma resposta aliatória de várias.
Nota que o nome da função e o valor entre () serve para localizar melhor a chamada no código.
Exemplo utilizando uma entidade:
O resultado do debug:
O resultado do debug é um pouco mais complexo, mas simples de se ler.
1ª interação:
- function: chatbot_response | msg: o user pergunta o horário de saída, mas não informa o tipo de periodo do filho
- function: getResponse(0) | statusBot: está limpo
- function: getResponse(0) | ints: informa que a intenção votada com quase 100% de certeza é "Responder_horario_saida" o que está correcto
- function: getEntity(5) | api_param_type: informa o tipo de entidade, nome da entidade, a frase do user e o resultado de que a entidade está em falta
- function: getResponse(3) | resposta API: mostra a resposta (prompt) obtida para o user identificar o valor da entidade em falta
- function: chatbot_response | resposta: com o prompt escolhido de forma aliatória da lista de prompts
2ª interação:
- __function: chatbot_response | msg: o user responde com "tempo inteiro"
- function: getResponse(0) | statusBot: tem informação que trata-se de uma resposta a uma questão do bot. podemos ver o url do webhook, e o param_type diz-nos que é uma resposta para identificar uma entidade, o que é verdade
- function: getResponse(0) | ints: neste caso, a informação apresentada não tem interesse
- function: getEntity(1) | api_param_type: mostra que a entidade é "periodo" e o valor identificado pelos sinónimos é "integral" para passar à api.
- function: getResponse(1) | element: informa que vai chamar a api com o valor da entidade correctamente identificado
- function: getResponse(2) | Recolha: mostra a resposta da chamada ao webhook
- function: chatbot_response | resposta: mostra a resposta dada ao user
Como se pode ver, os resultados imprimidos na linha de comando são bastante intuitivos. Os dois exemplos apresentados foram para ilustrar possíveis mensagens, mas não cobrem todas as mensagens programadas no código. Quando for desenvolvido o módulo de registo em ficheiro das mensagens, para curadoria, as mensagens serão ainda mais simples e intuitivas de serem lidas.
O JSON tem informação das intents e das entidades, em forma de array. Essa informação é necessária para definir a lógica do de atuação do bot. Desta forma, é muito importante ficar explicado como está estruturado e o significado de cada parte.
A estrutura da Intent pode ter:
Nome | significado | Mandatório | Exemplos |
---|---|---|---|
tag | Nome da intent | sim | 'Obter_nome_melhor_aluno' |
patterns | Formas diferentes de dizer o mesmo e que têm o mesmo sentido da Intent. Vão servir para a criação do modelo de IA | sim | ['Olá', 'Bom dia', ...] |
responses | Respostas possíveis a dar no caso da Intent ser escolhida como provável. É um array de respostas para permitir escolher uma de forma aleatória e assim parecer mais uma conversa humana | sim | ['olá', 'Viva, como posso ajudar?'] |
api_action | Corresponde ao url do webhook para fazer uma chamada ao exterior. Caso a resposta não necessite de fazer nenhuma chamada, deverá deixar com string vazia | sim | "https://xpto.com/demo?action=obter_nome_aluno" |
api_param_type | informa, quando diferente de string vazia, o tipo de informação a extrair da frase para entregar na chamada da API. Este pode ser nome próprio (proper noun), verbo (verb), pronome (pronoun), advérbios (adverb), adjetivo (adjective), entre outros permitidos pela biblioteca spacy do python. Também pode ter o valor "entity" quando se trata de uma entidade. | Apenas obrigatório quando tem api_action <> "" | 'proper noun' |
api_param_name | Representa o nome do parâmetro que a api está desenhada para receber. Quando o campo api_param_type for igual a "entity", este campo deve também corresponder ao name da entidade correspondente | Apenas obrigatório quando tem api_action <> "" | 'aluno' |
api_responses_missing_param | Sempre que não for encontrado a informação que mapea com o parâmetro de entrada para a API, pode colocar aqui a questão para obter a informação em falta. Este campo é um array de questões para que seja escolhida uma aleatoriamente e assim parecer uma conversa mais naturar com o cliente | Apenas se api_action <> "" e tiver parâmetro de entrada (api_param_type <> "") | ['Qual o nome da criança?', 'Como se chama a criança'] |
A estrutura da Entity pode ter:
Nome | significado | Mandatório | Exemplos |
---|---|---|---|
name | Nome da entidade. Deverá corresponder ao valor dado ao parâmetro api_param_name da Intent que o vai usar | sim | periodo |
values | corresponde ao dicionário dos possíveis valores e os respectivos sinónimos. Recomenda-se que a key do dicionário esteja também na lista de valores, para que seja tido em conta na procura. | sim | "values": {"parcial" : ["parcial", "meio periodo"], "integral" : ["integral", "dia completo"]} |
Este ficheiro é o coração do bot, onde toda a configuração vai implicar o comportamento do mesmo.
O modelo de IA criado é de 3 camadas.
- Primeira camada 128 neurônios
- Segunda camada 64 neurônios
- Terceira camada de saída contém o número de neurônios igual ao número de intenções para prever a intenção de saída com softmax
Não cheguei a experimentar variar os parâmetros como o número de camadas, o número de neorónios ou até mesmo as funções de ativação entre outros. A ideia aqui não era arranjar o melhor modelo possível, mas um que respondesse de forma razoavel para o desafio. (esta informação pode encontrar no file "chatbot.py")
Como ainda não é usado nenhum processo de contexto ou desambiguação, optei por usar dois tipos de threshold para aceitação da intent com maior probabilidade. Assim se o comprimento da frase do user for curto (len(sentença) < 10) uso um threshold de 80%, caso contrário este passa para 25%. Com isto, caso a maior probabilidade esteja abaixo do threshold a resposta será "Não entendi. Pode reformular a pergunta?" . (esta infomração encontra-se no file "processor.py")
Boa parte do código foram retirados de:
https://data-flair.training/blogs/python-chatbot-project/
https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-create-bot?view=azure-bot-service-4.0&tabs=python%2Ccli
Criação de environment:
https://docs.python.org/3/tutorial/venv.html
Bibliotecas usadas: https://www.nltk.org/ https://realpython.com/natural-language-processing-spacy-python/
livecicle variáveis entre sessões de onde vem e para onde vai (o a n) quebra na sequência caso o user sai da fluxo