Автор оригинала: Daoud Clarke.
Там много много инструментов для создания чата. Перефразировать доктор Сьюю, некоторые хорошие, а некоторые грустно, а некоторые очень, очень плохо. Я знаю, я Рассмотрено куча их.
Но что, если вы хотите написать один сами, с нуля, не используя никаких модных инструментов? Это даже возможно? И вы можете сделать что-то полезное? Ответ да, потому что я сделал это, и я собираюсь показать вам, как.
Весь код доступен здесь на github. Мы будем создавать бот для Facebook Messenger, и мы будем использовать двигатель Google App для размещения нашего бота, который будет написан в Python.
Но подождите, зачем ли вы этого сделать? Использование графического пользовательского интерфейса, чтобы ваш бот намного проще. Ну, вот несколько причин:
- Это бесплатно. App Engine’s свободный уровень Настолько щедро, вряд ли вы превысим его, если у вас нет много тысяч пользователей бота – в этом случае вы будете смеяться.
- Учить. Посмотрите, что на самом деле нужно, чтобы построить чатбот.
- Выйти за рамки того, что может сделать инструменты создания Chatbot. Чувствую себя амбициозным? Сделать что-то совершенно оригинальное или сделать свой Собственный Платформа Chatbot.
Выбор канала Chatbot
Вы можете построить бот для многих разных каналов. Некоторые из самых популярных являются Messenger Facebook, Kik, Slack, Twitter и Telegram. Если вам нужно поддержать несколько платформ, вы будете лучше использовать бот. Таким образом, вам не придется писать код для интеграции со всеми платформами, которые вы хотите поддерживать.
В этой статье мы собираемся построить Chatbot для Messenger Facebook. Почему? Ну, во-первых, это самая популярная платформа для чата. Почти все инструменты для создания целевого мессенджера Chatbots, и довольно много из них Только Поддержка мессенджера. И с хорошей причиной: в 2017 году у него было 1,2 миллиарда ежемесячных активных пользователей. Это много потенциальных пользователей Chambot.
Есть еще одна причина, по которой мы хотим нацелить Messenger: быстрые ответы. Это кнопки, которые ваш чатбен может предлагать пользователям ярлык, чтобы они не должны набрать. Не только они делают ваш бот гораздо более привлекательными
Если вы предлагаете кнопки пользователей, они нажимают на эти кнопки. Это означает, что вам не нужно беспокоиться о разборке произвольных запросов от пользователей, которые хотят знать, собирается ли завтра дождь или где они могут получить пиццу. Руководящие пользователи полезны для них и нас.
Что такое бот?
Ваш бот нужен целью. Это не может сделать все. Мой друг Наре Варданян И я разработал бот, чтобы помочь людям ориентироваться в мутные воды визовых приложений в Великобританию. Мы будем использовать упрощенную версию этого бота в качестве примера в этой статье.
Treat Traversal Magic
Мы будем использовать метод дизайна бота, основанный на деревья Отказ Каждый узел дерева представляет собой возможное состояние разговора. Каждый ребенок узла соответствует возможным пользователю, которое мы понимаем как актуальность с этого конкретного состояния.
say: "What is the purpose of your visit? (options: travel, study, business/work, medical treatment, join family/get married, visit child at school, diplomatic/government visit)" answers: travel: say: You need a Standard Visitor Visa study: say: How long are you going to stay in the UK? up to 6 months; more than 6 months answers: up to 6 months: say: You can apply for a Short-term Study Visa more than 6 months: say: You need a Study Visa (Tier 4) business/work: say: How long are you going to stay in the UK? up to 6 months; more than 6 months answers: up to 6 months: say: You need a Standard Visitor Visa more than 6 months: say: Are you an 1. entrepreneur 2.investor 3. leader in arts or sciences 4. none of the above answers: '1': say: You can apply for a Tier 1 Entrepreneur '2': say: You can apply for Tier 1 Investor '3': say: You can apply for Tier 1 (Exceptional Talent) '4': say: Are you offered 1. a skilled job 2. role in the UK branch of your employer 3. job in a religious community 4. job as an elite sportsman or coach answers: '1': say: You can apply for a Tier 2 (General) visa '2': say: You can apply for a Tier 2 (Intra-company transfer) '3': say: Tier 2 (Minister of Religion) '4': say: Tier 2 (Sportsperson) medical treatment: say: You need a Standard Visitor Visa join family/get married: say: You need a Family of a settled person visa if your family/partner are settled in the UK or a 'dependant' visa of their visa category if they are working or studying visiting a child: say: You need a Parent visa if you're visiting for over 6 months and a Standard Visitor visa if your visit is for less than 6 months diplomatic or government visit: say: You can apply for exempt vignette (exempt from immigration control)
Это упрощенная версия нашего совета визы бота, в форме дерева. Это в YAML (еще один язык разметки), который позволяет легко прочитать. Корневой узел определяет первое сообщение, которое BOT отправляет пользователю, в данном случае, спрашивая пользователя «Какова цель вашего визита?» Детские узлы (указанные под «ответами») содержат возможные ответы, которые мы будем принимать, а именно «путешествовать», «изучать», «Бизнес/работа», и так далее.
Начиная
Чтобы создать наш бот, нам нужно создать кучу вещей в Facebook. Официальный Инструкции здесь , а в резюме вам понадобится:
- Страница в Facebook – каждый бот нуждается в другой странице Facebook.
- Учетная запись разработчика позволяет вам создавать приложения.
- Приложение Facebook, чтобы получить секретный токен доступа, который потребуется позже.
Боты Facebook работают с WebHooks , которые просто URL-адреса, которые Facebook Messenger использует для взаимодействия с вашим ботом.
Чтобы создать наш веб -ook, мы будем использовать Google App Engine Отказ Преимущество этого заключается в том, что это бесплатно для небольших объемов и автоматически масштабируется, поскольку вы получаете больше трафика. Для этой статьи я использовал Python, но есть много других языков, которые вы можете использовать. Вам нужно будет Скачать Python SDK и Создать проект Google Cloud Если у вас еще нет одного.
Создание нашего веб -ook.
Первое, что нужно сделать наш веб -ookook, это позволить Facebook проверить, что мы действительно правильный веб -ook. Мы делаем это, обработав запрос Get, который содержит «проверить токен». Это секретная случайная строка, которую мы поделились с Facebook. Эта часть нашего кода основана на отличном боте Messenger Facebook Репозиторий Отказ
class MainPage(webapp2.RequestHandler): def __init__(self, request=None, response=None): super(MainPage, self).__init__(request, response) logging.info("Initialising with new bot.") self.bot = TreeBot(send_message, UserEventsDao(), TREE) def get(self): self.response.headers['Content-Type'] = 'text/plain' mode = self.request.get("hub.mode") if mode == "subscribe": challenge = self.request.get("hub.challenge") verify_token = self.request.get("hub.verify_token") if verify_token == VERIFY_TOKEN: self.response.write(challenge) else: self.response.write("Ok")
Здесь мы впервые инициализируем класс для обработки запросов в WebApp2 Framework Отказ Сначала мы регистрируемся, чтобы сказать, что бот инициализируется, затем построить класс Treebot
Это будет обрабатывать всю логику бота, обсуждаемую ниже.
Далее мы проверяем запросы «подписаться» из Facebook и убедитесь, что The Verify Token, отправленный в запросе, совпадает с секретом, который мы поделились с Facebook.
Обработка сообщений от пользователей
Далее нам нужно интерпретировать сообщения от пользователей, которые отправляются Facebook в нашем веб -ookook с использованием почтовых запросов.
def post(self): data = json.loads(self.request.body) logging.info("Got data: %r", data) if data["object"] == "page": for entry in data["entry"]: for messaging_event in entry["messaging"]: sender_id = messaging_event["sender"]["id"] if messaging_event.get("message"): message = messaging_event['message'] if message.get('is_echo'): logging.info("Ignoring echo event: " + message.get('text', '')) continue message_text = messaging_event['message'].get('text', '') logging.info("Got a message: %s", message_text) self.bot.handle(sender_id, message_text) if messaging_event.get("postback"): payload = messaging_event['postback']['payload'] self.bot.handle(sender_id, payload) logging.info("Post-back")
Здесь мы сначала разбираем данные JSON из Facebook и регистрируйте его, чтобы помочь с отладкой. Затем мы повторяем события обмена сообщениями в данных. Сначала мы извлекаем идентификатор отправителя, который нам нужно будет отправить ответы обратно пользователю. Существует два типа событий, сообщений (которые набрали пользователь) и события «Отказа», которые отправляются, когда пользователь нажимает кнопку «Быстрый ответ».
Для первого из них нам нужно игнорировать «эхо» события. Затем мы извлеките текст сообщения и отправляем его на нашу логику бота для обработки. Мы делаем то же самое с событиями обратной связи, извлекая полезную нагрузку, которая в нашем случае является просто текстом кнопки.
Отправка сообщений пользователям
Когда мы построили наши Treebot
Класс, мы прошли функцию send_message
Это позволяет логику бота отправлять возвратные сообщения пользователю. Вот:
def send_message(recipient_id, message_text, possible_answers): logging.info("Sending message to %r: %s", recipient_id, message_text) headers = { "Content-Type": "application/json" } message = get_postback_buttons_message(message_text, possible_answers) if message is None: message = {"text": message_text} raw_data = { "recipient": { "id": recipient_id }, "message": message } data = json.dumps(raw_data) r = urlfetch.fetch("https://graph.facebook.com/v2.6/me/messages?access_token=%s" % ACCESS_TOKEN, method=urlfetch.POST, headers=headers, payload=data) if r.status_code != 200: logging.error("Error sending message: %r", r.status_code) def get_postback_buttons_message(message_text, possible_answers): if possible_answers is not None and len(possible_answers) <= 3: buttons = [] for answer in possible_answers: if len(answer) > 20: return None buttons.append({ "type": "postback", "title": answer, "payload": answer, }) return { "attachment": { "type":"template", "payload": { "template_type": "button", "text": message_text, "buttons": buttons, } } } return None
ID получателя будет идентификатор отправителя, который мы извлекли ранее. Наряду с тем, что у нас есть текст сообщения, а некоторые краткие кнопки ответа для пользователя нажать. Сначала мы убедимся, что заголовки запросов указывают наш контент как JSON, а затем построить наши кнопки от записи. Часть сообщения. Мы указываем идентификатор получателя и текст сообщения и преобразовать в JSON. Мы производим нашу заявку на API Facebook Graph API, проходя в секретном токене доступа, что Facebook дал нам, когда мы создали наше приложение.
Запуск сервера бота
Последний кусок кода в этом файле просто строит основной класс и работает:
app = webapp2.WSGIApplication([ ('/', MainPage), ], debug=True)
Бот мозги
Теперь мы приходим на интересную бит: как бот знает, что сказать? Мозги бота в файле Bot.py
Отказ
class TreeBot(object): def __init__(self, send_callback, users_dao, tree): self.send_callback = send_callback self.users_dao = users_dao self.tree = tree def handle(self, user, message): self.users_dao.add_user_event(user, 'received', message) history = self.users_dao.get_user_events(user) tree = self.tree logging.debug("History items: %d", len(history)) restarting = False nothing_sent = True response = DEFAULT possible_answers = DEFAULT_POSSIBLE_ANSWERS for direction, content in history: response = DEFAULT possible_answers = DEFAULT_POSSIBLE_ANSWERS if direction == 'received': key = get_content_match(content, tree) if nothing_sent: response = tree['say'] possible_answers = tree['answers'].keys() elif key is not None: tree = tree[key] if 'say' in tree: response = tree['say'] possible_answers = None if 'answers' in tree: possible_answers = tree['answers'].keys() restarting = False elif restarting: if content == 'yes': tree = self.tree response = tree['say'] possible_answers = tree['answers'].keys() restarting = False elif direction == 'sent': nothing_sent = False if 'answers' in tree and direction == 'sent' and content == tree.get('say'): tree = tree['answers'] elif direction == 'sent' and content == DEFAULT: restarting = True else: raise ValueError("Unexpected direction: " + direction) logging.debug("Responding: %s", response) self.send_callback(user, response, possible_answers) self.users_dao.add_user_event(user, 'sent', response)
Класс инициализируется с тремя параметрами:
- Функция обратного вызова (которая была определена выше) для отправки сообщений к пользователям
- Объект доступа к данным для хранения информации о пользователях
- Дерево, которое содержит логику того, что следует сказать, когда. Это анализируется от YAML, мы показали выше.
Во-первых, мы записываем, что мы получили сообщение пользователя, а затем извлеките все прошлые действия пользователя из объекта доступа к данным. Затем мы воспроизведем действия пользователя, чтобы выяснить, где они в настоящее время в дереве.
Мы инициализируем ответ на сообщение по умолчанию, которое будет возвращено, когда пользователь говорит что-то, что мы не понимаем. В нашем случае это «извините, я не понимал, мы начнем снова?» Есть также некоторые возможные ответы по умолчанию, которые «да» и «нет». Мы также сохраняем запись о том, думаете ли мы, мы перезапускаем разговор с нуля.
Затем мы начнем итацию по истории пользователя. Для каждого сообщения мы проверяем, было ли это отправлено нами или получили ли мы его от пользователя. Если это было получено, мы проверяем матч с текущими вариантами в дереве. Это использует следующую функцию:
def get_content_match(content, tree): matches = [] for key in sorted(tree): if content.lower() in key.lower(): matches.append(key) if len(matches) == 1: return matches[0]
Это проверяет содержимое сообщения пользователя, чтобы увидеть, если он возникает в качестве подстроки одного из текущих вариантов в дереве. Если именно есть единый матч, мы возвращаем этот матч, в противном случае либо ответ пользователя неоднозначен, либо совсем не совпадает.
Далее мы проверяем, отправили нам что-нибудь вообще для пользователя раньше. Если нет, мы устанавливаем наш ответ на первый ответ на дереве и установить возможные ответы на первый набор из дерева.
Затем мы проверяем, нашли ли мы матч для сообщения пользователя. Если мы сделали, мы обновляем дерево, чтобы быть соответствующим ребенком, и извлечь правильный ответ и возможные ответы от дерева.
Затем мы проверяем, предложив ли мы перезапустить, и если пользователь подтвердил, что они хотят перезапустить разговор. В этом случае мы сбрасываем дерево обратно в свое начальное состояние и используем первый ответ, как и раньше.
Для каждого сообщения в истории, которая была отправлена ботом, мы обновляем дерево соответственно. Или если мы отправили сообщение по умолчанию, запишите, что мы можем перезапустить разговор.
Наконец, после пересечения всей истории мы регистрируем наш ответ, отправьте сообщение обратно пользователю и записываем сообщение, которое мы отправили в наш объект доступа к данным.
Последний кусок головоломки
Единственный кусок, оставленный для обсуждения, является объект доступа к данным, который хранит все взаимодействия пользователя. Мы приняли решение о дизайне хранить все действия пользователей и воспроизвести их, поскольку мы сделали выше, потому что это позволило нам легко изменить логику бота и все еще иметь возможность вывести подходящее состояние для бота и пользователя. Если мы выбрали вместо этого, чтобы обозначить каждый узел дерева и хранить эту метку, то любые изменения на дерево оказали бы старые разговоры недействительными.
Таким образом, наш объект доступа к данным должен иметь возможность делать две вещи: сохранить новое пользовательское событие и извлечь все события для конкретного пользователя.
class UserEvent(ndb.Model): user = ndb.StringProperty() direction = ndb.StringProperty() message = ndb.StringProperty() date = ndb.DateTimeProperty(auto_now_add=True) class UserEventsDao(object): def add_user_event(self, user, direction, message): event = UserEvent() event.user = user event.direction = direction event.message = message logging.info("Adding event: %r", event) event.put() def get_user_events(self, user): events = UserEvent.query(UserEvent.user == user) sorted_events = sorted(events, key=lambda x: x.date) return [(event.direction, event.message) for event in sorted_events]
Наш объект доступа к данным использует Google DataStore , который очень прост в использовании из App Engine и имеет щедрый свободный уровень использования. Python API делает использование хранения данных очень легко. Сначала мы создаем модель класса, Увенсент
который указывает поля и их типы. В нашем случае идентификатор пользователя, направление сообщения и сам сообщение являются строками, и, наконец, дата события имеет тип даты.
Чтобы создать и хранить новое событие пользователя, мы просто строим этот класс, установите свойства, а затем вызовите поставить ()
на объекте.
Чтобы получить события для пользователя, мы называем Запрос ()
Функция на классе, проходящее в идентификаторе пользователя. Затем мы сортируем события по дате и вернем список пар направления-сообщения.
Развертывание
Это все биты нашего бота! Теперь развернуть его и подключить его к мессенджере.
Чтобы развернуть приложение к приложению двигателя, используйте gcloud
Команда, которая пришла с приложенным двигателем SDK:
gcloud app deploy --project [YOUR_PROJECT_ID]
После развертывания URL вашего веб-капуста
http://[YOUR_PROJECT_ID].appspot.com/
Обновите свое приложение Facebook с помощью этого URL-адреса веб -ook (следуйте инструкциям здесь ), и вы должны быть хороши, чтобы пойти!
Мир – ваш устриц чата
Вы можете сделать много видов чата, используя эти методы. Однажды я весело сделала Выберите свое собственное приключение Стиль бот, но я уверен, что вы сможете придумать гораздо более изобретательные вещи. О, и если вы хотите попробовать розницу, вы можете попробовать это здесь (Хотя эта версия немного более сложная).
И, если вы не любите всю эту тяжелую работу, вы всегда можете попробовать одно из многочисленных сотворений Chatbot Инструменты Отказ