owned this note
owned this note
Published
Linked with GitHub
---
title: Введение в разработку диалоговых ботов. День 2
---
# Яндекс.Алиса

Яндекс.Алиса - это продукт от компании Яндекс, представляющий из себя голосового ассистента, который работает на таких платформах как:
- Умные колонки (Яндекс.Станция и т.д.)
- Яндекс.Браузер
- Яндекс.Телефон
- Яндекс.Авто
- Некоторые модели умных часов
- Некоторые модели наушников
Вот что умеет Алиса:
- Поиск информации в интернете
- Получение данных о погоде
- Работа с картами и навигацией
- Работа с программами на устройстве
- Голосовой заказ услуг
- Распознавание изображений
- и многое другое...
# Навыки для Алисы
Навык для Алисы представляет из себя диалог, который запускается заданной командой активации в Алисе и расширяющий возможности голосового помощника от Яндекса.

Платформа [Яндекс.Диалоги](https://dialogs.yandex.ru/) предоставляет возможность сторонним разработчикам создавать собственные навыки для Алисы. Таким образом пользователь будет общаться уже не с самой Алисой, а с отдельным навыком.
Также Яндекс.Диалоги позволяют создавать чаты для бизнеса и системы управления умным домом.
## Отличия от ботов в Telegram
Навыки Алисы и боты в Telegram имеют отличия, вот некоторые из них:
- Навык Алисы не может сам написать пользователю, а лишь ответить на его запрос
- Чтобы люди могли использовать навык, он должен пройти модерацию
- Навык не видит личную информацию о пользователе (только `userId`)
- При разработке нужно учитывать устройства без экрана
- Единственное, что может принимать навык - текст
# Создание навыка для Алисы
Для того, чтобы создать навык, нужно иметь аккаунт Яндекс.
Зарегистрировать навык можно тут: https://dialogs.yandex.ru/developer/
## API
Когда пользователь общается с навыком, его голосовой запрос преобразуется в текст и отправляется на ваш сервер в формате JSON. Пример запроса:
```json
{
"meta": {
"locale": "ru-RU",
"timezone": "Europe/Moscow",
"client_id": "ru.yandex.searchplugin/5.80 (Samsung Galaxy; Android 4.4)",
"interfaces": {
"screen": { }
}
},
"request": {
"command": "закажи пиццу на улицу льва толстого 16 на завтра",
"original_utterance": "закажи пиццу на улицу льва толстого, 16 на завтра",
"type": "SimpleUtterance",
"markup": {
"dangerous_context": true
},
"payload": {},
"nlu": {
"tokens": [
"закажи",
"пиццу",
"на",
"льва",
"толстого",
"16",
"на",
"завтра"
],
"entities": [...]
}
},
"session": {
"new": true,
"message_id": 4,
"session_id": "2eac4854-fce721f3-b845abba-20d60",
"skill_id": "3ad36498-f5rd-4079-a14b-788652932056",
"user_id": "AC9WC3DF6FCE052E45A4566A48E6B7193774B84814CE49A922E163B8B29881DC"
},
"version": "1.0"
}
```
Запрос представляет из себя POST request. Чтобы ответить пользователю на его сообщение, нужно ответить на POST request также в JSON формате. Пример:
```json
{
"response": {
"text": "Здравствуйте! Это мы, хороводоведы.",
"tts": "Здравствуйте! Это мы, хоров+одо в+еды.",
"buttons": [
{
"title": "Надпись на кнопке",
"payload": {},
"url": "https://example.com/",
"hide": true
}
],
"end_session": false
},
"session": {
"session_id": "2eac4854-fce721f3-b845abba-20d60",
"message_id": 4,
"user_id": "AC9WC3DF6FCE052E45A4566A48E6B7193774B84814CE49A922E163B8B29881DC"
},
"version": "1.0"
}
```
## Простой эхо навык для Алисы
Рассмотрим, как написать и задеплоить простой эхо навык на Яндекс.Диалогах.
Для сервера, который будет принимать запросы от Яндекса, будем использовать Flask.
```python=
from flask import Flask, request
import os
app = Flask(__name__)
@app.route('/', methods=['POST'])
def echo():
response = {
'version': request.json['version'],
'session': request.json['session'],
'response': {
'text': request.json['request']['original_utterance'] or 'Привет!'
}
}
return response
app.run(host='0.0.0.0', port=os.getenv('PORT', 5000))
```
Задеплоим бота на Heroku. Не забудьте про `requirements.txt` и `Procfile`!
Протестировать бота можно во вкладке "Тестирование" в панели администрирования навыка.

## Задание №1: Навык - проверка на палиндром
Модифицируйте простой эхо навык таким образом, чтобы он:
1) При входе в навык приветствовал пользователя и сразу спрашивал у него слово
2) При получении слова отвечал пользователю, является ли его слово палиндромом и предложил сказать еще одно слово
3) Если слов больше чем одно, сообщил об ошибке и предложил сказать другое слово
Задеплойте ваш навык на heroku, и протестируйте его.
## Персонализация
Запрос, присланный с серверов Яндекса, имеет в себе поле `userId` - идентификатор пользователя (как `user_id` в Telegram). Используя `userId`, мы можем реализовать поддержку многопользовательности.
Простой навык, спрашивающий у пользователя имя и город:
```python=
from flask import Flask, request
import os
app = Flask(__name__)
states = {}
def get_state(user_id):
return states.get(user_id, 0)
def set_state(user_id, state):
if states.get(user_id, None) is None:
states.update({user_id: state})
else:
states[user_id] = state
@app.route('/', methods=['POST'])
def webhook():
response = {
'version': request.json['version'],
'session': request.json['session'],
'response': {
'text': 'Я тебя не понимаю...',
'end_session': False
}
}
user_id = request.json['session']['user_id']
state = get_state(user_id)
if state == 0:
response['response']['text'] = 'Привет! Как тебя зовут?'
set_state(user_id, 1)
elif state == 1:
response['response']['text'] = 'Приятно познакомиться! А где ты живёшь?'
set_state(user_id, 2)
elif state == 2:
response['response']['text'] = 'Классный город! Я всё записала!'
response['response']['end_session'] = True
set_state(user_id, 3)
elif state == 3:
response['response']['text'] = 'Я уже всё про тебя знаю!'
return response
app.run(host='0.0.0.0', port=os.getenv('PORT', 5000))
```
## Задание №2: Навык с сохранением данных в БД
Разработайте навык, спрашивающий у пользователя имя, фамилию и номер телефона.
- Данные должны сохраняться в базе данных
- После опроса пользователя, при его фразе "Мои данные", наык должен отправить пользователю его имя, фамилию и номер телефона (в одном сообщении)
Задеплойте бота на heroku и протестируйте его.
# Архитектура Telegram-бота с воркерами
Для ускорения процесса обработки сообщения мы можем:
- Обрабатывать каждое сообщение в отдельном потоке
- Создавать воркеров и распределять между ними задачи
Воркер (англ. worker – работник) – программа, выполняемая в отдельном потоке в фоновом режиме. Обычно каждый воркер выполняет свою часть большой задачи.
## RabbitMQ
Чтобы реализовать общение между воркерами, воспользуемся брокером сообщений (message broker) RabbitMQ. Он реализует протокол AMQP, призванный выстроить удобное и быстрое общение между программами.
Для установки RabbitMQ: https://www.rabbitmq.com/download.html
Официальные туториалы: https://www.rabbitmq.com/getstarted.html
## Протокол работы RabbitMQ

