Volver a las novedades para desarrolladores

Añade WhatsApp Flows a tu experiencia de bot de chat

20 de marzo de 2024DeGafi G e Iryna Wagner

Ahora, más empresas que nunca usan los bots de chat a través de WhatsApp para interactuar con los clientes, identificar sus necesidades y recopilar datos básicos. Sin embargo, este proceso de recopilación y organización de datos puede suponer varios desafíos en cuanto a la eficacia. Descubre WhatsApp Flows.

Integrar los bots de chats con WhatsApp Flows permite a las empresas interpretar la información que reciben de los clientes de una forma más eficaz. Los bots de chat pueden iniciar flujos específicos personalizados para recopilar los 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 habilidades de recopilación de datos. Con esto, verás en primera persona cómo WhatsApp Flows hace que la experiencia de bot de chat sea más accesible para el usuario y mejora la precisión y la eficacia de la recopilación de datos de clientes.

Mejora la experiencia de bot de chat con WhatsApp Flows

El bot de chat responderá a las solicitudes e instrucciones de los usuarios con WhatsApp Flows para recopilar datos. De forma más específica, permitirá a los usuarios conversar con el bot, buscar información sobre los servicios ofrecidos por un hotel en España y ponerse en contacto con la empresa para obtener ayuda.

El bot de chat usará expresiones sencillas if junto con el modelo estándar Llama 2 para darle acceso a una base de conocimientos general.

Pasos que debes seguir

Parte 1:

Parte 2:

  • Convierte el modelo Llama 2 de GGML a GGUF.

  • Escribe el código de Python para integrar los flujos y el bot de chat.
  • Crea un webhook para escuchar los mensajes.
  • Publica la aplicación.

Parte 1: cómo crear los flujos

En esta parte, usarás el creador de flujos para generar algunos flujos. También puedes usar la API de flujos, pero no lo detallaremos aquí.

Parte 1: requisitos previos

Para continuar, asegúrate de que dispones de lo siguiente:

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

Primeros pasos

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

Haz clic en el botón que se muestra para abrir un cuadro de diálogo en el que puedes introducir algunos detalles de tu flujo:

Primeros pasos con los bots de chat de WhatsApp

Flujo de consulta de servicios

Primero, crearás un flujo que permita al usuario buscar información sobre los servicios ofrecidos por una empresa hotelera.

Introduce un nombre en el campo Nombre. Después, en el desplegable Categorías, selecciona Registrarte y deja el desplegable Plantilla con la opción Ninguna seleccionada. Haz clic en Enviar.

La siguiente página muestra un editor a la izquierda y una vista previa a la derecha.

Sustituye los contenidos del editor por el marcado JSON de tu flujo (obtén más información sobre el JSON del flujo en la documentación para desarrolladores).

Guarda el flujo. La vista previa deberá ser similar a esta imagen:

Flujo del bot de chat

El flujo consta de una pantalla que permite a los usuarios introducir sus datos, seleccionar los servicios que le interesan y añadir un mensaje adicional opcional. Cuando el usuario hace clic en Enviar, el flujo se cierra y envía los datos recopilados a tu empresa para realizar el tratamiento. Uno de los métodos para transmitir los datos incluye el uso de extremos. Sin embargo, este proyecto no necesita ningún extremo. Los datos se pasarán al mismo webhook con el que funciona el bot de chat.

Puedes llegar a esta transferencia usando varias acciones. Define los datos que se pasarán a la siguiente pantalla con 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 hacer clic en el botón del usuario activa on-click-action, lo que captura los datos en la carga útil. La carga útil se envía entonces al servidor del webhook y cierra el flujo a través de una acción complete.

Puedes asignar las claves payload como asignarías cualquier variable. Los valores correspondientes pueden representar objetos de datos o los nombres de los componentes del flujo (similares al atributo de nombre de un formulario HTML).

Ahora puedes ver el flujo en funcionamiento y simular una experiencia de usuario real con la opción Vista previa interactiva:

Vista previa interactiva del bot de chat

Después de la prueba, puedes publicar el flujo, que ya está en el estado Borrador. Para ello, abre el menú a la derecha de la opción Guardar y haz clic en Publicar. El flujo ya está preparado y listo para usar.

Flujo de contacto

Ahora, crearás un flujo de contacto.

Empieza repitiendo los pasos iniciales del proceso de creación de flujos. Selecciona la categoría Contactar con nosotros. Sustituye los contenidos del editor por este marcado JSON para reproducir lo siguiente:

Flujo de contacto del bot de chat

Publica el flujo y pasa a la siguiente sección para configurar un bot de chat. Esta sección cuenta con tres funciones: send_message, flow_details y flow_reply_processor, que contienen la lógica básica para enviar los flujos a los usuarios. También incluye la lógica para procesar las cargas útiles de los flujos entrantes. Por lo tanto, te recomendamos que le eches un vistazo aunque 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 en tus flujos.

Parte 2: requisitos previos

Antes de continuar, asegúrate de que dispones de lo siguiente:

  • Conocimientos básicos y una versión reciente de Python.

  • La versión HuggingFace de Llama 2 descargada. El modelo HuggingFace de Llama 2 no requiere herramientas adicionales ni hardware específico. También puedes usar la versión oficial, pero requiere una configuración adicional.

  • El identificador 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á mediante un script prestablecido diseñado para ayudar a los usuarios en función de la información que introduzcan. En la interacción inicial, proporciona un texto de saludo personalizado y un menú de texto que depende del mensaje del usuario. Estas opciones atienden a necesidades específicas: servicios de consultas ofrecidos por el hotel, contactar con un agente o interactuar con un bot de chat que funciona con Llama.

Al responder con un carácter alfanumérico, el sistema conectará al usuario con la acción o el servicio correspondientes. No obstante, ante cualquier otra respuesta se activa la función del bot de chat predeterminado, que ofrece asistencia con las cuestiones generales o guía al usuario por los servicios disponibles según vaya indicando 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
        

Después, instala los paquetes necesarios:

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

Usa 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 del entorno.

A continuación, crea un archivo de entorno con el nombre .env y el siguiente contenido, asignando los valores según corresponda (puedes usar cualquier cadena para TOKEN).

TOKEN = 
ACCESS_TOKEN = 
PHONE_NUMBER_ID = 
        

En el mismo directorio, crea un archivo con el nombre main.py y empieza a añadir los paquetes que vas a usar:

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, inicia las variables y las clases. El fragmento también inicia Flask y llama al método load_dotenv() para que le ayude 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"
]

service_list almacena los servicios que ofrece el hotel. La lista code_prompt_texts contiene las opciones correspondientes a las opciones de entrada de los usuarios, que son 1, 2, Y y N, mediante la siguiente función. Esta función ayudará a asignar las respuestas de los usuarios 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 minúsculas para evitar errores con las concordancias al ejecutar la lógica condicional. La estructura match…case coincide con las instrucciones introducidas por los usuarios para los resultados. Por ejemplo, si un usuario introduce “1”, se activará la función "Contact us".

Después, la siguiente función contiene una expresión if que usa el paquete RegEx de Python, re, para buscar ciertos términos a partir de un mensaje del cliente y determinar qué tipo de respuesta se envía 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)
        

Un mensaje del tipo "Hello there" lanza el método send_message con SEND_GREETINGS_AND_PROMPT como segundo argumento. A continuación verás el método send_message. Sustituye 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 instrucciones adicionales (greetings_text_body).

Saludo de muestra del bot de chat

De la misma forma, cuando un usuario hace una pregunta sobre los servicios que se ofrecen, se envía un mensaje de texto (service_intro_text) que indica los servicios. Además, también incluye una instrucción para que el usuario elija si quiere hablar con un agente.

Ofertas de muestra del bot de chat

