Volver a las novedades para desarrolladores

Agregar WhatsApp Flows a la experiencia de tu bot de chat

20 de marzo de 2024DeGafi G. e Iryna Wagner

Hoy en día, más empresas que nunca usan bots de chat a través de WhatsApp para interactuar con los clientes, determinar sus necesidades y recopilar datos esenciales. Sin embargo, este proceso de recopilar y organizar datos puede plantear desafíos en términos de eficiencia. Y es ahí donde WhatsApp Flows puede ser muy útil.

Integrar bots de chat con WhatsApp Flows permite a las empresas interpretar la información entrante de los clientes con mayor eficacia. Luego, los bots de chat pueden iniciar flujos específicos adaptados para la recopilación de datos, en función del contexto de la conversación.

En este tutorial, crearás un bot de chat usando Llama 2 y Python, y lo conectarás a WhatsApp Flows para mejorar sus funciones de recopilación de datos. En el proceso, podrás apreciar cómo WhatsApp Flows simplifica el uso de la experiencia del bot de chat y mejora la precisión y la eficiencia de la recopilación de datos de los clientes.

Mejorar la experiencia del bot de chat con WhatsApp Flows

Tu bot de chat responderá a las solicitudes e indicaciones de los usuarios con WhatsApp Flows para recopilación de datos. Más específicamente, permitirá a los usuarios conversar con el bot de chat, buscar información sobre los servicios ofrecidos por un hotel ficticio ubicado en España y se contactará con la empresa para obtener ayuda.

El bot de chat usará instrucciones if simples junto con el modelo estándar Llama 2 para brindar acceso a una base de conocimientos generales suficiente.

Pasos que seguirás

Parte 1:

Parte 2:

  • Convertir el modelo Llama 2 de GGML a GGUF.

  • Escribir el código Python para integrar los flujos y el bot de chat.
  • Crear un webhook para reproducir los mensajes.
  • Ejecutar la aplicación.

Parte 1: Cómo crear flujos

En esta parte, usarás Flow Builder para crear algunos flujos. También puedes usar la API de flujos, pero no cubriremos esa alternativa en este tutorial.

Requisitos previos de la parte 1

Para poder seguir las instrucciones, asegúrate de contar con lo siguiente:

Por último, asegúrate de completar los pasos necesarios para usar WhatsApp Flows. También puedes obtener una vista previa del código del proyecto completo.

Empezar

Para empezar, ve a la página Flujos en tu cuenta de WhatsApp Business. Si esta es tu primera interacción con WhatsApp Flows, deberías ver el botón Empezar a crear flujos. De lo contrario, verás el botón Crear flujo en la parte superior derecha de la página.

Haz clic en el botón que aparezca para abrir un cuadro de diálogo donde podrás ingresar algunos detalles sobre el flujo que deseas crear.

Empezar a usar el chat de WhatsApp

El flujo de consultas sobre servicios

En primer lugar, crearás un flujo que permita al usuario buscar información sobre los servicios que ofrece la empresa, en nuestro caso, el hotel.

Escribe un nombre en el campo Nombre. Luego, en el menú desplegable Categorías, elige Registrarte, y, en el menú desplegable Plantilla, deja la opción Ninguna. Haz clic en Enviar.

En la siguiente página, aparecerá un editor a la izquierda y una vista previa a la derecha.

Reemplaza el contenido del editor por el marcado JSON de flujo (puedes obtener más información sobre el JSON de flujo en la documentación para desarrolladores).

Guarda el flujo. La vista previa debe ser similar a la que se muestra a continuación:

Flujo del chat

El flujo contiene una pantalla que le permite al usuario ingresar sus detalles, seleccionar los servicios que le interesan y agregar un mensaje adicional opcional. Cuando el usuario hace clic en Enviar, el flujo se cierra y envía los datos capturados a tu empresa para su procesamiento. Un método de transmisión de estos datos supone el uso de puntos de conexión. Sin embargo, en este proyecto, no es necesario un punto de conexión. Los datos se transmitirán al mismo webhook en el que se basa el bot de chat.

