返回開發人員最新消息

將 WhatsApp Flows 加至聊天機械人體驗

2024年3月20日發佈者:Gafi G 和 Iryna Wagner

現今,越來越多的企業選擇透過 WhatsApp 使用聊天機械人,以與顧客互動、了解他們的需求,以及蒐集所需的資料。然而,他們在蒐集和整理資料時,往往面臨著諸多效率方面的挑戰。在這種情況下,WhatsApp Flows 便能大派用場。

只需將聊天機械人與 WhatsApp Flows 整合,企業便可以更高效地解讀收到的顧客資料。如此一來,聊天機械人能夠根據對話情景,啟動專為蒐集資料而設的特定 Flows 流程。

在本教學導覽中,您將使用 Llama 2 和 Python 來構建聊天機械人,並將其連結至 WhatsApp Flows,以提升其資料蒐集能力。在這個過程中,您將可見證 WhatsApp Flows 如何提升聊天機械人體驗的易用性,並提高顧客資料蒐集的準確性和效率。

使用 WhatsApp Flows 加強聊天機械人體驗

聊天機械人會使用 WhatsApp Flows 來回覆用戶的要求和提示,以便蒐集資料。以本文所用的虛構西班牙酒店為例,它可讓用戶與聊天機械人交談、搜尋酒店所提供的服務之資訊,並聯絡酒店以尋求支援。

聊天機械人將配搭使用簡單的 if 陳述式和標準 Llama 2 模型,以便系統存取充足的常識知識庫。

您需採取的步驟

第 1 部分:

第 2 部分:

  • 將 Llama 2 模型從 GGML 轉換為 GGUF

  • 編寫 Python 程式碼以整合 Flows 流程和聊天機械人。
  • 建立 Webhook 以偵聽訊息。
  • 運行應用程式。

第 1 部分:如何建立 Flows 流程

在這部分,您需要使用流程建造工具來建立一些 Flows 流程。或者,您可以使用 Flows API;本文對此不作詳細闡述。

第 1 部分必備條件

若要採取本部分的步驟,請確保您具備下列條件:

最後,確保您已完成使用 Flows 所需執行的步驟。您亦可預覽完整專案程式碼。

新手指南

首先,前往您 WhatsApp Business 帳戶的「流程」頁面。若這是您首次與 Flows 互動,您應該會看到一個標題為「開始建立流程」的按鈕。否則,此頁面右上方會顯示「建立流程」按鈕。

點擊畫面所顯示的按鈕以開啟對話框,並在當中輸入您 Flows 流程的一些詳細資訊:

WhatsApp Chatbox 新手指南

「服務查詢」Flows 流程

首先,您需要建立一個 Flows 流程,讓用戶用來搜尋酒店所提供的服務之資訊。

在「名稱」欄位中輸入所需名稱。接著,在「類別」下拉式選單中,選擇「註冊」,並將「範本」下拉式選單設為「」。點擊「提交」。

在下一個頁面中,畫面左側將顯示編輯工具,右側則會顯示預覽畫面。

將編輯工具的內容替換為您 Flows 流程的 JSON 標記。您可以查閱開發人員文件,進一步了解 Flow JSON。

儲存 Flows 流程;您的預覽畫面應與下圖相似:

Chatbox 流程

此 Flows 流程包含一個畫面,用戶可在當中輸入其詳細資訊、選擇感興趣的服務,以及查閱一則選用的額外訊息。當用戶點擊「提交」時,這個 Flows 流程便會關閉,並將收到的資料傳送至您企業以供處理。傳輸此類資料的方法之一便是使用端點。不過,此專案不會用到端點。這些資料會傳送至支援聊天機械人的同一個 Webhook。

您可以使用各個動作來執行此傳輸程序。您需要使用 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"
    }
}
...
        

在此程式碼片段中,用戶的按鈕點擊動作會觸發 on-click-action,以擷取裝載中的資料。接著,它會將裝載傳送至 Webhook 伺服器,並透過 complete 動作關閉 Flows 流程。

您可以沿用分配任何變數的方法來分配 payload 鍵。相應的值可代表資料物件或 Flows 流程元件的名稱(類似 HTML 表格的名稱屬性)。

現在,您應會查看運作中的 Flows 流程,並使用「互動式預覽」切換按鈕來模擬真實的用戶體驗:

