Back To Top

July 24, 2024

Adding WhatsApp Flows to Your Chatbot Experience

  • 0

More businesses than ever are now utilizing chatbots on WhatsApp to interact with customers, understand their needs, and collect crucial data. However, efficiently gathering and organizing this data can be challenging. This is where WhatsApp Flows come into play.

By integrating chatbots with WhatsApp Flows, businesses can better interpret incoming customer information. Chatbots can then trigger specific Flows for data collection, tailored to the conversation’s context.

In this tutorial, you’ll create a chatbot using Llama 2 and Python, and connect it to WhatsApp Flows to boost its data collection capabilities. You’ll see firsthand how WhatsApp Flows enhances the chatbot experience, making it more user-friendly and improving the accuracy and efficiency of customer data collection.

Enhancing the Chatbot Experience with WhatsApp Flows

Your chatbot will use WhatsApp Flows to respond to user requests and prompts for data collection. Specifically, it will enable users to interact with the chatbot, search for information about services offered by a fictitious hotel in Spain, and contact the company for support.

The chatbot will employ simple if statements along with the standard Llama 2 model to offer access to a broad knowledge base.

Steps

Part 1:

Part 2:

  • Convert the Llama 2 model from GGML to GGUF.

  • Write the Python code for integrating the Flows and the chatbot.
  • Create a webhook for listening to the messages.
  • Run the application.

How to Create Flows

In this portion, you’ll use the Flow Builder to create some Flows. Alternatively, you can use Flows API, which we won’t cover here.

Part 1 Prerequisites

To follow along, ensure you have:

Finally, ensure you complete the required steps for using Flows. You can also preview the complete project code.

Getting Started

To start, go to the Flows page in your WhatsApp Business Account. If this is your first time using Flows, you will see a button labeled “Start building Flows.” If not, there will be a “Create Flow” button at the top-right of the page.Click the appropriate button to open a dialog box where you can enter details about your Flow:

The Services Inquiry Flow

First, you’ll create a Flow that allows the user to seek information on services offered by the hotel company.

Enter a name in the Name field. Then, under the Categories drop-down, choose Sign Up, and leave the Template drop-down set to None. Click Submit.

The next page displays an editor on the left and a preview on the right.

Replace the contents of the editor with the JSON markup for your Flow. (You can learn more about Flow JSON in the developer documentation.)

Save the Flow, and your preview should resemble the image below:

The Flow includes a single screen where the user can enter their details, select the services they are interested in, and optionally add an additional message. When the user clicks “Submit,” the Flow closes and sends the captured data to your business for processing. Although one method of transmitting this data involves using endpoints, this project doesn’t require one. Instead, the data will be passed to the same webhook that powers the chatbot.

You can achieve this transfer using actions. You define the data to be passed to the next screen using the payload object:

...

"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"
    }
}
...

In the snippet, the user’s button-click action triggers the on-click-action, capturing the data in the payload. It then sends the payload to your webhook server and closes the Flow via the complete action.

You can assign the payload keys as you would any variable. The corresponding values can represent data objects or the names of Flow components (similar to an HTML form’s name attribute).

Now, you can see the Flow in action and simulate a real user experience using the Interactive preview toggle:

 

 

After testing, you can publish the Flow, since it’s currently in the Draft state. To do so, open the menu to the right of Save and click Publish. The Flow is now ready and usable.

The Contact Us Flow

Now, you’ll create a “Contact us” Flow.

Begin by repeating the same initial Flow creation process. For the category, choose Contact Us. Replace the contents of the editor with this JSON markup to render the following:

 

 

Publish the Flow and continue to the next section to set up a chatbot. The section features three functions — send_messageflow_details, and flow_reply_processor — which contain vital logic for sending the Flows to users. The section also includes the logic for processing incoming Flow payloads. Therefore, we recommend looking through it even if you’ve already built a chatbot.

How to Set Up the Chatbot

Next, you’ll configure the chatbot and integrate it into your Flows.

Part 2 Prerequisites

Before continuing, ensure you have the following:

  • Basic knowledge and a recent version of Python

  • The HuggingFace version of Llama 2 downloaded. The HuggingFace Llama 2 model requires no additional tools or specialized hardware. You can also use the official version, but it necessitates additional setup.

  • Your account’s access token and phone number ID

  • A code editor

Using Python to Integrate the Flows and Chatbot

The chatbot will operate through a predefined script designed to assist users based on their input. Upon initial interaction, it provides a personalized greeting and a text-based menu tailored to the user’s message. These options address specific needs: querying the services offered by the hotel, contacting an agent, or interacting with a Llama-powered chatbot.

Responding with an alphanumeric character will connect the user to the corresponding service or action. Any other response triggers the default chatbot functionality, which helps with general inquiries or guides the user through the available services based on further conversation.

To start, create a virtual environment by running the following command in your terminal:

python -m venv venv
        

Activate it:

source venv/bin/activate
        

Then, install the required packages:

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

