返回开发者新闻

在智能聊天助手体验中添加 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

  • 编写用于集成 Flows 与智能聊天助手的 Python 代码。
  • 创建用于监听消息的 Webhook。
  • 运行应用程序。

第 1 部分:如何创建 Flows

在本部分,您将使用 Flow 创建工具创建一些 Flows。或者,您也可以使用 Flows API,本文不会对此进行介绍。

第 1 部分前提条件

要继续操作,请确保您:

最后,确保您完成使用 Flows 所需的步骤。您还可以预览完整的项目代码。

入门指南

首先,在您的 WhatsApp Business 商业账号中前往 Flows 页面。如果这是您首次与 Flows 互动,应该会看到一个名为开始创建 Flow 的按钮。否则,页面右上方会显示创建 Flow 按钮。

点击显示的按钮打开一个对话框,您可以在其中输入有关 Flow 的一些详细信息:

开始使用 WhatsApp 聊天框

服务查询 Flow

首先,您将创建一个 Flow,允许用户查询酒店公司提供的服务信息。

名称栏输入名称。然后,在类别下拉菜单中选择注册,并将模板下拉菜单设为。点击提交

下一页面的左侧将显示一个编辑工具,右侧将显示预览。

将编辑工具的内容替换为 Flow 的 JSON 标记。(您可以阅读开发者文档,详细了解 Flow JSON。)

保存 Flow,您的预览应该如下图所示:

Flow 聊天框

此 Flow 包含一个屏幕,用户可以在此屏幕中输入个人详情、选择感兴趣的服务,还可以选择性地提供其他消息。用户点击提交后,Flow 将关闭并将收集的数据发送给您的企业进行处理。传输此数据的一种方法涉及到端点的使用。然而,此项目不需要使用端点。数据将传递至为智能聊天助手提供支持的同一个 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 操作关闭 Flow。

您可以像分配任何变量一样分配 payload 密钥。相应的值可以表示数据对象或 Flow 组件的名称(类似于 HTML 表单的 name 属性)。

现在,您可以使用互动预览开关查看 Flow 的运行情况并模拟真实的用户体验:

互动预览聊天框

测试完成后,即可发布 Flow,因为它当前处于草稿状态。为此,请打开保存右侧的菜单,然后点击发布。现在,Flow 已准备就绪,可以开始使用。

“联系我们”Flow

现在,您将创建“联系我们”Flow。

首先,重复相同的初始 Flow 创建过程。对于类别,选择联系我们。将编辑工具的内容替换为此 JSON 标记以显示如下内容:

“联系我们”Flow 聊天框

发布 Flow,并继续前往下一版块设置智能聊天助手。该版块包含三个函数:send_messageflow_detailsflow_reply_processor,其中包含将 Flows 发送给用户的重要逻辑。该版块还包含处理传入 Flow 有效负载的逻辑。因此,即使您已经创建智能聊天助手,我们也建议您仔细阅读该部分的内容。

第 2 部分:如何设置智能聊天助手

接下来,您将配置智能聊天助手并将它集成到您的 Flows 中。

第 2 部分前提条件

在继续操作之前,请确保您拥有以下内容:

  • 基础知识和最新版本的 Python

  • 已下载 Llama 2 的 HuggingFace 版本。HuggingFace 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 的环境文件和以下内容,并适当地赋值。(您可以针对 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)。

问候聊天框示例

同样,当用户询问有关所提供服务的问题时,就会向用户发送一条包含服务内容的短信 (service_intro_text)。除了服务内容之外,短信中还会提示用户选择是否要与客服交谈。

提供的服务聊天框示例

如果输入的内容需要智能聊天助手回复 (CHATBOT),则初始化模型,为其输入消息内容,然后处理回发给用户的回复。FLOW_RESPONSE 将显示 Flow 收集的回复。

其他选项 CONTACT_USTALK_TO_AN_AGENT 会将 Flow 有效负载发送给用户。Flow 有效负载源自 flow_details 函数,其正文如下所示。有效负载包含基本的 Flow 详细信息,其中包括可从 WhatsApp Business 商业账号的 Flows 页面获取的 FLOW_ID。也可以在环境变量中定义这些编号。

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 路由。在 Meta 开发者上向应用添加 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 的辅助函数从 Flow 中提取回复并将其回发给用户:

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)
        

该函数使用 key(flow_key) 来区分两个 Flows,从而在传递正确的首屏编号时适当地提取回复。

在运行代码之前,请将其与完整版本进行比较,并确认所有内容均匹配。

如何设置 Webhook

在继续操作之前,请在终端运行此命令:

flask --app main run --port 5000 
        

如果运行成功,您会看到如下消息:

* Running on http://127.0.0.1:5000
        

接下来,运行 ngrok http 5000 以获取映射到您应用程序的网址。复制该链接。

然后,在 Meta 开发者上的开发者账户中,点击左侧导航窗口中 WhatsApp 下的配置菜单:

配置聊天框

Webhook 图卡中,点击编辑

然后,在打开的对话框的回调网址栏中,粘贴复制的网址并附加 /webhook

.env 文件的 TOKEN 变量中的口令添加到验证口令栏中。点击验证并保存关闭对话框。

现在,在同一张图卡上,点击管理并勾选消息栏。图卡应如下所示:

WhatsApp 聊天框 Webhook

Webhook 现已准备就绪。

运行应用程序

您可以向自己的账号发送“您好”等消息。您应该会收到相应的回复。尝试使用菜单中显示的提示进行回复,以便测试 Flows:

WhatsApp 运行应用聊天框

下面是另一张显示智能聊天助手回复的截图。用户要求快速翻译,智能助手可以提供此服务。

WhatsApp 运行应用聊天框

总结

WhatsApp Flows 是企业用来收集结构化信息的强大工具,可以增强客户互动,方便企业与消费者进行沟通。创建 Flows 的方法之一是使用 WhatsApp 管理工具中的 Flow 创建工具,此工具提供了用户友好型 Flows 设计界面。

通过此试用版应用程序,您可以了解如何利用 WhatsApp Flows 提高客户互动量并改善数据驱动式分析。针对更专业的用途,您还可以配置 Llama 2 和 WhatsApp Flows,将智能聊天助手与自定义模型连接,或者使用专有数据源对其进行训练,使其能够回答有关产品及其他功能的自然语言问题。

现在就利用 WhatsApp Flows 提升客户互动量,简化应用程序的数据收集操作吧。马上试试