Puedes lograr esta transferencia usando acciones. Para definir los datos que se transmitirán a la siguiente pantalla, usa el objeto payload:

...

"on-click-action": {
    "name": "complete",
    "payload": {
        "firstname": "${form.first_name}",
        "secondname": "${form.second_name}",
        "services_interested": "${form.services_interested}",
        "additional_info": "${form.additional_info}",
        "flow_key": "agentconnect"
    }
}
...
        

En el fragmento, la acción de clic en el botón del usuario activa on-click-action, lo que captura los datos en la carga. Luego, envía la carga al servidor del webhook y cierra el flujo a través de la acción complete.

Puedes asignar las claves de payload como lo harías con cualquier variable. Los valores correspondientes pueden representar objetos de datos o los nombres de los componentes del flujo, del mismo modo que el atributo de nombre de un formulario HTML.

Ahora, puedes ver el flujo en acción y simular la experiencia de un usuario real usando el botón Vista previa interactiva:

Vista previa interactiva del chat

Después de probar el flujo, puedes publicarlo, ya que, hasta este punto, se encuentra en versión borrador. Para hacerlo, abre el menú a la derecha de Guardar y haz clic en Publicar. Ahora el flujo está listo para usar.

El flujo "Contactarnos"

Ahora, crearás un flujo "Contactarnos".

Para empezar, repite el mismo proceso inicial de creación de flujo. En la categoría, elige Contactarnos. Reemplaza el contenido del editor por este marcado JSON para que se muestre lo siguiente:

Flujo "Contactarnos" del chat

Publica el flujo y continúa con la siguiente sección para configurar un bot de chat. La sección ofrece tres funciones (send_message, flow_details y flow_reply_processor), que contienen la lógica vital para enviar el flujo a los usuarios. La sección también incluye la lógica para procesar las cargas entrantes del flujo. Por lo tanto, recomendamos echarle un vistazo por más que ya hayas creado un bot de chat.

Parte 2: Cómo configurar el bot de chat

A continuación, configurarás el bot de chat y lo integrarás a los flujos.

Requisitos previos de la parte 2

Antes de continuar, asegúrate de contar con lo siguiente:

  • Una versión reciente de Python y conocimientos básicos sobre cómo usarla.

  • La versión HuggingFace de Llama 2 descargada. La versión HuggingFace del modelo Llama 2 no requiere ninguna otra herramienta adicional ni hardware especializado. También puedes usar la versión oficial, pero esta requiere configuración adicional.

  • El token de acceso de tu cuenta y el identificador del número de teléfono.

  • Un editor de código.

Usar Python para integrar los flujos y el bot de chat

El bot de chat funcionará a través de un script predefinido diseñado para brindar asistencia a los usuarios a partir de los datos que ingresen. Tras la interacción inicial, ofrece un texto de bienvenida personalizado y un menú basado en texto en función del mensaje del usuario. Estas opciones se adaptan a necesidades específicas: consultar por los servicios que ofrece el hotel, contactarse con uno de sus agentes o interactuar con un bot de chat con tecnología Llama.

Responder con un carácter alfanumérico conectará al usuario con el servicio o la acción correspondiente. Por el contrario, cualquier otra respuesta activará la funcionalidad predeterminada del bot de chat, que brinda asistencia con consultas generales u orienta al usuario sobre los servicios disponibles en función de la información proporcionada en la conversación.

Para empezar, crea un entorno virtual ejecutando el siguiente comando en tu terminal:

python -m venv venv
        

Actívalo:

source venv/bin/activate
        

Luego, instala los paquetes necesarios:

pip install requests flask llama-cpp-python python-dotenv
        

Usas Flask para crear rutas e interactuar con la API, requests para enviar solicitudes de internet, llama-cpp-python para interactuar con el modelo y python-dotenv para cargar las variables de entorno.

