Criando uma habilidade com estado para Alice usando funções sem servidor de Yandex.Cloud e Python

Vamos começar com as novidades. Ontem Yandex.Cloud anunciou o lançamento de um serviço de computação sem servidor Funções da Nuvem Yandex. Isso significa: você apenas escreve o código do seu serviço (por exemplo, uma aplicação web ou um chatbot), e a própria Cloud cria e mantém as máquinas virtuais onde roda, e até as replica caso a carga aumente. Você não precisa pensar nada, é muito conveniente. E o pagamento é apenas pelo tempo de cálculo.

No entanto, alguns podem não pagar nada. Estes são os desenvolvedores Habilidades externas de Alice, ou seja, chatbots integrados a ele. Qualquer desenvolvedor pode escrever, hospedar e registrar tal habilidade, e a partir de hoje as habilidades nem precisam ser hospedadas - basta enviar seu código para a nuvem no formulário a mesma função sem servidor.

Mas existem algumas nuances. Em primeiro lugar, seu código pit pode exigir algumas dependências e arrastá-las para a nuvem não é trivial. Em segundo lugar, qualquer chatbot normal precisa armazenar o estado do diálogo em algum lugar (portanto, com estado); Qual é a maneira mais fácil de fazer isso em uma função sem servidor? Em terceiro lugar, como você pode escrever uma habilidade rápida e suja para Alice ou mesmo algum tipo de bot com enredo diferente de zero? O artigo é, na verdade, sobre essas nuances.

Criando uma habilidade com estado para Alice usando funções sem servidor de Yandex.Cloud e Python

Preparação moral

Para os impacientes: eu coleto as dependências necessárias com um makefile antes de enviar a função para a nuvem, armazeno o estado da caixa de diálogo no Yandex Object Storage (ele suporta a API S3) e uso minha própria biblioteca para gerenciar a caixa de diálogo tgalice. No final das contas acontece tal habilidade de demonstração. Agora vamos examinar tudo isso com um pouco mais de detalhes.

Para aqueles um pouco menos impacientes, antes de mergulhar na minha história sobre complexidade, recomendo fortemente começar com algo simples. Ou seja, crie uma função echobot de arquivo único, registre-a e teste-a no console do desenvolvedor do Dialogs; detalhes - em Blog de diálogos и Documentação da nuvem. E só então comece.

Se você quiser sentir imediatamente pelo que estamos lutando aqui, pergunte a Alice 'habilitar a habilidade “horóscopo de TI”'. Um diálogo semelhante ao seguinte deve ocorrer:

Alice: Olá! Você está na habilidade “Horóscopo de TI”. Diga “Iniciar” para descobrir o que as estrelas prometem.
Usuário: Iniciar
R: Por favor, indique seu nome
Yu: Vasisualiy
R: Agora me diga o ano do seu nascimento. Apenas quatro dígitos, nada extra.
Yu: segundo
R: Por favor, tente novamente. Digite o ano do seu nascimento – quatro dígitos.
Yu: 2002
R: Ótimo! Agora nomeie seu mês de nascimento.
Yu: fevereiro
R: Ótimo! Finalmente, diga-me a sua data de nascimento – apenas um número, apenas um ou dois dígitos.
Yu: 18
R: Obrigado, Vasisualiy! Agora sabemos: você tem 17 anos e é aquário. Bem, claro, você tem sorte! As estrelas lhe dizem: а что говорят звёзды, вы узнаете, запустив навык.

O problema é que, para manter um diálogo tão simples, você precisa lembrar o nome e a data de nascimento do usuário e, em um ambiente de função sem servidor, isso não é trivial. Não será possível armazenar o contexto na RAM ou como arquivo no disco, pois Yandex.Cloud pode executar uma função em várias máquinas virtuais ao mesmo tempo e alternar entre elas à vontade. Você terá que usar algum tipo de armazenamento externo. O Object Storage foi escolhido como um armazenamento bastante barato e descomplicado diretamente no Yandex.Cloud (ou seja, provavelmente rápido). Como alternativa gratuita, você pode experimentar, por exemplo, uma peça grátis Monga nublado em algum lugar distante. Existem wrappers Python convenientes para Object Storage (que suporta a interface S3) e Mongo.

Outro problema é que para acessar Object Storage, MongoDB e qualquer outro banco de dados ou armazenamento de dados, você precisa de algumas dependências externas que precisam ser carregadas no Yandex Functions junto com seu código de função. E eu gostaria de fazer isso convenientemente. Infelizmente, não será totalmente conveniente (como no Heroku), mas algum conforto básico pode ser criado escrevendo um script para construir o ambiente (criar arquivo).

