Создаем Telegram бота на API.AI

  • Tutorial

Чат боты — довольно интересная тема, которой интересуются как гики-энтузиасты, так и компании, которые хотят организовать взаимодействие со своими клиентами наиболее удобным для них способом


Сегодня я опишу вам простой пример создания бота Telegram с использованием платформы для создания разговорных интерфейсов API.AI, который будет приветствовать пользователя и отвечать на вопросы о погоде. По большей части я следовал этим инструкциям, в реальной практике, можно не ограничиваться погодой и реализовать интерфейсы
для автоматизированной поддержки или продаж.


Шаг первый: Подготовка инфраструктуры.


В этом кейсе мы будем использовать только Telegram бота и API.AI, оба сервиса предоставляются бесплатно — нам остается только завести учетные записи.


Создайте бота Telegram


Чтобы создать бота — просто напишите @BotFather (это такой бот, которые умеет создавать и настраивать другие боты):


  1. Отправьте команду /newbot — так мы сообщаем @BotFather, что нам нужен новый бот
  2. Теперь @BotFather попросит нас дать имя новому боту. Это имя будут видеть наши
    будущие пользователи, поэтому название нужно давать понятное и простое.
  3. Последним шагом укажем для бота username, в конце которого нужно обязательно
    написать “bot”.
  4. Если имя не занято, получаем сообщение с подтверждением и токен доступа.

Чтобы было понятнее — ниже скриншот со всеми действиям:


image


Немного теории


Пришло время создать агента API.AI, который в сущности является проектом или контейнером (как вам удобнее называть). Агент содержит настройки контекстов, сущностей и ответов:


  • “контекст” (Intent) отражает связь между тем, что сказал пользователь и
    тем что должна сделать наша программа
  • “сущности” (Entities) — это инструмент извлечения значений параметров для
    нашей программы из естественного языка (того что сказал или написал
    пользователь)
  • “ответы” — это конечный результат работы нашей программы, который мы
    отправляем пользователю на его сообщение

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


Создайте проект в API.AI


Для регистрации в API.AI вам потребуется аккаунт Google (достаточно завести в почту в Gmail). Теперь перейдите по адресу https://api.ai/, нажмите на кнопку “SIGN UP FOR FREE”, а за тем выберите аккаунт, от имени которого хотите авторизоваться.


Теперь переходим к созданию самого агента. Нажмите на “Create agent” и укажите как минимум Имя(Name), Язык(Language) и Часовой пояс (Time Zone).


image


Шаг второй: Настройте агента.


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


image


  1. Кликните на в разделе “Контекст” (Intents). В агенте уже настроены “контексты” на приветствие и ошибки, оставим их пока без изменений.
  2. Укажите название для “контекста” — любое, главное чтобы оно было понятно вам и вашим коллегам.
  3. В разделе “Реплики пользователя” (User Says) приведите примеры вопросов, который может ваш пользователь. Так как мы говорим о погоде, человек может задать вопрос в привязке ко времени и место — учтем это. Чем больше примеров вы предоставите в настройках, тем точнее будет работать агент. Некоторые примеры я привел на скриншоте:

image


В последнем примере слова “завтра” и “Нижнем Тагиле” подсвечены разными цветами — таким образом слова связываются с сущностями (Entities) (в нашем случае сущности системные). Используя эти параметры агент “поймет” в каком городе и для какой даты нужно узнавать погоду.


Добавьте еще парочку своих примеров и нажмите “Сохранить” (SAVE).


Тестируем!


Проверим работу агента на простых вопросах, например, “Погода в Перми в среду”:


image


Все это время в правой верхней части экрана маячила надпись “Try it now” — напишите в это поле или произнесите простой вопрос о погоде и нажмите “Ввод”.


Мы еще не настраивали автоматический ответ, но некоторые параметры агент уже научился определять! В разделе INTENT отражено, что по “мнению” агента пользователь интересуется погодой (настроенный нами “контекст”), в PARAMETER — дату и название города в соответствующих переменных.