A continuación, crea un archivo de entorno denominado .env y el siguiente contenido, y asigna los valores correctamente (puedes usar cualquier cadena para TOKEN).

TOKEN = 
ACCESS_TOKEN = 
PHONE_NUMBER_ID = 
        

En el mismo directorio, crea un archivo denominado main.py y empieza a añadir los paquetes que usarás:

import os
import re
import time
import uuid
import requests
from dotenv import load_dotenv
from flask import Flask, request, make_response, json
from llama_cpp import Llama
        

Ahora, inicializa las variables y las clases. El fragmento también inicia Flask e invoca el método load_dotenv() para ayudar a cargar las variables:

app = Flask(__name__)

load_dotenv()
PHONE_NUMBER_ID = os.getenv('PHONE_NUMBER_ID')
url = f"https://graph.facebook.com/v18.0/{PHONE_NUMBER_ID}/messages"
TOKEN = os.getenv('TOKEN')
ACCESS_TOKEN = os.getenv('ACCESS_TOKEN')

code_prompt_texts = ["Contact us", "Chat with our chatbot", "YES", "NO"]


service_list = [
    "Accommodation Services",
    "Spa Services",
    "Dining Services",
    "Recreational Facilities",
    "Business & Conference Services",
    "Transportation Services",
    "Accessibility Services",
    "Pet-Friendly Services"
]

La lista service_list almacena los servicios que ofrece el hotel. La lista code_prompt_texts contiene las opciones correspondientes a las opciones de entrada, que son 1, 2, Y y N usando la función a continuación, la cual ayudará a asignar las respuestas del usuario a la opción correspondiente.

def extract_string_from_reply(user_input):
    match user_input:
        case "1":
            user_prompt = code_prompt_texts[0].lower()
        case "2":
            user_prompt = code_prompt_texts[1].lower()
        case "Y":
            user_prompt = code_prompt_texts[2].lower()
        case "N":
            user_prompt = code_prompt_texts[3].lower()
        case _:
            user_prompt = str(user_input).lower()

    return user_prompt
        

El código convierte las cadenas a letra minúscula para evitar discrepancias al ejecutar la lógica condicional. La estructura de match…case asigna las indicaciones entrantes del usuario a las funcionalidades salientes. Por ejemplo, si un usuario ingresa "1", se activará la funcionalidad "Contact us".

La siguiente función contiene una instrucción if que utiliza el paquete RegEx de Python, re, para buscar ciertos términos incluidos en el mensaje de un cliente a fin de determinar qué tipo de respuesta enviar al usuario:

def user_message_processor(message, phonenumber, name):
    user_prompt = extract_string_from_reply(message)
    if user_prompt == "yes":
        send_message(message, phonenumber, "TALK_TO_AN_AGENT", name)
    elif user_prompt == "no":
        print("Chat terminated")
    else:
        if re.search("service", user_prompt):
            send_message(message, phonenumber, "SERVICE_INTRO_TEXT", name)

        elif re.search(
            "help|contact|reach|email|problem|issue|more|information", user_prompt
        ):
            send_message(message, phonenumber, "CONTACT_US", name)

        elif re.search("hello|hi|greetings", user_prompt):
            if re.search("this", user_prompt):
                send_message(message, phonenumber, "CHATBOT", name)

            else:
                send_message(message, phonenumber, "SEND_GREETINGS_AND_PROMPT", name)

        else:
            send_message(message, phonenumber, "CHATBOT", name)
        

Entonces, un mensaje como "Hello there" activará el método send_message con SEND_GREETINGS_AND_PROMPT como segundo argumento. A continuación, sigue el método send_message. Reemplaza el contenido entre <xxx> según corresponda.

