На сегодняшний день существует много интегрированных информационных систем и клиентских приложений, и при работе с ними у пользователей возникают проблемы различной степени сложности, и чтобы разгрузить и улучшить качество взаимодействия с ними, в разрабатываются диалоговые помощники и виртуальные консультанты с использованием искусственного интеллекта и технологиями NLP.
Одним из инструментов создания диалоговых помощников является Rasa — сценарная платформа машинного обучения с открытым исходным кодом.
Для более удобного взаимодействия с виртуальным консультантом встает вопрос об интеграции его в социальные сети и мессенджеры, что позволит работать с чат‑ботом при помощи смартфона.
Вводная
В рамках задачи нужно было интегрировать помощника Rasa в социальную сеть VK. В этой статье и поговорим о том как это сделать.
Руководство предполагает, что вы уже создали проект Rasa и обучили модель.
Rasa Connector
В Rasa уже имеется модуль для вывода взаимодействия с моделью за пределами командной строки и он называется Rasa Connector.
Коннектор — это модуль, который предоставляет внешним ресурсам делать запросы к Rasa. Он принимает внешний запрос обрабатывает его и приводит к нужному формату, затем отправляя запрос на сервер Rasa. Далее он обрабатывает ответ и отправляет обратно во внешний ресурс.
Архитектура
Здесь представлен возможный вариант архитектуры, который предполагает создание коннектора под каждый канал обращения, таким образом пользователи могут вести диалог в нескольких каналах одновременно.
Создание коннектора
Перейдем к написанию кода. Мы рассмотрим создание коннектора на примере VK с использованием Callback API. Но вы также можете использовать это руководство при создании коннекторов для других сервисов.
Шаг 1. Создание структуры файлов
Создаем директорию, например connectors, в корне проекта Rasa и далее в ней создаем файл vk.py в нём мы и будем создавать наш коннектор
Шаг 2. Добавление учётных данных
В нашем случае с VK понадобятся ключ доступа и строка-подтверждение. Чтобы узнать как их получить обратитесь к документации CallbackAPI.
Далее добавляем их в credentials.yml, но также нужно написать путь до InputChnnel коннектора: <директория>.<файл>.<название_класса>, так Rasa будет знать к чему относить эти данные.
connectors.vk.VkInputChannel:
access_token: '<YOUR_ACCESS_TOKEN>' # ключ доступа
confirmation_token: '<YOUR_CONF_TOKEN>' # строка подтверждение
Шаг 3. Создаем InputChannel
Первое что нам необходимо сделать, это создать свой класс входного канала VkInputChannel, он будет наследоваться от базового класса InputChannel, который предлагает нам Rasa, в нем объявлены методы для определения префикса URL-адреса веб-хука, создания схемы Sanic, а также получения токенов из credentials.yml.
Есть и другие методы, которые мы не будем использовать в данном коннекторе, их можно посмотреть в документации.
Файл vk.py
Шаг 4. Определяем создание обьекта
Объект класса VkInputChannel будет содержать всю информацию которую мы указали для этого канала в credentials.yml. Это необходимо чтобы мы могли отправлять обратные ответы с помощью API.
def __init__(
self,
access_token: Optional[Text],
confirmation_token: Optional[Text],
) -> None:
self.access_token = access_token # ключ доступа
self.confirmation_token = confirmation_token # строка подтверждение
Шаг 5. Переопределение методов
Так как InputChannel предоставляет нам только описание методов, необходимо переопределить их для конкретного канала.
Метод name определяет префикс URL-адреса веб-хука коннектора, итоговый адрес будет выглядеть http://<host>:<port>/webhooks/vk/webhook
, где хост и порт это соответствующие значения работающего сервера Rasa.
@classmethod
def name(cls) -> Text:
return 'vk'
Переопределим еще и метод получения данных из credentials.yml, Rasa сама загружает нужные данные и преобразует их в Dict.
@classmethod
def from_credentials(
cls,
credentials: Optional[Dict[Text, Any]],
) -> InputChannel:
# Базовый метод вызова ошибки, при отсутствии credentials.
if not credentials:
cls.raise_missing_credentials_exception()
# Возвращаем __init__ с полученными токенами.
return cls(
credentials.get('access_token'),
credentials.get('confirmation_token'),
)
Шаг 6. Создание OutputChannel
Второй класс который мы создадим это VkOutputChannel, он наследуется от базового класса OutputChannel. Последний реализует методы отправки сообщений разного формата(текст, картинка и тп.) обратно в канал обращения.
class VKOutputChannel(OutputChannel):
Здесь мы тоже определим метод name.
@classmethod
def name(cls) -> Text:
return 'vk'
В объекте VkOutputChannel, нам потребуется использовать VkAPI для отправки ответного сообщения.
def __init__(
self,
access_token: Optional[Text],
) -> None:
self.vk = VkApi(token=access_token)
Переопределяем метод отправки ответа, здесь мы вызываем у обьекта класса VkApi метод отправки текстового сообщения, и задаём user_id и text , а также random_id необходимый для того, чтобы ваш бот не отравлял одно и то же сообщение несколько раз.
async def send_text_message(
self,
recipient_id: Text,
text: Text,
**kwargs: Any,
) -> None:
for message_part in text.strip().split('\n\n'):
self.vk.method(
'messages.send',
{
'user_id': recipient_id,
'message': message_part,
'random_id': get_random_id(),
}
)
Шаг 7. Добавление метода blueprint в InputChannel
Этот метод будет создает схему Sanic, и закрепляет ее за сервером Sanic, который будет обрабатывать входящие маршруты. В методе мы также создадим объект класса VKOutputChannel .
Sanic — быстрый асинхронный веб‑сервер и веб‑фреймворк, использующий синтаксис async/await.
def blueprint(
self,
on_new_message: Callable[[UserMessage], Awaitable[Any]]
) -> Blueprint:
# webhooks/vk
webhook = Blueprint('webhook', __name__)
output_channel = VKOutputChannel(self.access_token)
Нам необходимо создать минимум два маршрута / и /webhook (подробнее в документации).
# webhooks/vk/
@webhook.route('/', methods=['GET'])
async def health(request: Request) -> HTTPResponse:
return response.json({'status': 'ok'})
На этот маршрут будут прилетать запросы от VK.
# webhooks/vk/webhook
@webhook.route('/webhook', methods=['POST'])
async def recieve(request: Request) -> HTTPResponse:
# Берем из запроса json
request = request.json
# Если запрос в виде строки, то преобразуем
if isinstance(request, Text):
request = json.loads(request)
Но, если запрос прилетает некорректный или вообще не от VK, мы тоже это должны обработать, для этого обратимся к структуре входящего JSON.
# Если пришел некорректный запрос
if 'type' not in request.keys():
return response.text('not vk')
Далее мы будем определять тип события, и в случае нового сообщения будем брать id пользователя и текст сообщения.
# Обработка запроса строки-подтверждения
if request['type'] == 'confirmation':
return response.text(self.confirmation_token)
# Обработка нового сообщения от пользователя
if request['type'] == 'message_new':
sender_id = request['object']['message']['from_id']
text = request['object']['message']['text']
И наконец мы говорим Rasa обработать запрос, и формируем UserMessage. Важно отметить, в случае с VK, для каждого события сервер должен отвечать 'ok' в случае успешного запроса, подробнее тут.
metadata = self.get_metadata(request)
# Говорим Rasa обработать сообщение пользователя
await on_new_message(
UserMessage(
text,
output_channel,
sender_id,
input_channel=self.name(),
metadata=metadata,
)
)
return response.text('ok')
return webhook
Шаг 8. Запуск сервера Rasa
Далее запустите сервер раса c вашей обученой моделью командой:
rasa run
Шаг 9. Настройка Ngrok
Так как мы разворачиваем сервер Rasa на локально, то нужно создать публичный адрес на который будут приходить запросы и перебрасывать их на наш локальный адрес и заданный порт. Для этого будем использовать ngrok, но подойдет и любая другая альтернатива, к примеру localtunnel или Pagekite.
Установка ngrok
1. Установим и добавим токен ngrok по инструкции.
2. Создаем статический домен ngrok здесь.
3. Далее идём в терминал и открываем туннель с портом 5005
ngrok http --domain=master-positively-flounder.ngrok-free.app 5005
Шаг 10. Настройка сервиса CallbackAPI
1. Для начала создадим сообщество в VK, и в его настройках включим "Сообщения сообщества".
2. Далее переходим в раздел "Работа с API", создаем ключ доступа, его и записываем в credentials.yml.
3. Идём в "CallbackAPI" в разделе "Типы событий" выберем "Входящие сообщения", так бот не будет реагировать на другие типы событий.
4. В "Настройках сервера" нужно скопировать строку которую будет возвращать сервер и записать в credentials.
5. В адрес указываем созданный ранее публичный адрес и подтверждаем.
https://master-positively-flounder.ngrok-free.app/webhooks/vk/webhook
Результат работы
Переходим в диалог с сообществом и теперь мы можем вести общение с нашим помощником.
Как происходит взаимодействие?
На диаграмме ниже я представил взаимодействие всех компонентов.Пользователь обращается к помощнику с приветствием, далее запрос отправляется на публичный адрес ngrok, который перенаправляет его на наш сервер. Запрос обрабатывается классом InputChannel и потом отправляет запрос в Rasa для получения ответа от модели. Rasa возвращает ответ и OutputChannel отправляет его в качестве ответа помощнику, а тот пользователю.
Итог
Надеюсь это руководство было вам полезно! Теперь вы сможете самостоятельно интегрировать чат-бота Rasa, и улучшить user experience во взаимодействии в с диалоговым искусственным интеллектом. Если возникнут проблемы, приглашаю обсудить их в комментариях.
Полезные ссылки