Буквой `P` обозначен Producer сообщения, т.е. программа, добавляющая сообщения в очередь под названием `hello`. Буквой `C` обозначен Consumer сообщения, т.е. программа, принимающая сообщения из очереди.
При добавлении нового сообщения в очередь, RabbitMQ автоматически отдаст его consumer'у.
## Python3 + RabbitMQ
Для того чтобы взаимодействовать с сервером RabbitMQ через Python, мы будем использовать модуль [Pika](https://github.com/pika/pika).
Установка: `python3 -m pip install pika`
Пример использования:
```python=
# producer.py
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
channel.basic_publish(exchange='', routing_key='hello', body='Hello World!')
print(" [x] Отправил сообщение: 'Hello World!'")
connection.close()
```
```python=
# consumer.py
import pika
connection = pika.BlockingConnection(
pika.ConnectionParameters(host='localhost'))
channel = connection.channel()
channel.queue_declare(queue='hello')
def callback(ch, method, properties, body):
print(" [x] Получено сообщение: %r" % body)
channel.basic_consume(
queue='hello', on_message_callback=callback, auto_ack=True)
print(' [*] Жду сообщений...')
channel.start_consuming()
```
## Задание №3: Запуск и тестирование RabbitMQ
- Установите и запустите сервер RabbitMQ
- Создайте и скопируйте скрипты `producer.py` и `consumer.py`
- Запустите `consumer.py`
- Несколько раз запустите `producer.py` в отдельном окне терминала
Все ли работает корректно? Увидили ли вы отправку и получение сообщений?
# Telegram бот с воркерами
Вспомним нашего Speech-to-text бота. Проблема в том, что при отправке нескольких голосовых сообщений от разных пользователей, каждому пользователю нужно будет дождаться, пока обработаются запросы всех предыдущих пользователей.

Как решить эту проблему?
Декомпозируем работу бота на несколько компонентов-воркеров:
- Receiver (Получение сообщения и отправление сообщения "Обрабатываю...")
- Downloader (Скачивание голосового сообщения с серверов Telegram)
- Transcoder (Преобразование .ogg в .wav)
- Speech-to-text (Преобразование .wav в текст)
- Editor (Изменение сообщения на распознанный текст)
:::success
Такой подход позволяет при нахождении узких мест (воркеров, занимающих больше всего времени), решать эту проблему с помощью запуска дополнительных воркеров этого типа, не меняя код воркера.
:::
## Архитектура STT-бота с воркерами

## Задание №4: Speech-to-text бот с воркерами
Модифицируйте вашего STT бота таким образом, чтобы он состоял из 5 отдельных воркеров, общающихся с помощью RabbitMQ.
Воркеры:
- Receiver (Получение сообщения и отправление сообщения "Обрабатываю...")
- Downloader (Скачивание голосового сообщения с серверов Telegram)
- Transcoder (Преобразование .ogg в .wav)
- Speech-to-text (Преобразование .wav в текст)
- Editor (Изменение сообщения на распознанный текст)
# Что можно изучить дальше?
- AIOGram, асинхронная библиотека для разработки Telegram ботов. Намного лучше поддерживается и развивается чем pyTelegramBotAPI, имеет больше функционала: https://github.com/aiogram/aiogram
- Telethon, поможет в разработке user-ботов (для обхода ограничений обычных ботов): https://github.com/LonamiWebs/Telethon
- Библиотеки и ресурсы для Яндекс.Диалогов: https://github.com/sameoldmadness/awesome-alice