Creating a stateful skill for Alice on the serverless functions of Yandex.Cloud and Python

Let's start with the news. Yesterday, Yandex.Cloud announced the launch of serverless computing service Yandex Cloud Functions . This means: you only write the code of your service (for example, a web application or chatbot), and the Cloud itself creates and maintains virtual machines where it starts, and even replicates them if the load increases. It’s not necessary to think at all, it’s very convenient. And the fee goes only during the calculation.







However, some may not pay at all. These are the developers of Alice's external skills , that is, chatbots built into her. Any developer can write, host and register such a skill, and from today the skills do not even need to be hosted - just upload their code to the cloud in the form of the very serverless function .







But there are a couple of nuances. Firstly, your pet code may require some dependencies, and pulling them into the Cloud is not trivial. Secondly, any normal chatbot needs to store the state of the dialogue somewhere (stateful therefore); how to do this in a serverless function the easiest? Thirdly, how can you quickly and dirty write a skill for Alice, or even some bot with a non-zero plot? About these nuances, in fact, an article.







image

Moral training



For the impatient: I collect the necessary dependencies with a make-file before uploading the function to the cloud, I store the state of the dialogue in Yandex Object Storage (it supports the S3 API), and to manage the dialogue I use my own tgalice library. The result is such a demo skill. And now we will analyze all this in a little more detail.







For a little less impatient: before diving into my story about difficulties, I highly recommend starting with a simple one. Namely, create a single-file echo sounder function, register and test it in the Dialogs developer console; details are on the Cloud Dialogs and documentation blog . And only then to proceed.







If you want to immediately feel what we are fighting for, ask Alice to turn on the skill "IT horoscope. " The following dialog should happen:







Alice: Hi! You are in the skill of Aishish Horoscope. Say “Start” to find out what the stars promise you.

User: Start

A: Please give your name.

Yu: Vasisualiy

A: Now tell me the year of your birth. Only four numbers, nothing more.

Yu: second

A: Please try again. What is the year of your birth - four digits.

U: 2002

A: Great! Now name the month of your birth.

U: February

A: Great! Finally, tell me the date of your birth — only a number, just one or two digits.

U: 18

A: Thank you, Vasisuali! Now we know: you are 17 years old, and you are Aquarius. Here you are, of course, lucky! The stars tell you: , , .









The problem is that to maintain even such a simple dialogue, you need to remember the name and date of birth of the user, and in a serverless environment, this is not trivial. Store the context in RAM or a file on the disk will not work, because Yandex.Cloud can run the function on several virtual machines at the same time and switch between them arbitrarily. Have to use some kind of external storage. Object Storage was chosen as a rather inexpensive and uncomplicated storage directly in Yandex.Cloud (i.e., probably fast). As a free alternative, you can try, for example, a free piece of cloudy Monga somewhere far away. Both Object Storage (it supports the S3 interface) and Mongo have convenient Python wrappers.







Another problem is that for walking in Object Storage, and in MongoDB, and in any other database or data warehouse, you need some external dependencies that you need to upload to Yandex Functions along with your function code. And I would like to do it conveniently. It’s quite convenient (like heroku), alas, it won’t work out, but you can create some basic comfort by writing a script to build the environment (make-file).







How to run a horoscope skill



  1. Prepare yourself: go to some Linux machine. In principle, you can probably work with Windows as well, but then you have to conjure up the launch of the make-file. And in any case, you will need installed Python no lower than 3.6.
  2. Clone yourself with github an example of a horoscope skill .
  3. Register in Y. Cloud: https://cloud.yandex.ru
  4. Create two buckets for yourself in Object Storage , name them with any name {BUCKET NAME}



    and tgalice-test-cold-storage



    (this second name is now hardcoded in main.py



    my example). The first bucket will be needed only for deployment, the second - for storing dialogue states.
  5. Create a service account , give it the role of editor



    , and get static credit cards {KEY ID}



    and {KEY VALUE}



    - we will use them to record the status of the dialogue. All this is necessary so that a function from Y. Cloud can access the repository from Y. Cloud. Someday, I hope authorization will become automatic, but for now - so.
  6. (Optional) install the yc



    command line interface . You can create a function through the web interface, but the CLI is good in that all sorts of innovations appear in it faster.
  7. Now you can, in fact, prepare the dependency assembly: run it on the command line from the folder with the make all



    skill example. A bunch of libraries (mostly, as usual, unnecessary) will be installed in the dist



    folder.
  8. Pour in Object Storage (into the {BUCKET NAME}



    ) the dist.zip



    archive obtained in the previous step. If desired, you can do this from the command line, for example, using the AWS CLI .
  9. Create a serverless function via the web interface or using the yc



    utility. For the utility, the command will look like this:


 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
      
      





When manually creating a function, all parameters are filled in the same way.







Now the function you created can be tested through the developer's console, and then modify and publish the skill.













What's under the hood



The makefile actually contains a fairly simple script for installing dependencies and putting them into the dist.zip



archive, something like this:







 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 ./*
      
      





The rest are a few simple tools wrapped in the tgalice



library. The process of filling in user data is described by the form.yaml 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: ,   .       (, );     .
      
      





The parsing class takes care of the analysis of this config and the calculation of the final result.







 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
      
      





More precisely, the base class FormFillingDialogManager



is responsible for filling out the "form", and the method of the child class handle_completed_form



says what to do when it is ready.







In addition to this main flow of the user’s dialogue, you also need to greet, and also give help on the “help” command and release from the skill on the “exit” command. tgalice



also has a template for this, so the whole dialog manager is made up of pieces:







 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



works simply: it tries to apply all its components to the current state of the dialogue in turn, and selects the first appropriate one.







As a response to each message, the dialog manager returns a Response



object, which can be further converted into bare text, or into a message in Alice or Telegram - depending on where the bot is launched; it also contains the changed state of the dialogue, which must be saved. Another class is engaged in this whole kitchen, DialogConnector



, so the direct script for launching the skill on Yandex Functions looks like this:







 ... 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
      
      





As you can see, most of this code creates a connection to the Object Storage S3 interface. How this connection is directly used can be read in the tgalice code .

The last line creates the alice_handler



function - the one we ordered Yandex.Cloud to pull when we set the --entrypoint=main.alice_handler



.







That, in fact, is all. Makefiles for the assembly, S3-like Object Storage for storing context, and the tgalice python library. Together with the serverless functions and expressiveness of the python, this is enough to develop the skill of a healthy person.







You may ask, why tgalice



you need to create tgalice



? All the boring code that transfers JSONs from request to response and from storage to memory and vice versa lies in it. There is also a regular controller application, a function for understanding what “February” is like “February”, and other NLUs for the poor. According to my idea, this should already be enough so that you can sketch out prototypes of skills in yaml files without being too distracted by technical details.







If you want a more serious NLU, you can screw Rasa or DeepPavlov to your skill, but to configure them, you will need additional dances with a tambourine, especially on serverless. If you don’t feel like coding at all, you should use a visual constructor like Aimylogic . Creating tgalice, I was thinking of some kind of intermediate path. Let's see what happens.







Well, now join the chat of developers of their skills , read the documentation , and create wonderful skills !








All Articles