def send_message(message, phone_number, message_option, name):
    greetings_text_body = (
        "\nHello "
        + name
        + ". Welcome to our hotel. What would you like us to help you with?\nPlease respond with a numeral between 1 and 2.\n\n1. "
        + code_prompt_texts[0]
        + "\n2. "
        + code_prompt_texts[1]
        + "\n\nAny other reply will connect you with our chatbot."
    )

    # loading the list's entries into a string for display to the user
    services_list_text = ""
    for i in range(len(service_list)):
        item_position = i + 1
        services_list_text = (
            f"{services_list_text} {item_position}. {service_list[i]} \n"
        )

    service_intro_text = f"We offer a range of services to ensure a comfortable stay, including but not limited to:\n\n{services_list_text}\n\nWould you like to connect with an agent to get more information about the services?\n\nY: Yes\nN: No"

    contact_flow_payload = flow_details(
        flow_header="Contact Us",
        flow_body="You have indicated that you would like to contact us.",
        flow_footer="Click the button below to proceed",
        flow_id=str("<FLOW-ID>"),
        flow_cta="Proceed",
        recipient_phone_number=phone_number,
        screen_id="CONTACT_US",
    )

    agent_flow_payload = flow_details(
        flow_header="Talk to an Agent",
        flow_body="You have indicated that you would like to talk to an agent to get more information about the services that we offer.",
        flow_footer="Click the button below to proceed",
        flow_id=str("<FLOW-ID>"),
        flow_cta="Proceed",
        recipient_phone_number=phone_number,
        screen_id="TALK_TO_AN_AGENT",
    )

    match message_option:
        case "SEND_GREETINGS_AND_PROMPT":
            payload = json.dumps(
                {
                    "messaging_product": "whatsapp",
                    "to": str(phone_number),
                    "type": "text",
                    "text": {"preview_url": False, "body": greetings_text_body},
                }
            )
        case "SERVICE_INTRO_TEXT":
            payload = json.dumps(
                {
                    "messaging_product": "whatsapp",
                    "to": str(phone_number),
                    "type": "text",
                    "text": {"preview_url": False, "body": service_intro_text},
                }
            )
        case "CHATBOT":
            LLM = Llama(
                model_path="/home/incognito/Downloads/llama-2-7b-chat.ggmlv3.q8_0.gguf.bin",
                n_ctx=2048,
            )
            # create a text prompt
            prompt = message
            # generate a response (takes several seconds)
            output = LLM(prompt)
            payload = json.dumps(
                {
                    "messaging_product": "whatsapp",
                    "to": str(phone_number),
                    "type": "text",
                    "text": {
                        "preview_url": False,
                        "body": output["choices"][0]["text"],
                    },
                }
            )
        case "CONTACT_US":
            payload = contact_flow_payload
        case "TALK_TO_AN_AGENT":
            payload = agent_flow_payload
        case "FLOW_RESPONSE":
            payload = json.dumps(
                {
                    "messaging_product": "whatsapp",
                    "to": str(phone_number),
                    "type": "text",
                    "text": {"preview_url": False, "body": message},
                }
            )

    headers = {
        "Content-Type": "application/json",
        "Authorization": "Bearer " + ACCESS_TOKEN,
    }

    requests.request("POST", url, headers=headers, data=payload)
    print("MESSAGE SENT")

Si el mensaje es un simple saludo (SEND_GREETINGS_AND_PROMPT), la respuesta contiene indicaciones adicionales (greetings_text_body).

Ejemplo de saludo del chat

De igual modo, cuando un usuario hace una pregunta sobre los servicios que se ofrecen, se le envía un mensaje de texto (service_intro_text) que incluye los servicios. Pero, además de los servicios, contiene una indicación para que el usuario elija si desea hablar con un agente.

Ejemplo de listado de servicios ofrecidos del chat

Si la información ingresada demanda una respuesta del bot de chat (CHATBOT), inicializas el modelo, lo alimentas con el contenido del mensaje y procesas la respuesta para enviársela al usuario. FLOW_RESPONSE muestra la respuesta capturada de un flujo.

