返回開發人員最新消息

新增 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 蒐集資料,以回應用戶的要求和提供提示。更具體來說,它將能讓用戶與聊天機器人對話、搜尋西班牙虛構飯店提供的服務資訊,並聯繫該公司以獲得支援。

這個聊天機器人將會結合標準的 Llama 2 模型,利用簡單的 if 陳述式來存取使用資訊充足的通用知識庫。

遵循步驟

單元 1:

單元 2:

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

  • 編寫用於整合 Flows 和聊天機器人的 Python 程式碼。
  • 建立用於監聽訊息的 Webhook。
  • 執行應用程式。

單元 1:如何建立 Flows

在本部分中,您將會使用 Flow 建立工具建立一些 Flows。或者,您可以使用 Flows API,但我們不會在這裡介紹。

單元 1 必要條件

若要繼續操作,請確保您具備以下條件:

最後,確認您已完成使用 Flows 的必要步驟。您也可以預覽完整的專案程式碼。

新手指南

首先,前往 WhatsApp Business 帳號中的流程頁面。如果這是您第一次使用 Flows,應該會看到標題為開始建立流程的按鈕。否則,頁面右上角會有一個建立流程按鈕。

點擊顯示的按鈕即可開啟對話方塊,您可以在其中輸入流程相關詳細資訊:

開始使用 WhatsApp Chatbox

服務查詢流程

首先,您將會建立一個「流程」,讓用戶能夠搜尋與飯店公司服務有關的資訊。

名稱欄位中輸入名稱。接著,在類別下拉式功能表下方選擇註冊,並將範本下拉式功能表設定為。點擊提交

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

將編輯器的內容取代為流程的 JSON 標記(您可以在開發人員文件中深入瞭解流程 JSON)。

儲存流程,預覽看起來應該如下圖所示:

Chatbox 流程

該流程包含一個允許用戶輸入自己的詳細資訊、選擇感興趣的服務,以及選用額外訊息的畫面。當用戶點擊提交時,流程便會關閉並將擷取的資料傳送到您的企業商家進行處理。傳輸此資料的一種方法涉及使用端點。然而,這個專案不需要端點。資料將會傳遞到為聊天機器人提供支援的相同 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 動作關閉流程。

指派 payload 金鑰的方式與指派任何變數一樣。相對應的值可代表資料物件或流程元件的名稱(類似於 HTML 表單的名稱屬性)。

現在,您可以使用互動式預覽切換開關來查看正在執行的流程並模擬真實的用戶體驗:

Chatbox 互動式預覽

測試完成後,您可以發佈流程,因為它目前處於草稿狀態。若要執行此操作,請開啟儲存右側的功能表,然後點擊發佈。流程現在已準備就緒且可供使用。

聯絡我們流程

現在,您將建立「聯絡我們」流程。

首先重複相同的初始流程建立程序。針對類別的部分,選擇聯絡我們。將編輯器的內容取代為此 JSON 標記,以顯示下方畫面:

Chatbox 聯絡我們流程

發佈流程並繼續前往下一節以設定聊天機器人。該節主要說明三個函式:send_messageflow_detailsflow_reply_processor,其中包含將流程傳送給用戶的重要邏輯。另外,也包含處理傳入流程裝載的邏輯。因此,即使您已經建立聊天機器人,我們也建議您仔細閱讀該節內容。

單元 2:如何設定聊天機器人

接下來,您將設定聊天機器人並整合到流程中。

單元 2 必要條件

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

  • 基礎知識與最新版本的 Python

  • 已下載 Llama 2 HuggingFace 版本。HuggingFace Llama 2 模型不需要額外的工具或專用硬體。您也可以使用官方版本,但需要額外的設定。

  • 您帳號的存取權杖和電話號碼編號

  • 程式碼編輯器

使用 Python 整合流程與聊天機器人

聊天機器人將透過預先定義的指令碼運作,該指令碼旨在根據用戶輸入的內容為用戶提供協助。在初始互動時,它會根據用戶的訊息提供個人化問候文字和文字功能表。這些選項可滿足特定需求:查詢飯店提供的服務、聯絡飯店客服人員,或與 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 清單包含與用戶輸入選擇相對應的選項,分別是使用下方函式的 12YN。下方函式可協助將用戶的回應對應到相對應的選項。

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" 功能。

接下來,以下函式包含一個 if 敘述,該敘述使用 Python RegEx 套件 re 來搜尋顧客訊息中的特定字詞,以決定要向用戶傳送哪種回應:

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 顯示流程擷取的回應。

其他選項(CONTACT_USTALK_TO_AN_AGENT)會向用戶傳送流程裝載。流程裝載源自於 flow_details 函式,其內文如下所示。裝載包含基本流程細節(包括 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_tokenaction.parameters.flow_action_payload.screen 會作為參數(screen_id)來傳遞。在理想情况下,它應該代表您意圖在執行 action.parameters.flow_cta 時向用戶顯示的初始畫面編號。

最後,新增 webhook 路徑。將 Webhook 新增至 Meta for Developers 上的應用程式時,將會啟動 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 的協助工具函式,來擷取來自流程的回應並傳回給用戶:

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)來區分兩個流程,進而在傳遞正確的第一個畫面編號時適當地擷取回應。

在執行程式碼之前,請先比較函式與完整版本並確認所有內容完全一致。

如何設定 Webhook

在繼續之前,請從終端機執行以下指令:

flask --app main run --port 5000 
        

如果成功,您應該會看到以下訊息:

* Running on http://127.0.0.1:5000
        

接著,執行 http 5000,以取得對應到您的應用程式的網址,並複製該連結。

然後,在 Meta for Developers 的開發人員帳號中,點擊左側導覽面板中 WhatsApp 下方的設定功能表:

Chatbox 設定

Webhook 頁籤卡中,點擊編輯

然後,在顯示對話方塊的回呼網址欄位中,貼上複製的網址並附加 /webhook

將來自 .env 檔案的 TOKEN 變數的權杖新增到驗證權杖欄位中。點擊驗證並儲存以關閉對話方塊。

現在,從同一張圖卡中,點擊管理並檢查訊息欄位。圖卡應該如下所示:

WhatsApp Chatbox webhook

Webhook 現已準備就緒。

執行應用程式

您可以向您的帳號號碼傳送「Hello」等訊息。您應該會收到適當的回應。嘗試回覆功能表中顯示的提示以測試流程:

WhatsApp Chatbox 執行應用程式

下方為顯示聊天機器人回應的另一個螢幕截圖。用戶要求快速翻譯,機器人可以提供這項服務。

WhatsApp Chatbox 執行應用程式

結論

WhatsApp Flows 是企業商家蒐集結構化資訊、加強顧客互動以及簡化企業商家與消費者之間通訊的強大工具。建立流程的其中一種方法是使用 WhatsApp 管理工具 Flow 建立工具,該工具提供對用戶友善的流程設計介面。

這個示範應用程式可讓您瞭解如何利用 WhatsApp Flows 來提高顧客互動率和改善資料導向分析。對於更專業的使用情境,您還可以設定 Llama 2 和 WhatsApp Flows,讓您的聊天機器人連結至自訂模型,或使用專屬的資料來源來訓練模型,讓聊天機器人能夠回答與產品和其他功能有關的自然語言問題。

現在就開始利用 WhatsApp Flows 提升顧客互動率,並簡化應用程式中的資料蒐集方式。立即試用