Chatbox 互動式預覽

測試之後,您便可發佈目前仍處於「草稿」狀態的 Flows 流程。若要發佈此 Flows 流程,請開啟「儲存」右側的選單並點擊「發佈」。這個 Flows 流程現已準備就緒並可供使用。

「聯絡我們」Flows 流程

現在,您需要建立「聯絡我們」Flows 流程。

首先,重複執行上文所述的初始 Flows 流程建立程序,但將類別設為「聯絡我們」。將編輯工具的內容替換為這個 JSON 標記,以顯示以下畫面:

Chatbox「聯絡我們」流程

發佈此 Flows 流程並繼續執行下一部分,以設定聊天機械人。該部分會用到 send_messageflow_detailsflow_reply_processor 這 3 個函數;這些函數包含將 Flows 流程傳送給用戶的必要邏輯。該部分亦會提及處理傳入 Flows 流程裝載的邏輯。因此,即使您已構建聊天機械人,我們仍建議您仔細查閱該部分的內容。

第 2 部分:如何設定聊天機械人

接下來,您需要配置聊天機械人並將其整合到您的 Flows 流程。

第 2 部分必備條件

在繼續操作之前,請確保您具備下列條件:

  • 擁有 Python 的基本知識和最新版本。

  • 已下載 HuggingFace 版本 Llama 2。HuggingFace Llama 2 模型無需使用任何其他工具或專用硬件。您亦可使用官方版本 Llama 2,但您將需進行額外設定。

  • 擁有您帳戶的存取憑證和手機號碼編號。

  • 擁有程式碼編輯工具。

使用 Python 整合 Flows 流程和聊天機械人

聊天機械人將透過一組預先定義的指令碼來運作;該組指令碼旨在根據用戶輸入的內容,為其提供相應的協助。在初次互動時,聊天機械人會根據用戶的訊息,提供個人化問候語文字,以及一個文字選單。這些選項可迎合特定需求:酒店所提供的查詢服務、聯絡酒店的客戶服務人員,或與由 Llama 支援的聊天機械人互動。

使用英數字元回覆時,系統會將用戶連結至相應的服務或動作。然而,任何其他回覆均將觸發預設聊天機械人功能,以協助處理一般查詢,或根據進一步對話,引導用戶取得可用服務。

首先,在終端上運行以下指令以建立虛擬環境:

python -m venv venv
        

啟用此環境:

source venv/bin/activate
        

接著,安裝所需套件:

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

您需要使用 Flask 來建立路線並與 API 互動、使用 requests 來傳送互聯網要求、使用 llama-cpp-python 來與模型互動,並使用 python-dotenv 來載入環境變數。

接下來,建立名為 .env 的環境檔案和下列內容,以分配相應的值;VERIFY_TOKEN 可使用任何字串。

TOKEN = 
ACCESS_TOKEN = 
PHONE_NUMBER_ID = 
        

在同一個目錄中,建立名為 main.py 的檔案,並開始新增您需要使用的套件:

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
        

現在,初始化變數和類別。此程式碼片段亦會啟動 Flask 並呼叫 load_dotenv() 方法,以助載入變數:

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 會用於儲存酒店所提供的各項服務。code_prompt_texts 清單包含與用戶的輸入選擇相應的選項;這些選項分別是「1」、「2」、「」和「」,可使用下方函數來指定。下方函數有助將用戶的回覆配對至相應的選項。

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
        

程式碼會將字串轉換為小楷字母,以免運行條件式邏輯時出現配對錯誤的情形。match…case 架構會將用戶輸入的提示配對至輸出項目。舉例來說,用戶輸入「1」時將觸發 "Contact us" 功能。

接下來,以下函數包含使用 Python RegEx 套件 reif 陳述式,以從顧客訊息當中搜尋特定字詞,從而判斷向用戶傳送哪類回覆:

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)
        

現在,訊息(如 "Hello there")將會觸發 send_message 方法,當中第二個引數為 SEND_GREETINGS_AND_PROMPT。以下是 send_message 方法;視乎具體情況,替換 <xxx> 之間的內容。

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

若訊息是簡單的問候語(SEND_GREETINGS_AND_PROMPT),回覆將包含額外的提示(greetings_text_body)。

Chatbox 問候語範例