Las otras opciones, CONTACT_US y TALK_TO_AN_AGENT, envían las cargas del flujo al usuario. Las cargas del flujo se originan en la función flow_details, cuyo cuerpo se muestra a continuación. La carga incorpora los detalles esenciales del flujo, incluido FLOW_ID, que puedes recuperar de la página Flujos de tu cuenta de WhatsApp Business. También es posible definir estos identificadores dentro de las variables de entorno.

def flow_details(flow_header, 
    flow_body, 
    flow_footer, 
    flow_id, 
    flow_cta, 
    recipient_phone_number, 
    screen_id
):
    # Generate a random UUID for the flow token
    flow_token = str(uuid.uuid4())

    flow_payload = json.dumps({
        "type": "flow",
        "header": {
            "type": "text",
            "text": flow_header
        },
        "body": {
            "text": flow_body
        },
        "footer": {
            "text": flow_footer
        },
        "action": {
            "name": "flow",
            "parameters": {
                "flow_message_version": "3",
                "flow_token": flow_token,
                "flow_id": flow_id,
                "flow_cta": flow_cta,
                "flow_action": "navigate",
                "flow_action_payload": {
                    "screen": screen_id
                }
            }
        }
    })

    payload = json.dumps({
        "messaging_product": "whatsapp",
        "recipient_type": "individual",
        "to": str(recipient_phone_number),
        "type": "interactive",
        "interactive": json.loads(flow_payload)
    })
    return payload
        

Este método crea action.parameters.flow_token mediante la generación de un UUID aleatorio. action.parameters.flow_action_payload.screen se transmite como un parámetro (screen_id). Idealmente, debe representar el identificador de la pantalla inicial que quieres mostrar al usuario cuando se ejecuta action.parameters.flow_cta.

Por último, añade las rutas del webhook. La solicitud GET del webhook se inicia al añadir el webhook a tu aplicación en Meta for Developers. Devuelve el hub.challenge de la solicitud cuando se realiza correctamente.

@app.route("/webhook", methods=["GET"])
def webhook_get():
    if request.method == "GET":
        if (
            request.args.get("hub.mode") == "subscribe"
            and request.args.get("hub.verify_token") == TOKEN
        ):
            return make_response(request.args.get("hub.challenge"), 200)
        else:
            return make_response("Success", 403)
        

La solicitud de POST extrae y procesa la carga del mensaje usando el método de user_message_processor del que hablamos anteriormente. Como el código solo se adapta a la carga del mensaje, generará errores al capturar cualquier otra carga. Por este motivo, puedes utilizar una instrucción if para verificar si existe un cuerpo de messages.

@app.route("/webhook", methods=["POST"])
def webhook_post():
    if request.method == "POST":
        request_data = json.loads(request.get_data())
        if (
            request_data["entry"][0]["changes"][0]["value"].get("messages")
        ) is not None:
            name = request_data["entry"][0]["changes"][0]["value"]["contacts"][0][
                "profile"
            ]["name"]
            if (
                request_data["entry"][0]["changes"][0]["value"]["messages"][0].get(
                    "text"
                )
            ) is not None:
                message = request_data["entry"][0]["changes"][0]["value"]["messages"][
                    0
                ]["text"]["body"]
                user_phone_number = request_data["entry"][0]["changes"][0]["value"][
                    "contacts"
                ][0]["wa_id"]
                user_message_processor(message, user_phone_number, name)
            else:
                # checking that there is data in a flow's response object before processing it
                if (
                    request_data["entry"][0]["changes"][0]["value"]["messages"][0][
                        "interactive"
                    ]["nfm_reply"]["response_json"]
                ) is not None:
                    flow_reply_processor(request)

    return make_response("PROCESSED", 200)
        

Además, puedes usar una función del asistente denominada flow_reply_processor para extraer la respuesta del flujo y volver a enviarla al usuario.

