Como criar um chatbot sem servidor para WhatsApp com Python, Google Cloud Functions e Twilio

March 25, 2021
Escrito por
Mwangi Kabiru
Contribuidor
As opiniões expressas pelos colaboradores da Twilio são de sua autoria

Como criar um chatbot sem servidor para WhatsApp com Python, Google Cloud Functions e Twilio

Um chatbot é um aplicativo de software usado para automatizar interações e conversas com pessoas por meio de plataformas de mensagens. Os usos comuns dos chatbots incluem roteamento de solicitações, atendimento ao cliente e coleta de informações.

A arquitetura sem servidor (Serverless architecture) é um padrão de design em que os aplicativos são divididos em funções individuais que podem ser chamadas e dimensionadas separadamente. O objetivo é acabar com a complexidade do processo de criação e execução de aplicativos para desenvolvimento e implantação da infraestrutura necessária para sua execução.

Neste tutorial, você vai aprender a criar um chatbot sem servidor do WhatsApp com API do WhatsApp da Twilio e as funções Python no Google Cloud. O chatbot aceita o nome de um país e mostra informações sobre ele. Os dados do país são obtidos da API pública REST Countries.

Demonstração do projeto

Requisitos do tutorial

Para seguir este tutorial, é necessário ter os seguintes itens:

Como configurar a área restrita do WhatsApp da Twilio

A Twilio oferece uma área restrita para o WhatsApp, onde é possível facilmente desenvolver e testar seu aplicativo. Depois que seu aplicativo estiver finalizado, você poderá solicitar o acesso de produção para o número de telefone da Twilio, que exige a aprovação pelo WhatsApp.

Vamos começar o teste da nossa área restrita do WhatsApp. No console da Twilio, selecione Programmable Messaging no menu da barra lateral à esquerda. Clique em "Try it Out" (Experimentar) e depois em "Try WhatsApp" (Experimentar WhatsApp). Na página da área restrita para o WhatsApp, é possível visualizar o número da área restrita e um código para entrar.

A sandbox da Twilio para WhatsApp

Para entrar na área restrita, envie uma mensagem de WhatsApp para o número da área restrita com o texto "join <your sandbox code>" (entrar em <código da área restrita>). É enviada uma resposta de confirmação da área restrita assim que o código é aceito.

Como criar um virtualenv do Python e instalar os requisitos

Na sua máquina local, crie uma pasta onde o código do nosso chatbot fica ativo e configure um virtualenv dentro dessa pasta. Os comandos a seguir funcionam nas plataformas de Unix e Mac. Abra um terminal e digite:

$ mkdir twilio-chatbot
$ cd twilio-chatbot
$ python3 -m venv twilio-chatbot-env
$ source twilio-chatbot-env/bin/activate

Nas plataformas do Windows, use os seguintes comandos:

$ md twilio-chatbot
$ cd twilio-chatbot
$ python -m venv twilio-chatbot-env
$ twilio-bot-env\Scripts\activate

Em seguida, é possível prosseguir para instalar os pacotes necessários com o seguinte comando:

(twilio-bot-env) $ pip install twilio requests

Este comando instala 2 pacotes:

  • twilio, para acessar as APIs da Twilio
  • requests, pacote para nos ajudar a acessar serviços da Internet de terceiros

Devemos gravar os pacotes que instalamos em um arquivo requirements.txt para que o Google Cloud Platform possa usá-los ao instalar a função de nuvem posteriormente. Veja o comando abaixo:

 $ pip freeze > requirements.txt

No momento da elaboração deste artigo, veja as versões do pacote em requirements.txt:

certifi==2020.12.5
chardet==4.0.0
idna==2.10
PyJWT==1.7.1
pytz==2021.1
requests==2.25.1
six==1.15.0
twilio==6.53.0
urllib3==1.26.3

Como criar uma função do Google Cloud

O Google Cloud Functions pode ser chamado usando HTTP ou acionadores de eventos. As funções com um acionador HTTP são chamadas quando uma solicitação HTTP é enviada para o URL da função. As funções com acionadores de eventos são chamadas quando um evento ocorre em seu projeto do GCP. Exemplos de fontes de eventos são Pub/Sub, Firestore e Firebase.

Para implementar um chatbot do WhatsApp, a Twilio precisa chamar um webhook HTTP quando um usuário enviar uma mensagem no WhatsApp. Assim, sabemos que é preciso uma função de nuvem chamada HTTP.

Como criar o webhook

Na nossa pasta raiz, twilio-chatbot, crie um arquivo chamado main.py. É aqui que o código do bot fica ativo. Verificação rápida: você deve ter 2 arquivos na pasta até agora, requirements.txt e main.py, além do diretório do ambiente virtual.

Vamos criar uma função que atua como o ponto de entrada para o webhook bot no arquivo main.py:

def whatsapp_webhook(request):
    """HTTP Cloud Function.
    Parameters
    ----------
    request (flask.Request) : The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>

    Returns
    -------
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.

    """

No corpo da função, é preciso começar recuperando a mensagem enviada pelo usuário pelo WhatsApp. Veja como fazer:

    country = request.values.get('Body', "").lower()

No código acima, recuperamos o valor do argumento Body, que é definido pela Twilio para a mensagem enviada pelo usuário. Se esse valor não existe, o padrão é uma string vazia. Convertemos o texto em minúsculas para padronizar a entrada do usuário. Isso significa que "alemanha", "Alemanha", "ALEMANHA" e "aLemAnha" são interpretados como "alemanha".

Com o nome do país recebido, solicitamos dados da API pública REST Countries da seguinte forma:

    resp = requests.get(
        f'https://restcountries.eu/rest/v2/name/{country}?fullText=true')

Se a API responder com um código de status não HTTP 2xx, significa que nossa solicitação não foi bem-sucedida. Precisamos detectar essa condição de erro e informar o usuário. Esta é a lógica que reproduz:

    if not (200 <= resp.status_code <= 299):
        return 'Sorry we could not process your request. Please try again or check a different country'
    data = resp.json()
    return data

Os dados devolvidos pela API são uma lista de comprimento 1. Na lista, temos um dicionário de dados de países com chaves como nameregioncapital, etc. No exemplo de bot, extrairemos os valores do nome nativo, capital, gentílico e região. Sinta-se à vontade para mudar ou adicionar esses valores ao criar seu bot. Veja aqui o modelo de estrutura de dados da API REST Countries.