同樣地,當用戶詢問關於酒店所提供服務的問題時,傳送給用戶的文字訊息(service_intro_text)便會包含相關服務。除了這些服務外,該訊息還會包含一則提示,提醒用戶選擇是否想與客戶服務人員交流。

Chatbox 服務範例

若輸入內容需要聊天機械人作出回覆(CHATBOT),則您需要初始化該模型、為其提供訊息內容,並處理需要傳回給用戶的回覆。FLOW_RESPONSE 會顯示 Flows 流程所擷取的回覆。

CONTACT_USTALK_TO_AN_AGENT 等其他選項則會將 Flows 流程裝載傳送至用戶。Flows 流程裝載來自 flow_details 函數,其主體如下所示。此裝載包含 Flows 流程的必要詳情,包括 FLOW_ID;您可以從 WhatsApp Business 帳戶的「流程」頁面獲取此類資訊。此外,您也可以在環境變數中定義這些編號。

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
        

此方法可產生隨機 UUID 以建立 action.parameters.flow_token。系統會將 action.parameters.flow_action_payload.screen 傳遞為參數(screen_id)。在理想情況下,它應代表執行 action.parameters.flow_cta 時,您想要向用戶顯示的初始畫面之編號。

最後,新增 Webhook 路線。當您在 Meta for Developers 將 Webhook 加至自家應用程式時,程式碼便會發起 Webhook GET 要求。若要求成功,系統便會傳回要求的 hub.challenge

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

POST 要求會使用上文所述的 user_message_processor 方法來擷取並處理訊息裝載。由於此程式碼僅用於處理訊息裝載,因此擷取任何其他裝載時會出現錯誤。因此,您可使用 if 陳述式來檢查是否存在 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)
        

此外,您可以使用名為 flow_reply_processor 的協助工具函數,從 Flows 流程中擷取回覆並將其傳回給用戶:

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)
        

此函數會使用 flow_key 鍵來區分這兩個 Flows 流程,並相應地擷取回覆,同時傳遞正確的首個畫面編號。

在執行程式碼前,將其與完整版本加以比較,並確認所有元素均為一致。

如何設定 Webhook

在繼續操作之前,先從您的終端執行此指令:

flask --app main run --port 5000 
        

若執行成功,您應該會看到下列訊息:

* Running on http://127.0.0.1:5000
        

接下來,執行 ngrok http 5000 以取得與您應用程式相應的網址。複製此連結。

接著,前往您的 Meta for Developers 開發人員帳戶,點擊左側導覽面板中「WhatsApp」下方的「配置」選單:

Chatbox 配置

在「Webhook」小卡片中,點擊「編輯」。

然後,在所開啟對話框的「回調網址」欄位中,貼上所複製的網址並附上 /webhook

在「驗證憑證」欄位中,加入 .env 檔案的 TOKEN 變數所提供的憑證。點擊「驗證並儲存」以關閉對話框。

現在,在同一個小卡片中,點擊「管理」並檢查「訊息」欄位。該小卡片應如下所示:

WhatsApp Chatbox Webhook

Webhook 現已就緒。

運行應用程式

您可以向自己的帳戶號碼傳送一則訊息,如「您好」。您應該會收到正確的回覆。嘗試使用選單所顯示的提示來回覆,以測試 Flows 流程:

WhatsApp Chatbox 運行應用程式

以下是另一個螢幕截圖,當中顯示了聊天機械人的回覆。用戶要求簡單地翻譯一些內容,而聊天機械人可以做到這一點。

WhatsApp Chatbox 運行應用程式

結論

WhatsApp Flows 是一款供企業蒐集結構化資料的強大工具,有助改善顧客互動,並簡化企業與消費者之間的溝通交流。建立 Flows 流程的方式之一是使用 WhatsApp 管理工具的流程建造工具;該工具提供簡單易用的 Flows 流程設計介面。

本文的示範應用程式概要地介紹了如何利用 WhatsApp Flows,從而改善顧客互動及進行以資料為本的分析。若要用於更具體的專業用途,您亦可配置 Llama 2 和 WhatsApp Flows,將聊天機械人連結至自訂模型,或者使用專有資料來源來予以訓練,使其能夠回答有關您商品及其他功能的自然語言問題。

立即使用 WhatsApp Flows,改進顧客互動體驗並簡化應用程式的資料蒐集流程。立即試用