You use Flask to create routes and interact with the API, requests for sending internet requests, llama-cpp-python for interacting with the model, and python-dotenv for loading environment variables.

Next, create an environment file named .env and the following content, assigning the values appropriately. (You can use any string for the TOKEN.)

TOKEN = 
ACCESS_TOKEN = 
PHONE_NUMBER_ID = 
        

In the same directory, create a file called main.py and start by adding the packages you’ll use:

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
       

Now, initialize the variables and classes. The snippet also initiates Flask and calls the load_dotenv() method to help load the 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"
]

The service_list stores the services offered by the hotel. The code_prompt_texts list contains the options corresponding to users’ input choices, which are, 12Y, and N using the function below. The function below will help map the user’s replies to the corresponding option.

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
        

The code converts the strings to lowercase to prevent mismatches when running the conditional logic. Its match…case structure matches the user-entered prompts to the outputs. For example, a user entering “1” will trigger the "Contact us" functionality.

Next, the following function contains an if statement that uses the Python RegEx package, re, to search for certain terms from a customer message to decide which type of response to send the user:

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)
        

Now, a message such as "Hello there" will fire the send_message method with SEND_GREETINGS_AND_PROMPT as the second argument. Below is the send_message method. Replace the content between <xxx> appropriately.

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")

If the message is a simple greeting (SEND_GREETINGS_AND_PROMPT), the response contains additional prompts (greetings_text_body).

Chatbox sample greeting

In the same fashion, when a user asks a question about the services offered, a text message (service_intro_text) containing the services is sent to the user. In addition to the services, it also contains a prompt to the user to choose if they want to talk to an agent.

Chatbox sample offers

If the entry requires a chatbot response (CHATBOT), you initialize the model, feed it the message contents, and process the response to send back to the user. FLOW_RESPONSE displays a Flow’s captured response.

The other options, CONTACT_US and TALK_TO_AN_AGENT, send Flow payloads to the user. The Flow payloads originate from the flow_details function, whose body is shown below. The payload incorporates essential Flow details, including the FLOW_ID, which you can retrieve from the Flows page of your WhatsApp Business account. It’s also possible to define these IDs within your environment variables.

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
        

This method creates the action.parameters.flow_token by generating a random UUID. The action.parameters.flow_action_payload.screen is passed as a parameter (screen_id). Ideally, it should represent the ID of the initial screen you intend to show the user when the action.parameters.flow_cta executes.

Finally, add the webhook routes. The webhook GET request initiates when adding the webhook to your app on Meta for Developers. It returns the request’s hub.challenge when successful.

@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)

The POST request extracts and processes the message payload using the user_message_processor method introduced earlier. Because the code only caters to the message payload, it will raise errors upon capturing any other payload. For this reason, you can use an if statement to check whether there’s a messages body.

@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)

Additionally, you can use a helper function called flow_reply_processor to extract the response from the Flow and send it back to the user:

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)

It uses a key(flow_key) to differentiate between the two Flows and thereby extracting the responses appropriately while passing in the correct first screen IDs.

Before running the code, compare it with the full version and confirm that everything matches.

How to Set Up the Webhook

Before proceeding, run this command from your terminal:

flask --app main run --port 5000 

If successful, you should see a message that reads:

* Running on http://127.0.0.1:5000

Next, run ngrok http 5000 to obtain a URL that maps to your application. Copy the link.

Then, in your developer account on Meta for Developers, click the Configuration menu under WhatsApp in the left navigation pane:

Chatbox configuration

In the Webhook card, click Edit.

Then, in the Callback URL field of the dialog that opens, paste the copied URL and append /webhook to it.

Add the token from your .env file’s TOKEN variable into the Verify token field. Click Verify and save to close the dialog.

Now, from the same card, click Manage and check the messages field. The card should appear as follows:

WhatsApp Chatbox webhook

The webhook is now ready.

Running the Application

You can send a message like “Hello” to your account number. You should receive the appropriate response. Try replying with a prompt shown in the menu to test the Flows:

WhatsApp Chatbox running app

Below is another screenshot showing the chatbot’s response. The user asks for a quick translation, which the bot can provide.

WhatsApp Chatbox running app

Conclusion

WhatsApp Flows are a powerful tool for businesses to collect structured information, enhancing customer interactions and streamlining communication between businesses and consumers. One way to build Flows is with WhatsApp Manager Flow Builder, which offers a user-friendly interface for designing Flows.

This demo application demonstrates how you can leverage WhatsApp Flows for improved customer engagement and data-driven analysis. For more specialized uses, you can also configure Llama 2 and WhatsApp Flows to connect your chatbot to a custom model or train it on a proprietary data source, enabling it to answer natural language questions about your product and other functionalities.

Utilize WhatsApp Flows to elevate customer interactions and streamline data collection in your applications today.

 

Prev Post

Revolutionizing Business Communication: The Latest WhatsApp Features

Next Post

A Deep Dive into the WhatsApp Website | Revolutionizing Business…

post-bars
Mail Icon

Newsletter

Get Every Weekly Update & Insights

Leave a Comment