Назад к новостям для разработчиков

Добавление WhatsApp Flows в чат-бот

20 марта 2024 г.Автор:Гафи Дж (Gafi G) и Ирина Вагнер (Iryna Wagner)

Всё больше компаний используют чат-боты в WhatsApp, чтобы взаимодействовать с клиентами, выявлять их потребности и получать важные данные. Однако эффективно собирать и упорядочивать информацию непросто. В этом поможет WhatsApp Flows.

Интеграция WhatsApp Flows в чат-боты помогает компаниям эффективнее анализировать входящие данные о клиентах. В зависимости от содержания переписки чат-бот может отправить клиенту сценарий, адаптированный для сбора данных.

Из этой статьи вы узнаете, как создать чат-бот с помощью Llama 2 и Python, а также подключить его к WhatsApp Flows, чтобы улучшить сбор данных. Вы узнаете, как WhatsApp Flows делает чат-бот более понятным для пользователей и помогает улучшить точность и эффективность сбора данных о клиентах.

Улучшение функционала чат-бота с помощью WhatsApp Flows

Ваш чат-бот будет отвечать на запросы пользователей с помощью WhatsApp Flows и таким образом собирать данные. Другими словами, пользователи смогут общаться с чат-ботом, задавать вопросы об услугах вымышленного отеля в Испании и обращаться в компанию за поддержкой.

Чат-бот будет использовать простые утверждения if в сочетании со стандартной моделью Llama 2, чтобы предоставлять доступ к достаточной общей базе знаний.

Инструкция

Часть 1:

Часть 2:

  • Преобразуйте модель Llama 2 из формата GGML в GGUF.

  • Напишите код Python для интеграции сценария с чат-ботом.
  • Создайте Webhook для прослушивания сообщений.
  • Запустите приложение.

Часть 1: создание сценария

На этом этапе для создания сценария вы будете использовать Flow Builder. Вы также можете использовать Flows API (этот способ не описывается в этой статье).

Часть 1: предварительные требования

Чтобы приступить к работе, убедитесь, что вы:

Кроме того, выполните обязательные условия для использования Flows. Вы также можете открыть предварительный просмотр полного кода проекта.

Начало работы

Откройте страницу Сценарии в своем аккаунте WhatsApp Business. Если вы впервые работаете с Flows, то увидите кнопку Начать создание сценариев. В ином случае вы увидите кнопку Создать сценарий в правом верхнем углу страницы.

Нажмите кнопку, чтобы открыть диалоговое окно, где вы сможете указать данные о сценарии.

Диалоговое окно в чате WhatsApp: начало работы

Сценарий "Запрос информации об услугах"

Сначала вы создадите сценарий, который позволит клиенту задать вопрос об услугах туристической компании.

Введите название в поле Название. Далее в раскрывающемся меню Категории нажмите Зарегистрироваться, а в раскрывающемся меню Шаблон выберите Нет. Нажмите Отправить.

В левой части следующей страницы находится редактор, а в правой — предпросмотр.

Замените содержимое редактора разметкой JSON для нужного сценария. Узнать больше о JSON сценария можно в документации для разработчиков.

Сохраните сценарий. Предварительный предпросмотр должен выглядеть примерно так, как на скриншоте ниже.

Диалоговое окно в чате с 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.

Вы можете присвоить ключи payload любой переменной. Соответствующие значения могут отражать объекты данных или названия компонентов сценария (примерно как со свойством названия HTML-формы).

Теперь вы можете увидеть сценарий в работе и симулировать реальное взаимодействие с пользователем с помощью переключателя Интерактивный предпросмотр.

Диалоговое окно в чате: интерактивный предпросмотр

После тестирования вы можете опубликовать сценарий (до этого он будет находиться в черновиках). Для этого откройте меню справа от кнопки Сохранить и нажмите Опубликовать. Сценарий готов к использованию.

Сценарий "Связаться с нами"

Теперь давайте создадим сценарий "Связаться с нами".

Повторите шаги по созданию сценария. В качестве категории выберите Связаться с нами. Замените содержимое редактора разметкой JSON для отображения данных, как на скриншоте ниже.