def flow_reply_processor(request):
    request_data = json.loads(request.get_data())
    name = request_data["entry"][0]["changes"][0]["value"]["contacts"][0]["profile"]["name"]
    message = request_data["entry"][0]["changes"][0]["value"]["messages"][0]["interactive"]["nfm_reply"][
        "response_json"]

    flow_message = json.loads(message)
    flow_key = flow_message["flow_key"]
    if flow_key == "agentconnect":
        firstname = flow_message["firstname"]
        reply = f"Thank you for reaching out {firstname}. An agent will reach out to you the soonest"
    else:
        firstname = flow_message["firstname"]
        secondname = flow_message["secondname"]
        issue = flow_message["issue"]
        reply = f"Your response has been recorded. This is what we received:\n\n*NAME*: {firstname} {secondname}\n*YOUR MESSAGE*: {issue}"

    user_phone_number = request_data["entry"][0]["changes"][0]["value"]["contacts"][0][
        "wa_id"]
    send_message(reply, user_phone_number, "FLOW_RESPONSE", name)
        

Utiliza una clave (flow_key) para diferenciar los dos flujos y, de este modo, extraer correctamente las respuestas al transmitir los identificadores correspondientes de la primera pantalla.

Antes de ejecutar el código, compáralo con la versión completa y confirma que todo coincida.

Cómo configurar el webhook

Antes de proceder, ejecuta este comando desde tu terminal:

flask --app main run --port 5000 
        

Si lo haces correctamente, deberías ver el siguiente mensaje:

* Running on http://127.0.0.1:5000
        

A continuación, ejecuta ngrok http 5000 para obtener una URL que se corresponda con tu aplicación. Copia el enlace.

Luego, en tu cuenta de desarrollador en Meta for Developers, haz clic en el menú Configuración debajo de WhatsApp en el panel de navegación izquierdo:

Configuración del chat

En la tarjeta Webhook, haz clic en Editar.

Luego, en el campo URL de devolución de llamada del diálogo que se abra, pega la URL que copiaste y anexa /webhook.

Añade el token de la variable TOKEN del archivo .env en el campo Token de verificación. Haz clic en Verificar y guardar para cerrar el diálogo.

Ahora, desde la misma tarjeta, haz clic en Administrar y marca el campo Mensajes. La tarjeta debe verse así:

Webhook del chat de WhatsApp

El webhook ya está listo.

Ejecutar la aplicación

Puedes enviar un mensaje como "Hola" al número de tu cuenta. Deberías recibir la respuesta correspondiente. Intenta responder con una de las indicaciones que se muestran en el menú para probar los flujos:

Chat de WhatsApp ejecutando la app

A continuación, sigue otra captura de pantalla donde se muestra la respuesta del bot de chat. El usuario solicita una traducción rápida, que el bot es capaz de proporcionar.

Chat de WhatsApp ejecutando la app

Conclusión

WhatsApp Flows sirve como una herramienta eficaz que las empresas pueden utilizar para recopilar información estructurada, lo cual les permitirá mejorar las interacciones con los clientes y optimizar la comunicación entre la empresa y los consumidores. Una de las formas de crear flujos es con Flow Builder, una herramienta disponible en el administrador de WhatsApp que ofrece una interfaz fácil de usar para diseñar flujos.

Esta aplicación de demostración te permite entender cómo puedes aprovechar WhatsApp Flows para mejorar la interacción con los clientes y los análisis basados en datos. Para usos más especializados, también puedes configurar Llama 2 y WhatsApp Flows para conectar tu bot de chat a un modelo personalizado o entrenarlo con un origen de datos de propiedad exclusiva, lo que le permitirá responder preguntas de lenguajes naturales acerca de tu producto y otras funcionalidades.

Utiliza WhatsApp Flows para llevar las interacciones con clientes a otro nivel y optimizar la recopilación de datos en tus aplicaciones actuales. Probar.