Добавьте автоматические ответы


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


Перейдите в раздел “ Ответы” (Response) и введите простые ответы аналогично тому, как вы заполняли “Реплики пользователя”:


image


Как видите — в ответах можно использовать ссылки на выявленные сущности, начните набирать $ — и интерфейс предложит вам выбрать конкретную переменную.


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


Сохраните настройки и протестируйте еще раз:


image


Теперь у нас есть еще и ответ!


Шаг третий: Добавьте внешний сервис.


Наш агент уже “понимает” в каких случая пользователь хочет узнать погоду, на какое число и в каком городе. Теперь осталось получить эти данные из подходящего сервиса и передать агенту. Для этого вам нужно написать парочку скриптов на JS и разместить их в облачном сервисе, в нашем случае — Google Cloud Project.


Создайте стартовый JS файл


Для начала, создайте и перейдите в директорию с именем вашего проекта:


  • Linux или Mac OS X:


    mkdir ~/[PROJECT_NAME]
    cd ~/[PROJECT_NAME]


  • Windows:


    mkdir %HOMEPATH%[PROJECT_NAME]
    cd %HOMEPATH%[PROJECT_NAME]



Теперь создайте файл index.js со следующим содержанием:


Код index.js
/*
    * HTTP Cloud Function.
    *
    * @param {Object} req Cloud Function request context.
    * @param {Object} res Cloud Function response context.
    */
    exports.itsm365Weather = function itsm365Weather (req, res) {
      response = "This is a sample response from your webhook!" //Default response from the webhook to show it's working

      res.setHeader('Content-Type', 'application/json'); //Requires application/json MIME type
      res.send(JSON.stringify({ "speech": response, "displayText": response 
      //"speech" is the spoken version of the response, "displayText" is the visual version
      }));

Настройте Google Cloud Project


  • Выполните настройки “Before you
    begin”
    с 1 по 5 пункты
  • Разверните функцию в облаке выполнив в консоли:


    gcloud beta functions deploy itsm365Weather --stage-bucket [BUCKET_NAME] --trigger-http



где, itsm365Weather — название функции, а [BUCKET_NAME] — наименование хранилища
данных для проекта.


После завершения операции вы увидите результат с URL http триггера:


image


Включите Webhook в API.AI


image


  1. Убедитесь, что находитесь в нужном агенте, а затем кликните “Fulfillment” в левом скрывающемся меню.
  2. Включите использование Webhook в правой верхней части экрана.
  3. Введите URL, полученный на предыдущем этапе.
  4. Сохраните изменения.

Подключите исполнение новой функции в настройках “контекста”


image


  1. Перейдите в настройки “контекста” прогноза погоды
  2. Разверните блок Fulfillment в нижней части страницы
  3. Отметьте галочкой “Использовать Webhook”
  4. Сохраните настройки и проверьте результат:

image


Настройте API для получения погоды


Для простоты, воспользуемся сервисом WWO (World Weather Online), в котором вам нужно получить ключ API (просто зарегистрируйтесь через Facebook или Github).


Обновите код стартового JS файла, не забыв ввести ключ API для получения информации о погоде:


Исходный код сервиса для получения прогноза погоды
// Copyright 2017, Google, Inc.
// Licensed under the Apache License, Version 2.0 (the 'License');
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an 'AS IS' BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';
const http = require('http');
const host = 'api.worldweatheronline.com';
const wwoApiKey = '98cfb8e40ecc47c4a2f205209172608';
exports.itsm365Weather = (req, res) => {
  // Get the city and date from the request
  let city = req.body.result.parameters['geo-city']; // city is a required param
  // Get the date for the weather forecast (if present)
  let date = '';
  if (req.body.result.parameters['date']) {
    date = req.body.result.parameters['date'];
    console.log('Date: ' + date);
  }
  // Call the weather API
  callWeatherApi(city, date).then((output) => {
    // Return the results of the weather API to API.AI
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ 'speech': output, 'displayText': output }));
  }).catch((error) => {
    // If there is an error let the user know
    res.setHeader('Content-Type', 'application/json');
    res.send(JSON.stringify({ 'speech': error, 'displayText': error }));
  });
};
function callWeatherApi (city, date) {
  return new Promise((resolve, reject) => {
    // Create the path for the HTTP request to get the weather
    let path = '/premium/v1/weather.ashx?format=json&num_of_days=1' +
      '&q=' + encodeURIComponent(city) + '&key=' + wwoApiKey + '&date=' + date + '&lang=ru';
    console.log('API Request: ' + host + path);
    // Make the HTTP request to get the weather
    http.get({host: host, path: path}, (res) => {
      let body = ''; // var to store the response chunks
      res.on('data', (d) => { body += d; }); // store each response chunk
      res.on('end', () => {
        // After all the data has been received parse the JSON for desired data
        let response = JSON.parse(body);
        let forecast = response['data']['weather'][0];
        let location = response['data']['request'][0];
        let conditions = response['data']['current_condition'][0];
        let currentConditions = conditions['lang_ru'][0]['value'];
        // Create response
        let output = `На ${forecast['date']} в ${location['query']} ${currentConditions}, температура воздуха от ${forecast['mintempC']}°C до ${forecast['maxtempC']}°C.`;
        // Resolve the promise with the output text
        console.log(output);
        resolve(output);
      });
      res.on('error', (error) => {
        reject(error);
      });
    });
  });
}