Диалоговое окно в чате: сценарий "Связаться с нами"

Опубликуйте сценарий и приступайте к инструкциям в следующем разделе, чтобы настроить чат-бот. В этом разделе используются три функции: send_message, flow_details и flow_reply_processor. Они содержат логику, необходимую для отправки сценария пользователям. В этом разделе также содержится логика для обработки входящих полезных данных сценария. Поэтому рекомендуем просмотреть этот раздел, даже если у вас уже есть чат-бот.

Часть 2: настройка чат-бота

Теперь давайте настроим чат-бот и интегрируем его со сценарием.

Часть 2: предварительные требования

Прежде чем продолжить, убедитесь, что:

  • У вас есть базовые знания и последняя версия Python.

  • Вы скачали версию HuggingFace модели Llama 2. Модель HuggingFace Llama не требует дополнительных инструментов или специального оборудования. Вы также можете использовать официальную версию, однако тогда потребуется дополнительная настройка.

  • У вас есть маркер доступа к вашему аккаунту и ID номера телефона.

  • У вас есть редактор кода.

Интеграция сценариев и чат-бота с помощью 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 и указанный ниже контент, соответствующе присвоив значения. Для 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, Y и N с использованием функции ниже. Функция ниже помогает сопоставить ответы пользователя с нужным вариантом.

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 отображает ответ сценария.

Другие варианты (CONTACT_US и TALK_TO_AN_AGENT) отправят пользователю полезные данные сценария. Такие данные извлекаются из функции flow_details, основной текст которой показан ниже. Полезные данные содержат важные сведения о сценарии, в том числе FLOW_ID, который можно извлечь со страницы Сценарии в вашем аккаунте WhatsApp Business. Вы также можете определить эти 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
        

Этот метод создает action.parameters.flow_token, генерируя случайный UUID. action.parameters.flow_action_payload.screen передается в качестве параметра (screen_id). В идеале этот параметр должен отражать ID первого экрана, который вы планируете показывать пользователю при выполнении функции action.parameters.flow_cta.

Наконец, добавьте маршруты Webhook. Webhook GET запрашивает инициализацию, когда Webhook добавляется в ваше приложение на сайте Meta for Developers. Если запрос успешный, он возвращает 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) для различия двух сценариев и правильного извлечения ответов при передаче 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.

Добавьте маркер из переменной TOKEN файла .env в поле Маркер подтверждения. Нажмите Подтвердить и сохранить, чтобы закрыть диалоговое окно.

На этой же карточке нажмите Управлять и проверьте поле messages. Карточка должна выглядеть примерно так, как на скриншоте ниже.

Webhook диалогового окна чата в WhatsApp

Настройка Webhook завершена.

Запуск приложения

Вы можете отправить сообщение, например "Привет", на номер своего аккаунта. Вы должны получить соответствующее сообщение. Попробуйте ответить с помощью предложенного варианта меню, чтобы протестировать сценарий.

Диалоговое окно чата WhatsApp: запуск приложения

Ниже показан другой скриншот с ответом чат-бота. Пользователь просит быстро перевести сообщение, что бот и делает.

Диалоговое окно чата WhatsApp: запуск приложения

Заключение

WhatsApp Flows — это эффективный инструмент, который помогает компаниям собирать структурированную информацию, улучшать взаимодействие с клиентами и упрощать коммуникацию между бизнесом и клиентами. Один из способов создать сценарий — использовать Flow Builder в WhatsApp Manager с простым интерфейсом.

Это демо-приложение помогает посмотреть, как использовать WhatsApp Flows для улучшения взаимодействия с клиентами и выполнения анализа на основе данных. Для большей персонализации вы также можете настроить Llama 2 и WhatsApp Flows, чтобы подключить чат-бот к собственной модели или натренировать ее на своем источнике данных. В таком случае чат-бот сможет отвечать на вопросы на естественном языке о вашем продукте и его функциях.

Используйте WhatsApp Flows, чтобы улучшить взаимодействие с клиентами и упростить сбор данных в ваших приложениях. Скорее попробуйте этот инструмент.