{
    'name': str

Agora que sabemos que os dados retornados são uma matriz, é possível atualizar a instrução IF na parte inferior da função com as seguintes alterações para extrair os quatro valores mencionados acima.

    if not (200 <= resp.status_code <= 299):
        return 'Sorry we could not process your request. Please try again or check a different country'
    data = resp.json()[0]  # Extract the single dict in the response using the index 0

    # Extract values needed by the bot
    native_name = data['nativeName']
    capital = data['capital']
    people = data['demonym']
    region = data['region']

Com as quatro variáveis extraídas, podemos construir uma resposta legível usando as f-strings do Python:

    response = f"{country.title()} is a country in {region}. It's capital city is {capital}, while it's native name is {native_name}. A person from {country.title()} is called a {people}." # Note the use of str.title() to improve readability of final response
    
    return response

É possível agora nos comunicar com nossa API externa e extrair os dados necessários de sua resposta e precisamos pensar como esses dados serão passados para a Twilio e serem entregues ao usuário. Quando a Twilio chama um webhook, espera-se uma resposta do aplicativo no TwiML (Twilio Markup Language), que é uma linguagem baseada em XML. A biblioteca Twilio helper que instalamos oferece uma maneira fácil de criar e estruturar essa resposta em nosso código. Abaixo, você pode ver um exemplo de como estruturar o corpo da mensagem e incluir uma mídia relacionada.

from twilio.twiml.messaging_response import MessagingResponse

twilio_response = MessagingResponse()
msg = twilio_response.message()

msg.body('Sample text response')
msg.media('Url for any media to include in the response')

Vamos atualizar nosso código para que as respostas sigam a linguagem esperada. Observe que agora estamos passando a resposta de solicitações bem-sucedidas e mal-sucedidas para msg.body e gerar a resposta correta de TwiML e seu registro e abaixo é possível ver o corpo atualizado da função whatsapp_webhook():

    country = request.values.get('Body', "").lower()
    resp = requests.get(f'https://restcountries.eu/rest/v2/name/{country}?fullText=true')
    twilio_response = MessagingResponse()
    msg = twilio_response.message()
    if not (200 <= resp.status_code <= 299):
        logger.error(
            f'Failed to retrieve data for the following country - {country.title()}. Here is a more verbose reason {resp.reason}'
        )
        msg.body(
            'Sorry we could not process your request. Please try again or check a different country'
        )
    else:
        data = resp.json()[0]
        native_name = data['nativeName']
        capital = data['capital']
        people = data['demonym']
        region = data['region']
        msg.body(
            f"{country.title()} is a country in {region}. It's capital city is {capital}, while it's native name is {native_name}. A person from {country.title()} is called a {people}."
        )
    return str(twilio_response)

Neste momento, podemos reunir tudo e concluir nossa função. Veja abaixo a aparência do arquivo main.py completo, incluindo todas as importações necessárias. Verifique se a sua versão é igual.

import logging
import requests
from twilio.twiml.messaging_response import MessagingResponse

logger = logging.getLogger(__name__)


def whatsapp_webhook(request):
    """HTTP Cloud Function.
    Parameters
    ----------
    request (flask.Request) : The request object.
        <https://flask.palletsprojects.com/en/1.1.x/api/#incoming-request-data>
    Returns
    -------
        The response text, or any set of values that can be turned into a
        Response object using `make_response`
        <https://flask.palletsprojects.com/en/1.1.x/api/#flask.make_response>.
    """

    country = request.values.get('Body', "").lower()
    resp = requests.get(f'https://restcountries.eu/rest/v2/name/{country}?fullText=true')
    twilio_response = MessagingResponse()
    msg = twilio_response.message()
    if not (200 <= resp.status_code <= 299):
        logger.error(
            f'Failed to retrieve data for the following country - {country.title()}. Here is a more verbose reason {resp.reason}'
        )
        msg.body(
            'Sorry we could not process your request. Please try again or check a different country'
        )
    else:
        data = resp.json()[0]
        native_name = data['nativeName']
        capital = data['capital']
        people = data['demonym']
        region = data['region']
        msg.body(
            f"{country.title()} is a country in {region}. It's capital city is {capital}, while it's native name is {native_name}. A person from {country.title()} is called a {people}."
        )
    return str(twilio_response)

Implantação

Há quatro maneiras comuns de implantar a função de nuvem que acabamos de criar no Google Cloud Platform:

  1. No painel do GCP (Google Cloud Platform)
  2. Localmente em nosso terminal
  3. Do controle de origem
  4. A partir da API de funções de nuvem

Este tutorial mostra a primeira opção. Faça login na sua conta do GCP e navegue até o painel e clique em "Cloud Functions" (Funções da nuvem) no menu lateral.

Dashboard do console do GCP

Clique em "Create Function" (Criar função) e defina o nome da função como twilio-webhook. Marque também "Allow unauthenticated invocations" (Permitir chamadas não autenticadas) para tornar o webhook publicamente disponível. Ao clicar em "Save" (Salvar), aparece:

Criar função de nuvem

Clique em "NEXT" (Próximo) para ser encaminhado para a página onde inserimos o código de função de nuvem. Defina "Runtime" (Tempo de execução) para 'Python 3.7' e "Entry point" (Ponto de entrada) para 'whatsapp_webhook'. Substitua o conteúdo do arquivo *main.py* à esquerda por aqueles do arquivo main.py criados na seção anterior. Execute as mesmas ações no arquivo requirements.txt. Depois de terminar, clique em "DEPLOY" (IMPLANTAR).

Implantação da função de nuvem do GCP

Depois de implantado com êxito, aparece uma marca de seleção verde ao lado do nome da sua função, conforme mostrado abaixo.

Função de nuvem implantada com sucesso

Como configurar o webhook da Twilio

Clique na função de nuvem no painel acima e, em seguida, clique na guia "TRIGGER" (ACIONAR) para exibir o URL de acionamento.

URL de ativação da função de nuvem

Volte ao Console Twilio, clique em Programmable Messaging (Mensagens programáveis), depois em Settings (Configurações) e, por último, em WhatsApp Sandbox Settings (Configurações de sandbox para WhatsApp). Copie o "Trigger URL" (URL de acionamento) da função de nuvem e cole-o nas "WhatsApp Sandbox Settings" (Configurações de área restrita do WhatsApp) no campo "When a message comes in" (Quando receber uma mensagem). Verifique se o método de solicitação está definido como "HTTP POST" (PUBLICAÇÃO HTTP) e clique em "Save" (Salvar).

Com o número de WhatsApp conectado à área restrita, é possível fazer um bate-papo com o bot. Envie o nome de um país para obter uma resposta do webhook Python em execução no GCP. Veja os exemplos abaixo:

Resposta do webhook: Canadá

Resposta do webhook: França

Você pode verificar se o bot reconhece erros enviando uma palavra que não é um país:

Resposta de erro

Arquiteturas alternativas

As arquiteturas sem servidor oferecem a oportunidade de criar um ponto de conexão com a Internet sem se preocupar com infraestrutura e implantação. Conforme nosso exemplo acima, tínhamos um endpoint em menos de 30 linhas de código e não precisamos de uma estrutura da Internet para executá-lo. Além das funções de nuvem do GCP, o AWS oferece um produto semelhante chamado AWS Lambda e a Microsoft tem as funções do Azure.

Por vários motivos, como custos de hospedagem, também é possível considerar o uso de um endpoint tradicional criado com uma estrutura da Internet em Python para seu webhook. Se decidir pesquisar essa opção, veja aqui um tutorial sobre como criar um chatbot WhatsApp com Python, Flask e Twilio.

Este artigo foi traduzido do original "Building a Serverless WhatsApp Chatbot using Python, Google Cloud Functions and Twilio". Enquanto melhoramos nossos processos de tradução, adoraríamos receber seus comentários em help@twilio.com - contribuições valiosas podem render brindes da Twilio.