Заново разверните функцию в облачном проекте.


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


Взаимодействуя с пользователем мы не можем быть уверены в том, что он предоставит нам всю информацию, необходимую для подготовки ответа во внешнем сервисе сразу в самом первом сообщении. В нашем примере для получения прогноза сервису потребуется дата и город. Если дата не известна, мы можем с успехом предположить, что пользователь подразумевает “сегодня”, но о городе мы можем узнать только от самого пользователя.


Сделайте “расположение” обязательным параметром


image


Откройте настройки контекста “Прогноз погоды” и укажите параметр geo-city обязательным к заполнению. Затем настройте уточняющий вопрос по ссылке в колонке “Prompts”.


Сохраните настройки и проверьте поведение агента, задав ему простой вопрос “погода”:


image


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


Создайте возвращаемое уточнение для расположения


Чтобы использовать данные полученные на предыдущих этапа взаимодействия с пользователем, вам потребуется настроить соответствующие уточнения.


image


В настройка контекста “прогноз погоды” вбейте в поле “Add output context” название возвращаемого уточнения “location” и сохраните настройки.


Создайте новый контекст для уточнения


Удобно, когда по одному и тому же расположению можно задавать несколько вопросов, при этом не уточнять у пользователя, какой город он имеет ввиду. Вы уже настроили возвращаемое уточнение, которе как можно использовать для обработки уточняющих вопросов.


image


  1. Создайте новый контекст в разделе Intents или кликните по значку в строке
    Intents левого выдвигающегося меню.
  2. Назовите новый контекст “Уточнение погоды” (или любое другое понятное вам название).
  3. Установите входящие и исходящие уточнения как “location”
  4. Добавьте реплики пользователя, например, “Что на счет завтра”
  5. Добавьте параметр сущности со следующими значениями:
    Parameter Name:
    geo-city
    Value: #location.geo-city
  6. Добавьте ответ для пользователя в раздел “Response”:
    — “Извини, но я не могу получить прогноз на $date-period в #location.geo-city”
  7. Включите использование webhook в меню Fulfillment.
  8. Сохраните настройки и протестируйте в консоли:

image


Шаг пятый: Приветствие и обработка непредвиденных ситуаций


Основной костяк агента готов, теперь неплохо сделать так, чтобы робот
приветствовал пользователя, а также знал что отвечать на непредвиденные вопросы.


