More businesses than ever currently use chatbots via WhatsApp to engage with customers, discern their needs, and gather essential data. However, this process of collecting and organizing data can pose challenges in terms of efficiency. Enter WhatsApp Flows.
Integrating chatbots with WhatsApp Flows lets businesses interpret incoming customer information more effectively. Chatbots can then initiate specific Flows tailored for data collection, depending on the context of the conversation.
In this tutorial, you’ll build a chatbot using Llama 2 and Python, connecting it to WhatsApp Flows to enhance its data collection capabilities. In doing so, you’ll experience how WhatsApp Flows enhances the user-friendliness of the chatbot experience and improves the accuracy and efficiency of customer data collection.
Your chatbot will respond to user requests and prompts with WhatsApp Flows for data collection. More specifically, it’ll allow users to converse with the chatbot, search for information about services offered by a fictitious hotel in Spain, and contact the company for support.
The chatbot will use simple if
statements in conjunction with the standard Llama 2 model to provide access to a sufficient general knowledge base.
Part 1:
Create Flows using the WhatsApp Manager Flow Builder.
Part 2:
Convert the Llama 2 model from GGML to GGUF.
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.
To follow along, ensure you have:
ngrok installed.
A verifieddeveloper account on Meta for Developers and familiarity with Cloud API hosted by Meta.
Finally, ensure you complete the required steps for using Flows. You can also preview the complete project code.
To begin, navigate to the Flows page in your WhatsApp Business Account. If this is your first interaction with Flows, you should see a button titled Start building Flows. Otherwise, there will be a Create Flow button on the top-right of the page.
Click the displayed button to open a dialog box where you can input some details about your 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 contains one screen that allows the user to enter their details, select the services of interest, and an optional additional message. When the user clicks Submit, the Flow closes and sends the captured data to your business for processing. One method of transmitting this data involves the use of endpoints. However, this project doesn’t need an endpoint. 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.
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_message
, flow_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.
Next, you’ll configure the chatbot and integrate it into your Flows.
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
The chatbot will operate through a predefined script designed to assist users based on their input. Upon initial interaction, it offers a personalized greeting text and a text-based menu depending on the user’s message. These options cater to specific needs: querying services offered by the hotel, contacting one of its agents, or engaging with a Llama-powered chatbot.
Responding with an alphanumeric character will connect the user to the corresponding service or action. However, any other response triggers the default chatbot functionality, which assists with general inquiries or guides the user through the available services based on further conversation.
To begin, create a virtual environment by running the command below 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, 1, 2, Y, 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
).
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.
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.
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:
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:
The webhook is now ready.
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:
Below is another screenshot showing the chatbot’s response. The user asks for a quick translation, which the bot can provide.
WhatsApp Flows serve as a powerful tool for businesses to collect structured information, enhancing customer interactions and streamlining communication between businesses and consumers. One of the ways to build Flows is with WhatsApp Manager Flow Builder, which offers a user-friendly interface for designing Flows.
This demo application offers a glimpse of 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.
Leverage WhatsApp Flows to elevate customer interactions and streamline data collection in your applications today. Try it now.
Sign up for monthly updates from Meta for Developers.