Skip to main content

Rule based chatbots

Embora seja possível utilizar ferramentas prontas como o Chatterbot ou o NLTK, elas são indicadas para sistemas com muitas variações de respostas. Na verdade, um chatbot simples pode ser feito com um programa bem mais enxuto. A seguir, vamos ver a criação de um chatbot simples que se comunica com o usuário.

1. Criação de um chatbot simples

1.1. Escopo do chatbot

Vamos começar definindo o escopo do chatbot. Para o nosso exemplo, vamos criar uma aplicação extremamente simples para apenas detectar quando o usuário cumprimenta o chatbot e responde adequadamente.

Exemplo de interação:

Usuário: Bom dia, tudo bem?
R: Bom dia! Comigo está tudo ótimo, e com você?

1.2. Criando expressões regulares

A primeira coisa que vamos desenvolver é a expressão regular para detectar que o usuário teve a intenção de cumprimentar o chatbot. Embora seja possível apenas detectar a frase Bom dia, tudo bem?, essa solução não é nada robusta. Afinal, se o usuário disser Boa tarde ou mesmo deixar de usar pontuação ou letras maiúsculas o sistema já não conseguiria detectar a intenção corretamente.

Dica

Sempre que houver aqui uma expressão regular, recomenda-se fortemente testá-la no regex101

Vamos partir da premissa de que não temos intimidade com expressões regulares, portanto faz mais sentido irmos construindo nossa expressão aos poucos e considerando cada caso possível, um de cada vez. Vamos começar detectando o cumprimento do usuário. Vamos considerar que o usuário pode dar bom dia, boa tarde ou boa noite. A expressão regular fica:

(Bom dia|Boa tarde|Boa noite)
Explicação do chat gepeto
A expressão regular `(Bom dia|Boa tarde|Boa noite)` usa um mecanismo chamado alternância para combinar uma de várias opções diferentes. Aqui está o que cada parte faz:
  • ( ... ): Os parênteses são usados para agrupar partes da expressão regular. Eles não apenas agrupam a expressão, mas também criam o que é conhecido como um grupo de captura. Isso significa que o texto que corresponde a esta parte da expressão pode ser "capturado" para uso posterior.

  • Bom dia: Esta é uma sequência literal que corresponderá exatamente ao texto "Bom dia".

  • Boa tarde: Assim como "Bom dia", esta é outra sequência literal que corresponderá exatamente ao texto "Boa tarde".

  • Boa noite: E esta é mais uma sequência literal, correspondendo exatamente ao texto "Boa noite".

  • |: Este é o operador de alternância, e funciona como um "ou" lógico. Isso significa que a expressão regular corresponderá a "Bom dia" OU "Boa tarde" OU "Boa noite".

Portanto, essa expressão regular procura por uma de três diferentes saudações. Se qualquer uma das saudações especificadas aparecer no texto de destino, a expressão regular considerará isso uma correspondência.

Dica

O ChatGPT é muito bom para criar ou explicar expressões regulares. Use-o em conjunto com o regex101.

Temos alguns problemas aqui. O primeiro é que não estamos capturando a próxima parte da expressão, em que o usuário pode perguntar se está tudo bem. O segundo problema é que se o usuário não usar letras maiúsculas no começo das palavras, o padrão não é reconhecido. É possível resolver isso direto na aplicação em Python, mas vamos explorar um pouco mais as opções de regex. Vejamos como pode ficar:

\b(?:(?:[Bb]o[am])\s(tarde|dia|noite))
Explicação do chat gepeto

A expressão regular \b(?:(?:[Bb]o[am])\s(tarde|dia|noite)) é um pouco mais complexa do que a anterior e possui diversas partes:

  • \b: É um metacaractere que representa um limite de palavra (word boundary). Isso significa que o padrão seguinte deve começar no início de uma palavra.

  • (?: ... ): É um grupo de não captura. Grupos de não captura agrupam múltiplas partes de uma expressão regular juntas sem capturar o texto que corresponde a essa parte para uso posterior. Eles são usados para aplicar quantificadores a partes da expressão, mas sem a sobrecarga de armazenar a captura.

  • [Bb]: É um conjunto de caracteres que corresponderá a 'B' maiúsculo ou 'b' minúsculo.

  • o[am]: Segue a mesma lógica do conjunto anterior, correspondendo a "oa" ou "om", o que permite a formação das palavras "Boa" ou "Bom".

  • \s: Corresponde a qualquer espaço em branco, como espaços, tabs, etc.

  • (tarde|dia|noite): É um grupo de captura que vai corresponder a uma das palavras "tarde", "dia" ou "noite".

Portanto, essa expressão regular vai corresponder a "Bom dia", "Bom tarde", "Bom noite", "Boa dia", "Boa tarde", "Boa noite", com qualquer combinação de maiúsculas e minúsculas para "Bom/Boa", devido ao [Bb] e [oa], e apenas se essas combinações aparecerem como palavras isoladas no texto, por causa do \b.