Настройте ответы “по умолчанию” для непредвиденных ситуаций


Если пользовать задаст непредвиденный вопрос (в нашем случае — не о погоде) агент включит в работу контекст для обработки непредвиденных ситуаций (Default Fallback Intent):


image


Перейдите в настройке этого контекста, при необходимости настройте свои варианты ответов.


Настройте контекст приветствия


Приветствие можно настроить аналогичным способом в соответствующем контенте —
Default Welcome Intent


image


Шаг шестой: запустите бота


Подключите Telegram бота к агенту


Перейдите в настройки “Интеграций” (Integrations) и включите бота в разделе
“One-click integrations”:


image


Скопируйте в поле “Telegram token” токен, который вы получили у @botFather и
нажмите на START.


Проверьте работу бота


Перейдите в своего бота и попробуйте ему что-нибудь написать, в моем случае это
@itsm365_weather_bot (я пользовался бесплатными аккаунтами погоды, поэтому после 500 запросов в день бот превратится в тыкву).


image


Заключение


API.AI уже вполне можно пользоваться для построения диалоговых интерфейсов в мессенджерах, чатах поддержки с соцсетях. С учетом того, что инструмент можно легко интегрировать со своими сервисами — это удобная рабочая лошадка для автоматизации коммуникации с вашими пользователями.


P.S. Этой мой первый пост, буду признателен за конструктивную обратную связь!

  • +22
  • 75.5k
  • 9
Share post

Similar posts

Comments 9

    +2
    chatbotlab.io
    А вот такая штука, может еще больше расширить описанную вами функциональность. П.с. не реклама.
      0

      Спасибо! Обязательно посмотрю!

      +1
      Что до чатов поддержки — ЕМНИП, api не позволяло встроенными средствами добавить третью сторону к диалогу, нет? Если точно — я не нашёл возможности послать произвольное сообщение пользователю, кроме как с помощью webhook-а, но у них есть таймаут.

      Вроде бы единственный обходной путь — запускать собственного бота, который будет маршрутизировать сообщения между «пользовательским» ботом, api.ai-м и саппортом.

      Или за последние месяцы что-то таки поменялось?
        0

        Пока нет, есть и другие причины, почему стоит запускать своего бота в отдельном сервисе, например, если вы используете для общения Telegram — API.AI не может получить идентификатор автора сообщения и чата (из того, что я нашел в документации и формах, такой функционал предусмотрен только для slack).
        Мы сейчас как раз планируем настроить бота для своей техподдержки (для клиентов и наших специалистов), по результатам — обязательно отпишусь!

          0
          Насчет информации о пользователе, гляньте поле req.body.originalRequest — там хранится вся информация, которая приходит от Telegram. Оттуда можно получить user_id.
        +2
        Круто! Спасибо за статью и отдельное спасибо, что не поленились так подробно всё заскринить!:)

        Единственное, что во всем этом меня напрягает (если это кого-то вообще интересует), так это то, что вся логика обработки ответов находится у Google. И «по щучьему велению» внезапно может произойти так, что этот сервис окажется для Google «не перспективным» и закроется, и все останутся у разбитого корыта. Конечно, я понимаю, что сервис бесплатный и никто никому ничем не обязан, но внутренний параноик забеспокоился.
          0

          Есть такая проблема… У коллег в Севастополе API.AI только через VPN и Tor =(

          0
          Не совсем понятно, как связываются параметры вроде sys.date и слова вроде «завтра»? Нужно как-то научить, что «завтра» это дата + 1 день?
            0

            Это стандартные параметры, система уже "обучена" вытаскивать дату из таких слов. Если возникает потребность "вытягивать" новые параметры из текста — нужно настроить пользовательские сущности (Entities). Когда будет время, опишу на реальном примере как их настроить, чтобы вытаскивать нужную информацию из диалога с пользователем.

          Only users with full accounts can post comments. Log in, please.