開発者向けニュースに戻る

チャットボット体験に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の作成方法

ここでは、フロービルダーを使って複数のFlowsを作成します。ここでは扱いませんが、Flows APIを使うこともできます。

パート1の前提条件

以降の手順を実行するには以下のものが必要です。

最後に、Flowsを使用するために必要な手順を完了しておいてください。このプロジェクトのコード全文にあらかじめ目を通しておいてもよいでしょう。

はじめに

まずはじめに、WhatsApp BusinessアカウントのFlowsページに移動します。Flowsを使用するのが初めての場合は、[フローの作成を開始]ボタンが表示されるはずです。表示されない場合は、ページの右上に[Flowを作成]ボタンが表示されます。

表示されたボタンをクリックすると、Flowの情報を入力できるダイアログボックスが開きます。

はじめに WhatsAppチャットボックス

サービス問い合わせのFlow

はじめに、ホテル会社のサービスについての問い合わせを可能にするFlowを作成します。

[名前]フィールドに名前を入力します。続いて、[カテゴリ]ドロップダウンで[登録する]を選択し、[テンプレート]ドロップダウンは[なし]のままにしておきます。[送信]をクリックします。

次のページの左側にエディター、右側にプレビューが表示されます。

エディターの内容を、FlowのJSONマークアップで上書きします。(Flow JSONについて、詳しくは開発者向けドキュメントをご覧ください。)

Flowを保存します。以下の画像のようなプレビューが表示されるはずです。

チャットボックスのFlow

このFlowには、ユーザー自身が情報を入力し、興味のあるサービスを選択し、任意でメッセージを追加できる画面が1つあります。ユーザーが[送信]をクリックすると、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を作成します。カテゴリでは[お問い合わせ]を選択します。エディターの内容をFlowのJSONマークアップで上書きすると以下が表示されます。

チャットボックスの問い合わせのFlow

Flowを公開し、次のチャットボット設定セクションに進みます。次のセクションでは、Flowsをユーザーに送信するのに不可欠なロジックを含む3つの関数(send_messageflow_detailsflow_reply_processor)を取り上げます。受信したFlowペイロードを処理するロジックについても触れます。そのため、すでにチャットボットを構築している場合でも、一通り目を通すことをおすすめします。

パート2: チャットボットの設定方法

次に、チャットボットを設定し、Flowsと連携させます。

パート2の前提条件

以降の手順を実行するには以下のものが必要です。

  • Pythonの基本的な知識と最新バージョン

  • Llama 2のHuggingFaceバージョンのダウンロード。HuggingFaceのLlama 2モデルでは、追加のツールや専用のハードウェアは不要です。公式バージョンを使用することもできますが、追加で設定が必要になります。

  • アカウントのアクセストークンと電話番号ID

  • コードエディター

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"機能がトリガされます。

次に、以下の関数には、Python RegExパッケージ「re」を使用するif文を含めます。これにより、顧客のメッセージから特定の語句を探し、ユーザーへの返信のタイプを決めます。

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"などのメッセージに対して、2番目の引数がSEND_GREETINGS_AND_PROMPTsend_messageメソッドが発火するようになります。以下が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_ID(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_tokenを作成します。action.parameters.flow_action_payload.screenは、パラメーター(screen_id)として渡されます。できれば、action.parameters.flow_ctaの実行時にユーザーに見せる初期画面のIDにしてください。

最後に、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というヘルパー関数で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)
        

この関数は、キー(flow_key)で2つのFlowsを区別することで、適切に返信を抽出して正しい初期画面IDを渡します。

コードを実行する前に、コードの全文と見比べてすべて一致していることを確認してください。

Webhookの設定方法

設定に進む前に、以下のコマンドをターミナルで実行します。

flask --app main run --port 5000 
        

成功した場合は、以下のメッセージが表示されるはずです。

* Running on http://127.0.0.1:5000
        

次に、「ngrok http 5000」を実行してアプリケーションのURLを取得します。そのリンクをコピーします。

その後、Meta for Developersの開発者アカウントに移動し、左側のナビゲーションパネルで[WhatsApp][設定]メニューをクリックします。

チャットボックスの設定

[Webhook]カードで[編集]をクリックします。

その後、表示されたダイアログボックスの[コールバックURL]フィールドに、先ほどコピーしたURLをペーストして末尾に/webhookを付けます。

.envファイルのTOKEN変数のトークンを[トークンを認証]フィールドに追加します。[確認して保存]をクリックしてダイアログボックスを閉じます。

次に、同じカードで[管理]をクリックし、messagesフィールドを確認します。以下のようなカードが表示されるはずです。

WhatsAppチャットボックスのWebhook

これでWebhookの準備は完了です。

アプリケーションを実行する

「Hello」などのメッセージを自分のアカウント番号に送信できます。適切な返信が届くはずです。メニューにあるプロンプトで返信して、Flowsをテストしてみてください。

WhatsAppチャットボックス、アプリを実行する

以下は、チャットボットの返信を示した別のスクリーンショットです。簡単な翻訳を尋ねた例です。こうしたこともボットで対応できます。

WhatsAppチャットボックス、アプリを実行する

まとめ

WhatsApp Flowsは、構造化された情報を取得できる強力なツールです。これにより、顧客とのやり取りの質を高め、ビジネスと消費者とのコミュニケーションを合理化することができます。そのFlowsを構築する1つの方法が、Flowsを簡単にデザインできるインターフェイスが用意されたWhatsAppマネージャのフロービルダーです。

このデモアプリケーションでは、WhatsApp Flowsを用いて顧客エンゲージメントを改善し、データ主導の分析を行う方法の一端を紹介しました。個別の用途に特化させる場合は、チャットボットとカスタムモデルを接続したり、独自のデータソースでトレーニングしたりするようにLlama 2とWhatsApp Flowsを設定して、商品やその他の機能に関する自然言語の質問に答えられるようにすることも可能です。

WhatsApp Flowsを活用して、アプリケーションでの顧客とのやり取りの質を高め、データの取得を合理化しましょう。今すぐお試しください