Aqui estão alguns exemplos de como esta expressão regular funcionaria:

  • "Bom dia" → Corresponde (e captura "dia")
  • "boa tarde" → Corresponde (e captura "tarde")
  • "BoM Noite" → Corresponde (e captura "noite")
  • "abomdia" → Não corresponde (porque "abomdia" não começa no limite de uma palavra devido à ausência de espaço ou separador de palavras antes de "bom")

Acho que agora conseguimos uma expressão legal para capturar nossa primeira intenção! Vamos para a segunda? Agora vamos ver se o usuário perguntou se o chatbot está bem? Para isso, vamos direto para uma expressão regular mais robusta:

\b(?:[Tt]udo)?\s?(?:(?:[bB]em)|(?:[bB]ão)|(?:[fF]irme)|(?:em\sriba))\?
Explicação do chat gepeto
A expressão regular `\b(?:[Tt]udo)?\s?(?:(?:[bB]em)|(?:[bB]ão)|(?:[fF]irme)|(?:em\sriba))\?` busca por várias expressões que podem ser usadas para perguntar informalmente como alguém está. Aqui está o que cada parte da expressão faz:
  • \b: Indica um limite de palavra, o que significa que o padrão deve ocorrer no início de uma palavra.

  • (?:[Tt]udo)?: É um grupo de não captura que procura pela palavra "Tudo" ou "tudo". O ? depois do grupo de não captura indica que a presença da palavra "Tudo" ou "tudo" é opcional.

  • \s?: Corresponde a zero ou um espaço em branco (espaço, tabulação, etc.). A questão ? significa que o espaço pode ou não estar presente.

  • (?: ... ): São grupos de não captura usados para agrupar expressões alternativas sem capturar o que foi correspondido.

  • (?:[bB]em): Este grupo de não captura vai corresponder à palavra "bem" ou "Bem".

  • (?:[bB]ão): Este grupo de não captura vai corresponder à palavra "bão" (uma forma coloquial de "bom" em português) ou "Bão".

  • (?:[fF]irme): Este grupo de não captura vai corresponder à palavra "firme" ou "Firme".

  • (?:em\sriba): Este grupo de não captura vai corresponder à expressão "em riba" (provavelmente uma forma coloquial de "em cima").

  • \?: Corresponde a um ponto de interrogação literal, indicando que a expressão que está sendo buscada provavelmente é uma pergunta.

Juntando tudo isso, a expressão regular pode corresponder a várias formas de perguntar informalmente como alguém está, incluindo a presença ou ausência da palavra "Tudo" no começo e também considerando variações comuns de escrita e coloquialismos. Por exemplo, ela pode corresponder a:

  • "Tudo bem?"
  • "tudo bão?"
  • "Bem?"
  • "bão?"
  • "Firme?"
  • "em riba?"

A expressão é flexível o suficiente para pegar diferentes variações de espaçamento e capitalização.

1.3. Criando um dicionário de intenções

Agora que já temos nossas expressões regulares, vamos criar um dicionário para mapear as intenções do usuário. Para isso, crie um arquivo python:

touch test-chatbot.py

Nesse arquivo, vamos adicionar o seguinte:

test-chatbot.py
#! /bin/env python3

import re

intent_dict = {
r"\b(?:(?:[Bb]o[am])\s(tarde|dia|noite))": "greetings",
r"\b(?:[Tt]udo)?\s?(?:(?:[bB]em)|(?:[bB]ão)|(?:[fF]irme)|(?:em\sriba))\?": "genki"
}

command = input("Digite o seu comando: ")
for key, value in intent_dict.items():
pattern = re.compile(key)
groups = pattern.findall(command)
if groups:
print(f"Encontrei a seguinte intenção: {value}")

Deixe o script executável com:

chmod +x test-chatbot.py

E execute-o com:

./test-chatbot.py

Note que as expressões regulares ainda não estão perfeitas. Brinque um pouco com elas, talvez até criando novas expressões com intenções redundantes, para que o chatbot fique um pouco mais inteligente.

1.4. Atrelando a intenção a uma ação

Apenas identificar a intenção não é o suficiente para criar um chatbot capaz de executar ações mais complexas. Vamos agora criar um dicionário que linka intenções à ações. Edite o código anterior e deixe-o assim:

test-chatbot.py
#! /bin/env python3
import re

def greet_back(time_of_day):
if time_of_day == "dia":
return f"Bom {time_of_day}!"
else:
return f"Boa {time_of_day}!"

def genki_back(_):
return "Comigo está tudo bem, e com você?"

intent_dict = {
r"\b(?:(?:[Bb]o[am])\s(tarde|dia|noite))": "greetings",
r"\b(?:[Tt]udo)?\s?(?:(?:[bB]em)|(?:[bB]ão)|(?:[fF]irme)|(?:em\sriba))\?": "genki"
}

action_dict = {
"greetings": greet_back,
"genki": genki_back
}

command = input("Digite o seu comando: ")
for key, value in intent_dict.items():
pattern = re.compile(key)
groups = pattern.findall(command)
if groups:
print(f"{action_dict[value](groups[0])}", end=" ")
print()

Pronto! Temos o nosso pequeno chatbot configurado e funcional!

2. Exercícios propostos

Criando...