Como lançar uma habilidade de horóscopo

  1. Prepare-se: vá até alguma máquina com Linux. Em princípio, você provavelmente também pode trabalhar com o Windows, mas então terá que fazer mágica ao iniciar o makefile. E em qualquer caso, você precisará de pelo menos o Python 3.6 instalado.
  2. Clone-o do Github exemplo de habilidade horóscopo.
  3. Registre-se no Y.Cloud: https://cloud.yandex.ru
  4. Crie dois baldes em Armazenamento de objetos, chame-os por qualquer nome {BUCKET NAME} и tgalice-test-cold-storage (este segundo nome agora está codificado em main.py meu exemplo). O primeiro bucket será necessário apenas para implantação, o segundo - para armazenar estados de diálogo.
  5. criar conta de serviço, dê a ele um papel editore obtenha credenciais estáticas para ele {KEY ID} и {KEY VALUE} — vamos usá-los para registrar o estado do diálogo. Tudo isso é necessário para que uma função do Ya.Cloud possa acessar o armazenamento do Ya.Cloud. Algum dia, espero, a autorização se tornará automática, mas por enquanto é assim.
  6. (Opcional) instalar interface da Linha de comando yc. Você também pode criar uma função por meio da interface web, mas a CLI é boa porque todos os tipos de inovações aparecem nela com mais rapidez.
  7. Agora você pode realmente preparar o assembly de dependência: execute-o na linha de comando da pasta com o exemplo de habilidade make all. Um monte de bibliotecas (a maioria, como sempre, desnecessárias) serão instaladas na pasta dist.
  8. Despeje manualmente no Object Storage (no balde {BUCKET NAME}) arquivo obtido na etapa anterior dist.zip. Se desejar, você pode fazer isso na linha de comando, por exemplo, usando CLI da AWS.
  9. Crie uma função sem servidor por meio da interface da web ou usando um utilitário yc. Para o utilitário, o comando ficará assim:

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

Ao criar uma função manualmente, todos os parâmetros são preenchidos da mesma forma.

Agora a função que você criou pode ser testada por meio do console do desenvolvedor e então a habilidade pode ser aprimorada e publicada.

Criando uma habilidade com estado para Alice usando funções sem servidor de Yandex.Cloud e Python

O que está sob o capô

O makefile na verdade contém um script bastante simples para instalar dependências e colocá-las em um arquivo dist.zip, aproximadamente assim:

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

O resto são algumas ferramentas simples embaladas em uma biblioteca tgalice. O processo de preenchimento dos dados do usuário é descrito pela configuração 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: Пожалуйста, попробуйте ещё раз. Вам нужно назвать число своего рождения (например, двадцатое); это одна или две цифры.

O trabalho de analisar esta configuração e calcular o resultado final é assumido pela classe Python

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

Mais precisamente, a classe base FormFillingDialogManager trata do preenchimento do “formulário” e do método da classe filho handle_completed_form diz a ela o que fazer quando ela estiver pronta.

Além desse fluxo principal de diálogo, o usuário também deve ser cumprimentado, bem como receber ajuda através do comando “help” e liberado da habilidade através do comando “exit”. Para este efeito em tgalice Também existe um modelo, portanto todo o gerenciador de diálogo é composto de peças:

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 Funciona de forma simples: tenta aplicar todos os seus componentes ao estado atual do diálogo e seleciona o primeiro apropriado.

O gerenciador de diálogo retorna um objeto Python como resposta a cada mensagem. Response, que pode então ser convertido em texto simples ou em mensagem no Alice ou Telegram - dependendo de onde o bot está rodando; também contém o estado alterado do diálogo que precisa ser salvo. Toda essa cozinha é administrada por outra turma, DialogConnector, então o script direto para iniciar uma habilidade no Yandex Functions é assim:

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

Como você pode ver, a maior parte desse código cria uma conexão com a interface S3 do Object Storage. Você pode ler como esta conexão é usada diretamente em código tgalice.
A última linha cria a função alice_handler - o mesmo que dissemos ao Yandex.Cloud para extrair quando definimos o parâmetro --entrypoint=main.alice_handler.

Isso é tudo, na verdade. Makefiles para montagem, armazenamento de objetos semelhante ao S3 para armazenar contexto e uma biblioteca Python tgalice. Combinado com as funções sem servidor e a expressividade do Python, isso é suficiente para desenvolver uma habilidade humana saudável.

Você pode perguntar por que foi necessário criar tgalice? Todo o código enfadonho que transfere JSONs da solicitação para a resposta e do armazenamento para a memória e vice-versa está nele. Existe também uma aplicação de código regular, uma função para entender que “fevereiro” é semelhante a “fevereiro” e outras NLU para os pobres. Na minha ideia, isso já deve ser suficiente para que você possa esboçar protótipos de habilidades em arquivos yaml sem se distrair muito com detalhes técnicos.

Se você quiser uma NLU mais séria, poderá anexá-la à sua habilidade Rasa ou Deep Pavlov, mas configurá-los exigirá danças adicionais com pandeiro, especialmente em servidores sem servidor. Se você não quiser programar, você deve usar um construtor visual como Aimilógico. Ao criar o tgalice, pensei em algum tipo de caminho intermediário. Vamos ver o que acontece com isso.

Bem, agora junte-se bate-papo do desenvolvedor de habilidades de Alice, leitura documentaçãoe criar maravilhoso habilidades!

Fonte: habr.com

Adicionar um comentário