ProHoster > блог > адміністраванне > Стварэнне stateful навыку для Алісы на serverless функцыях Яндэкс.Аблокі і Пітон
Стварэнне stateful навыку для Алісы на serverless функцыях Яндэкс.Аблокі і Пітон
Пачнём з навін. Учора Яндекс.Воблака анансавала запуск сэрвісу бессерверных вылічэнняў Yandex Cloud Functions. Гэта значыць: ты пішаш толькі код свайго сэрвісу (напрыклад, вэб-прыкладанні або чатбота), а Воблака само стварае і абслугоўвае віртуальныя машыны, дзе ён запускаецца, і нават рэплікуе іх, калі ўзрастае нагрузка. Думаць увогуле не трэба, вельмі зручна. І плата ідзе толькі за час вылічэнняў.
Зрэшты, сёй-той можа ўвогуле не плаціць. Гэта - распрацоўшчыкі знешніх навыкаў Алісы, гэта значыць убудаваных у яе чататаў. Напісаць, захапіць і зарэгістраваць такі навык можа любы распрацоўшчык, а з сённяшняга дня навыкі нават не трэба хосціць - дастаткова заліць іх код у воблака ў выглядзе той самай бессервернай функцыі.
Але ёсць пара нюансаў. Па-першае, ваш пітніцкі код можа патрабаваць нейкіх залежнасцяў, і зацягваць іх у Воблака - нетрывіяльна. Па-другое, любому нармальнаму чату трэба захоўваць дзесьці стан дыялогу (stateful таму); як зрабіць гэта ў бессервернай функцыі прасцей за ўсё? Па-трэцяе, а як наогул можна хутка-брудна напісаць навык для Алісы ці ўвогуле нейкага бота з ненулявым сюжэтам? Аб гэтых нюансах, уласна, артыкул.
Маральная падрыхтоўка
Для нецярплівых: неабходныя залежнасці я збіраю make-файлам перад загрузкай функцыі ў воблака, стан дыялогу захоўваю ў яндэксаўскім Object Storage (яно падтрымлівае API S3), а для кіравання дыялогам выкарыстоўваю ўласную бібліятэку tgalice. У выніку атрымліваецца такі дэма-навык. А зараз разбяром усё гэта крыху больш падрабязна.
Для крыху менш нецярплівых: перш чым апускацца ў маю гісторыю пра складанасці, я вельмі рэкамендую пачаць з простага. А менавіта, стварыць аднафайлавую функцыю-эхобота, зарэгістраваць і пацясціць яе ў кансолі распрацоўніка Дыялогаў; падрабязнасці - у блогу Дыялогаў и дакументацыі Аблокі. І толькі потым ужо прыступаць.
Калі жадаецца адразу адчуць, завошта мы тут дужаемся, папытаеце Алісу 'уключы навык «айцішны гараскоп»'. Павінен адбыцца прыкладна наступны дыялог:
Аліса: Прывітанне! Вы знаходзіцеся ў навыку «айцішны гараскоп». Скажыце «Старт», каб даведацца, што абяцаюць вам зоркі.
Карыстальнік: Старт
А: Калі ласка, назавіце сваё імя
Ю: Васісуалій
А: Цяпер скажыце мне год вашага нараджэння. Толькі чатыры лічбы, нічога лішняга.
Ю: другі
А: Калі ласка, паспрабуйце яшчэ раз. Назавіце год вашага нараджэння - чатыры лічбы.
Ю: 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. Пры жаданні, можна зрабіць гэта і з каманднага радка, напрыклад, выкарыстоўваючы AWS CLI.
Стварыць бессерверную функцыю праз вэб-інтэрфейс або выкарыстоўваючы ўтыліту 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, прыблізна такі:
Астатняе - некалькі простых інструментаў, загорнутых у бібліятэку 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 таксама ёсць шаблон, таму цалікавы дыялогавы мэнэджар складзены з кавалачкаў:
CascadeDialogManager працуе проста: спрабуе прымяніць да бягучага стану дыялогу ўсе свае складнікі па чарзе, і выбірае першую дарэчную.
У якасці адказу на кожнае паведамленне дыялогавы мэнэджар вяртае сілкавальны аб'ект Response, які далей можна сканвертаваць у голы тэкст, або ў паведамленне ў Алісе ці Тэлеграме - гледзячы дзе бот запушчаны; у ім жа змяшчаецца і зменены стан дыялогу, які трэба захаваць. Усёй гэтай кухняй займаецца яшчэ адзін клас, DialogConnector, таму непасрэдны скрыпт для запуску навыку на Yandex Functions выглядае так:
Як бачыце, большая частка гэтага кода стварае падлучэнне да S3-інтэрфейсу Object Storage. Як непасрэдна выкарыстоўваецца гэтае падлучэнне, можна пачытаць у кодзе tgalice.
Апошні радок стварае функцыю alice_handler - тую самую, якую мы загадалі тузаць Яндэкс.Аблокі, калі задавалі параметр --entrypoint=main.alice_handler.
Вось, уласна, і ўсё. Make-файлы для зборкі, S3-падобнае Object Storage для захоўвання кантэксту, і питонячая бібліятэка tgalice. Разам з бессервернымі функцыямі і выразнасцю пітона гэтага дастаткова для распрацоўкі навыку здаровага чалавека.
Вы можаце спытаць, навошта патрэбна спатрэбілася ствараць tgalice? Увесь сумны код, які перакладае JSON'ы з запыту ў адказ і са сховішча ў памяць і зваротна, ляжыць у ёй. Там жа ляжыць ужывалка рэгулярак, функцыя для разумення таго, што "люты" падобна на "люты", і іншае NLU для бедных. Па маёй задумцы, гэтага ўжо павінна быць дастаткова, каб можна было накідваць прататыпы навыкаў у yaml-файлах, не занадта адцягваючыся на тэхнічныя дэталі.
Калі жадаецца больш сур'ёзнага NLU, можна прыкруціць да свайго навыку Раса або DeepPavlov, Але для іх наладкі запатрабуюцца дадатковыя скокі з бубнам, асабліва на serverless. Калі зусім не жадаецца кадзіць, варта скарыстацца візуальным канструктарам тыпу Aimylogic. Ствараючы tgalice, я думаў аб нейкім прамежкавым шляху. Пабачым, што з гэтага выйдзе.