Почнемо з новин. Вчора Яндекс.Хмара анонсувала запуск сервісу безсерверних обчислень . Це означає: ти пишеш тільки код свого сервісу (наприклад, веб-додатки або чата), а Хмара сама створює та обслуговує віртуальні машини, де він запускається, і навіть реплікує їх, якщо зростає навантаження. Думати взагалі не треба дуже зручно. І плата йде лише за час обчислень.
Втім, дехто може взагалі не платити. Це розробники , тобто вбудованих у неї чоботів. Написати, захистити та зареєструвати таку навичку може будь-який розробник, а з сьогоднішнього дня навички навіть не треба хостити – достатньо залити їх код у хмару у вигляді .
Але є кілька нюансів. По-перше, ваш питній код може вимагати якихось залежностей, і затягувати їх у Хмару — нетривіально. По-друге, будь-якому нормальному чату потрібно зберігати десь стан діалогу (stateful тому); як зробити це в безсерверній функції найпростіше? По-третє, а як взагалі можна швидко-брудно написати навичку для Аліси чи взагалі якогось бота з ненульовим сюжетом? Про ці нюанси, власне, стаття.

Моральна підготовка
Для нетерплячих: необхідні залежності я збираю make-файлом перед завантаженням функції в хмару, стан діалогу зберігаю в яндексівському Object Storage (воно підтримує API S3), а для керування діалогом використовую власну бібліотеку . У результаті виходить демо-навичка. А тепер розберемо все це трохи детальніше.
Для трохи менш нетерплячих: перш ніж занурюватися в мою історію про складнощі, я дуже рекомендую почати з простого. А саме, створити однофайлову функцію-ехобота, зареєструвати та потестити її в консолі розробника Діалогів; подробиці - у и . І лише потім уже приступати.
Якщо хочеться відразу відчути, за що ми тут боремося, попросіть Алісу ''. Повинен відбутися приблизно наступний діалог:
Аліса: Привіт! Ви знаходитесь у навичці «Айтішний гороскоп». Скажіть «Старт», щоб дізнатися, що обіцяють вам зірки.
Користувач: Старт
А: Будь ласка, назвіть своє ім'я
Ю: Васісуалій
А: Тепер скажіть мені рік вашого народження. Лише чотири цифри, нічого зайвого.
Ю: другий
А: Будь ласка, спробуйте ще раз. Назвіть рік вашого народження – чотири цифри.
Ю: 2002
А: Чудово! Тепер назвіть місяць вашого народження.
Ю: Лютий
А: Чудово! Нарешті, назвіть мені дату вашого народження — лише число, лише одна чи дві цифри.
Ю: 18
А: Дякую, Васісуалію! Тепер ми знаємо: вам 17 років, і ви Водолій. Оце вам, звичайно, пощастило! Зірки кажуть вам: а что говорят звёзды, вы узнаете, запустив навык.
Проблема в тому, що для підтримки навіть такого простого діалогу потрібно пам'ятати ім'я та дату народження користувача, а серед безсерверних функцій це нетривіально. Зберігати контекст оперативної пам'яті чи файликом на диску не вдасться, т.к. Яндекс.Хмара може запустити функцію на кількох віртуальних машинах одночасно і перемикатися між ними довільним чином. Прийде скористатися якимось зовнішнім сховищем. Вибрано було Object Storage, як досить недороге та нескладне сховище прямо в Яндекс.Хмарі (тобто, напевно, швидке). Як безкоштовна альтернатива можна спробувати, наприклад, халявний шматочок десь далеко. І для Object Storage (він підтримує інтерфейс S3), і для Mongo є зручні пітонівські обгортки.
Інша проблема — що для ходіння і в Object Storage, і MongoDB, і в будь-яку іншу базу або сховище даних, потрібні якісь зовнішні залежності, які потрібно залити на Yandex Functions разом з кодом своєї функції. І хотілося б це робити зручно. Дуже зручно (типу як на heroku), на жаль, не вийде, але якийсь базовий комфорт можна створити, написавши скрипт для складання оточення (make-файл).
Як запустити навичку-гороскоп
- Підготуватися: зайти на якусь машинку з лінуксом. В принципі, з Windows теж, напевно, можна працювати, але із запуском make-файлу тоді доведеться почаклувати. І в будь-якому випадку вам знадобиться встановлений Python не нижче 3.6.
- Схиляти собі з гітхабу .
- Зареєструватися в Я.Облаці:
- Створити собі два бакети в , назвати їх будь-яким ім'ям
{BUCKET NAME}иtgalice-test-cold-storage(ось це друге ім'я зараз захардшкірене вmain.pyмого прикладу). Перший бакет потрібен буде лише для деплою, другий — для зберігання станів діалогу. - Створити дати йому роль
editor, і отримати до нього статичні креденшали{KEY ID}и{KEY VALUE}— їх використовуватимемо для запису стану діалогу. Все це потрібно, щоб функція з Я.Хмари могла отримати доступ до сховища з Я.Хмари. Колись, сподіваюся, авторизація стане автоматичною, але поки що — так. - (Не обов'язково) встановити
yc. Створити функцію можна і через веб-інтерфейс, але CLI хороший тим, що всі нововведення з'являються в ньому швидше. - Тепер можна, власне, підготувати складання залежностей: запустити в командному рядку з папки з прикладом навички
make all. Встановиться купа бібліотек (переважно, як завжди, непотрібних) в папкуdist. - Ручками залити у Object Storage (у бакет
{BUCKET NAME}) архів, що вийшов на попередньому кроціdist.zip. За бажанням, можна зробити це і з командного рядка, наприклад, використовуючи . - Створити безсерверну функцію через веб-інтерфейс або за допомогою утиліти
yc. Для утиліти команда виглядатиме ось так:
yc serverless function version create
--function-name=horoscope
--environment=AWS_ACCESS_KEY_ID={KEY ID},AWS_SECRET_ACCESS_KEY={KEY VALUE}
--runtime=python37
--package-bucket-name={BUCKET NAME}
--package-object-name=dist.zip
--entrypoint=main.alice_handler
--memory=128M
--execution-timeout=3sПри створенні функції вручну всі параметри заповнюються аналогічно.
Тепер створену вами функцію можна тестувати через консоль розробника, а потім доопрацьовувати та публікувати навичку.

Що там під капотом
Make-файл насправді містить у собі досить простий скрипт для встановлення залежностей та їх укладання в архів dist.zip, приблизно такий:
mkdir -p dist/
pip3 install -r requirements.txt --target dist/
cp main.py dist/main.py
cp form.yaml dist/form.yaml
cd dist && zip --exclude '*.pyc' -r ../dist.zip ./*Решта — кілька простих інструментів, загорнутих до бібліотеки tgalice. Процес заповнення даних про користувача описується конфігом form.yaml:
form_name: 'horoscope_form'
start:
regexp: 'старт|нач(ать|ни)'
suggests:
- Старт
fields:
- name: 'name'
question: Пожалуйста, назовите своё имя.
- name: 'year'
question: Теперь скажите мне год вашего рождения. Только четыре цифры, ничего лишнего.
validate_regexp: '^[0-9]{4}$'
validate_message: Пожалуйста, попробуйте ещё раз. Назовите год вашего рождения - четыре цифры.
- name: 'month'
question: Замечательно! Теперь назовите месяц вашего рождения.
options:
- январь
...
- декабрь
validate_message: То, что вы назвали, не похоже на месяц. Пожалуйста, назовите месяц вашего рождения, без других слов.
- name: 'day'
question: Отлично! Наконец, назовите мне дату вашего рождения - только число, всего одна или две цифры.
validate_regexp: '[0123]?d$'
validate_message: Пожалуйста, попробуйте ещё раз. Вам нужно назвать число своего рождения (например, двадцатое); это одна или две цифры.Роботу з розбору цього конфігу та обчислення фінального результату бере на себе клас класу.
class CheckableFormFiller(tgalice.dialog_manager.form_filling.FormFillingDialogManager):
SIGNS = {
'январь': 'Козерог',
...
}
def handle_completed_form(self, form, user_object, ctx):
response = tgalice.dialog_manager.base.Response(
text='Спасибо, {}! Теперь мы знаем: вам {} лет, и вы {}. n'
'Вот это вам, конечно, повезло! Звёзды говорят вам: {}'.format(
form['fields']['name'],
2019 - int(form['fields']['year']),
self.SIGNS[form['fields']['month']],
random.choice(FORECASTS),
),
user_object=user_object,
)
return responseТочніше, базовий клас FormFillingDialogManager займається заповненням «форми», а метод дочірнього класу handle_completed_form каже, що робити, коли вона готова.
Крім цього основного потоку діалогу користувача треба ще привітати, а також видати довідку за командою «допомога» та випустити з навички за командою «вихід». Для цього в tgalice також є шаблон, тому ціліковий діалоговий менеджер складений зі шматочків:
dm = tgalice.dialog_manager.CascadeDialogManager(
tgalice.dialog_manager.GreetAndHelpDialogManager(
greeting_message=DEFAULT_MESSAGE,
help_message=DEFAULT_MESSAGE,
exit_message='До свидания, приходите в навык "Айтишный гороскоп" ещё!'
),
CheckableFormFiller(`form.yaml`, default_message=DEFAULT_MESSAGE)
)CascadeDialogManager Працює просто: намагається застосувати до поточного стану діалогу всі свої складові по черзі, і вибирає першу доречну.
Як відповідь на кожне повідомлення діалоговий менеджер повертає об'єкт живлення Response, який далі можна сконвертувати в голий текст, або повідомлення в Алісі або Телеграмі - дивлячись де бот запущений; в ньому міститься і змінений стан діалогу, який потрібно зберегти. Усією цією кухнею займається ще один клас, DialogConnectorтому безпосередній скрипт для запуску навички на Yandex Functions виглядає так:
...
session = boto3.session.Session()
s3 = session.client(
service_name='s3',
endpoint_url='https://storage.yandexcloud.net',
aws_access_key_id=os.environ['AWS_ACCESS_KEY_ID'],
aws_secret_access_key=os.environ['AWS_SECRET_ACCESS_KEY'],
region_name='ru-central1',
)
storage = tgalice.session_storage.S3BasedStorage(s3_client=s3, bucket_name='tgalice-test-cold-storage')
connector = tgalice.dialog_connector.DialogConnector(dialog_manager=dm, storage=storage)
alice_handler = connector.serverless_alice_handlerЯк бачите, більшість цього коду створює підключення до S3-інтерфейсу Object Storage. Як безпосередньо використовується це підключення, можна почитати .
Останній рядок створює функцію alice_handler — ту саму, яку ми наказали смикати Яндекс.Хмарі, коли задавали параметр --entrypoint=main.alice_handler.
Ось, власне, і все. Make-файли для збирання, S3-подібне Object Storage для зберігання контексту, та питоняча бібліотека tgalice. Разом з безсерверними функціями і виразністю пітона цього достатньо розробки навички здорової людини.
Ви можете запитати, навіщо потрібно створювати tgalice? Весь нудний код, що перекладає JSON'и із запиту у відповідь і зі сховища в пам'ять і назад, лежить у ній. Там лежить застосованка регулярок, функція розуміння те, що «лютий» схоже «лютий», та інше NLU для бідних. На мою думку, цього вже має бути достатньо, щоб можна було накидати прототипи навичок в yaml-файлах, не надто відволікаючись на технічні деталі.
Якщо хочеться більш серйозного NLU, можна прикрутити до своєї навички або , але для їх налаштування будуть потрібні додаткові танці з бубном, особливо на serverless. Якщо зовсім не хочеться кодувати, варто скористатися візуальним конструктором типу . Створюючи tgalice, я думав про якийсь проміжний шлях. Подивимося, що з цього вийде.
Ну а нині вступайте у , читайте , і створюйте чудові !
Джерело: habr.com