Si la entrada requiere una respuesta del bot de chat (CHATBOT), inicia el modelo, incluye los contenidos del mensaje y procesa la respuesta para enviársela de vuelta al usuario. FLOW_RESPONSE muestra una respuesta capturada de un flujo.

El resto de las opciones, CONTACT_US y TALK_TO_AN_AGENT, envían cargas útiles del flujo al usuario. Las cargas útiles del flujo provienen de la función flow_details, cuyo cuerpo se muestra a continuación. Incorpora los detalles básicos del flujo, incluido el FLOW_ID, que encontrarás en la página Flujos de tu cuenta de WhatsApp Business. También puedes establecer estos identificadores dentro de las variables de tu 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 generando un UUID aleatorio. action.parameters.flow_action_payload.screen se pasa en forma de parámetro (screen_id). Lo ideal sería que representara al identificador de la pantalla inicial que pretendes mostrar al usuario cuando se ejecute action.parameters.flow_cta.

Por último, añade las rutas del webhook. Se inicia la solicitud del webhook GET al añadir el webhook a tu aplicación en Meta for Developers. Si se produce con éxito, devuelve hub.challenge de la solicitud.

@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 POST extrae y procesa la carga útil del mensaje usando el método user_message_processor que se introdujo anteriormente. Dado que el código solo se adapta a la carga útil del mensaje, detectará errores al capturar otra carga útil. Por este motivo, puedes utilizar la expresión if para comprobar si hay 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 utilizar la función del asistente denominada flow_reply_processor para extraer la respuesta del flujo y devolverla 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)
        

Usa una clave (flow_key) para diferenciar ambos flujos y, por tanto, extraer las respuestas según corresponde mientras pasa los primeros identificadores de pantalla correctos.

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

Cómo configurar el webhook

Antes de continuar, ejecuta este comando desde tu terminal:

flask --app main run --port 5000 
        

Si todo está correcto, deberías ver el siguiente mensaje:

* Running on http://127.0.0.1:5000
        

Después, ejecuta ngrok http 5000 para obtener una URL que se asigne a tu aplicación. Copia el enlace.

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

Configuración del bot de chat

En el apartado Webhook, haz clic en Editar.

Después, en el campo URL de devolución de llamada del cuadro de diálogo que se abre, añade la URL copiada y adjunta /webhook.

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

Ahora, desde el mismo apartado, haz clic en Administrar y comprueba el campo Mensajes. El apartado debería tener el siguiente aspecto:

Webhook de bot de chat de WhatsApp

El webhook ya está listo.

Publicación de la aplicación

Puedes enviar un mensaje del tipo “Hola” a tu número de cuenta. Deberías recibir la respuesta adecuada. Intenta contestar con una instrucción del menú para probar los flujos:

Aplicación en funcionamiento de bot de chat de WhatsApp

A continuación verás otra captura de pantalla en la que se muestra la respuesta del bot de chat. El usuario pide una breve traducción, que el bot puede proporcionarle.

Aplicación en funcionamiento de bot de chat de WhatsApp

Conclusión

WhatsApp Flows es una potente herramienta para empresas para recopilar información de forma estructurada, mejorar las interacciones con los clientes y optimizar la comunicación entre consumidores y empresas. Los flujos se pueden generar mediante el creador de flujos del Administrador de WhatsApp, que cuenta con una interfaz intuitiva para diseñarlos.

Esta aplicación de demostración te da una idea de cómo puedes aprovechar WhatsApp Flows para mejorar las interacciones con el cliente y los análisis basados en datos. Para usos más especializados, también puedes configurar Llama 2 y WhatsApp Flows con el fin de conectar tu bot de chat a un modelo personalizado o entrenarlo con un origen de datos privado, lo que le permitirá responder preguntas en lenguaje natural sobre tu producto y otras funciones.

Aprovecha todas las posibilidades de WhatsApp Flows para llevar las interacciones con el cliente a otro nivel y agilizar la recopilación de datos en tus aplicaciones. Pruébalo.