Criação de uma habilidade stateful para a Alice em funções serverless na Yandex.Cloud e Python

Vamos começar com as notícias. Ontem, a Yandex.Cloud anunciou o lançamento do serviço de computação serverless Yandex Cloud Functions. Isso significa: você escreve apenas o código do seu serviço (por exemplo, uma aplicação web ou um chatbot), e a Cloud cria e mantém as máquinas virtuais onde ele é executado, até mesmo replicando-as se a carga aumentar. Não é necessário pensar em nada, é muito conveniente. E a cobrança é apenas pelo tempo de computação.

No entanto, alguns podem não pagar nada. Esses são os desenvolvedores de habilidades externas da Alice, ou seja, chatbots integrados. Qualquer desenvolvedor pode escrever, hospedar e registrar tal habilidade, e a partir de hoje, não é mais necessário hospedar as habilidades — basta carregar o código na nuvem na forma de aquela mesma função serverless.

Mas há algumas nuances. Primeiro, seu código em Python pode exigir algumas dependências, e trazê-las para a Cloud não é trivial. Em segundo lugar, qualquer chatbot normal precisa armazenar o estado do diálogo em algum lugar (por isso é stateful); como fazer isso da forma mais simples em uma função serverless? Em terceiro lugar, como é possível escrever rapidamente uma habilidade para a Alice ou para qualquer bot com uma história não nula? Sobre essas nuances, na verdade, trata-se do artigo.

Criação de uma habilidade stateful para a Alice em funções serverless na Yandex.Cloud e Python

Preparação moral

Para os impacientes: eu reúno as dependências necessárias com um Makefile antes de carregar a função na nuvem, armazeno o estado do diálogo no Object Storage da Yandex (que suporta a API S3) e uso minha própria biblioteca tgalice. No final, obtemos uma habilidade demo. Agora, vamos analisar tudo isso com um pouco mais de detalhes.

Para os menos impacientes: antes de mergulhar na minha história sobre as dificuldades, eu recomendo começar pelo básico. Ou seja, criar uma função de echo de um arquivo, registrar e testá-la no console do desenvolvedor de Diálogos; detalhes estão no blog de Diálogos e documentação da Cloud. E só depois começar.

Se você quer imediatamente sentir pelo que estamos lutando, peça à Alice ‘ative a habilidade ‘horóscopo de TI’‘. Deve ocorrer aproximadamente o seguinte diálogo:

Alice: Olá! Você está na habilidade ‘Horóscopo de TI’. Diga ‘Iniciar’ para descobrir o que as estrelas reservam para você.
Usuário: Iniciar
A: Por favor, diga seu nome
U: Vasisiualiy
A: Agora, diga-me o ano do seu nascimento. Apenas quatro dígitos, nada mais.
U: segundo
A: Por favor, tente novamente. Diga o ano do seu nascimento — quatro dígitos.
U: 2002
A: Maravilhoso! Agora, diga-me o mês do seu nascimento.
U: Fevereiro
A: Ótimo! Finalmente, diga-me a data do seu nascimento — apenas o número, de uma ou duas dígitos.
U: 18
A: Obrigado, Vasisuály! Agora sabemos: você tem 17 anos e é Aquário. Você realmente teve sorte! As estrelas dizem para você: o que as estrelas dizem, você descobrirá ao ativar a habilidade.

O problema é que, para manter mesmo um diálogo tão simples, é necessário lembrar o nome e a data de nascimento do usuário, e em um ambiente de funções sem servidor, isso não é trivial. Não é possível armazenar o contexto na memória ou em um arquivo no disco, pois o Yandex.Cloud pode executar a função em várias máquinas virtuais simultaneamente e alternar entre elas de forma arbitrária. Será necessário usar algum armazenamento externo. O Object Storage foi escolhido, como um armazenamento bastante barato e simples diretamente no Yandex.Cloud (ou seja, provavelmente rápido). Como uma alternativa gratuita, você pode tentar, por exemplo, um pedaço grátis da MongoCloud longe. E para o Object Storage (que suporta a interface S3) e para o Mongo existem convenientemente wrappers em Python.

Outro problema é que, para navegar tanto no Object Storage quanto no MongoDB, e em qualquer outro banco de dados ou armazenamento de dados, são necessárias algumas dependências externas que precisam ser enviadas para o Yandex Functions junto com o código da sua função. E gostaríamos de fazer isso de forma conveniente. Totalmente conveniente (como no Heroku), infelizmente, não será possível, mas algum conforto básico pode ser criado escrevendo um script para montar o ambiente (um arquivo make).

Como ativar a habilidade de horóscopo

  1. Preparação: acesse alguma máquina com Linux. Em princípio, também pode ser feito com Windows, mas ao executar o arquivo make, será necessário um pouco de mágica. E, de qualquer forma, você precisará ter o Python instalado, versão 3.6 ou superior.
  2. Clone para si mesmo do GitHub um exemplo de habilidade de horóscopo.
  3. Cadastre-se no Y. Cloud: https://cloud.yandex.ru
  4. Crie para si dois buckets em Object Storage, nomeie-os com qualquer nome {BUCKET NAME} e tgalice-test-cold-storage (este segundo nome está atualmente codificado em main.py do meu exemplo). O primeiro bucket será necessário apenas para o deployment, o segundo — para armazenar os estados do diálogo.
  5. Criar conta de serviço, conceder a ela o papel editor, e obter as credenciais estáticas {KEY ID} e {KEY VALUE} — usaremos isso para registrar o estado do diálogo. Tudo isso é necessário para que a função do Yandex.Cloud possa acessar o armazenamento do Yandex.Cloud. Um dia, espero, a autenticação se tornará automática, mas por enquanto — assim.
  6. (Opcional) instalar interface de linha de comando yc. A função também pode ser criada através da interface da web, mas o CLI é bom porque novidades aparecem nele mais rápido.
  7. Agora podemos, na verdade, preparar a montagem das dependências: executar na linha de comando a partir da pasta com o exemplo da habilidade make all. Uma série de bibliotecas (na maioria, como de costume, desnecessárias) será instalada na pasta dist.
  8. Carregue manualmente no Object Storage (no bucket {BUCKET NAME}) o arquivo gerado na etapa anterior dist.zip. Se desejado, isso também pode ser feito a partir da linha de comando, por exemplo, usando AWS CLI.
  9. Criar uma função serverless através da interface da web ou usando a ferramenta yc. Para a ferramenta, o comando será 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 a função manualmente, todos os parâmetros são preenchidos de forma semelhante.

Agora, a função que você criou pode ser testada através do console do desenvolvedor, e depois você pode aprimorar e publicar a habilidade.

Criação de uma habilidade stateful para a Alice em funções serverless na Yandex.Cloud e Python

O que está por trás

O arquivo Make contém na verdade um script bastante simples para instalar dependências e empacotar tudo em um arquivo zip 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 restante é um conjunto de ferramentas simples, empacotadas em uma biblioteca tgalice. O processo de preenchimento dos dados do usuário é descrito na configuração form.yaml:

form_name: 'horoscope_form'
start:
  regexp: 'iniciar|começar'
  suggests:
    - Iniciar
fields:
  - name: 'name'
    question: Por favor, diga o seu nome.
  - name: 'year'
    question: Agora, diga-me o ano do seu nascimento. Apenas quatro dígitos, nada mais.
    validate_regexp: '^[0-9]{4}$'
    validate_message: Por favor, tente novamente. Diga o ano do seu nascimento - quatro dígitos.
  - name: 'month'
    question: Maravilha! Agora, diga o mês do seu nascimento.
    options:
      - janeiro
      ...
      - dezembro
    validate_message: O que você mencionou não parece um mês. Por favor, diga o mês do seu nascimento, sem outras palavras.
  - name: 'day'
    question: Ótimo! Finalmente, diga-me a data do seu nascimento - apenas o número, um ou dois dígitos.
    validate_regexp: '[0123]?d$'
    validate_message: Por favor, tente novamente. Você precisa dizer o número do seu nascimento (por exemplo, vigésimo); são um ou dois dígitos.

O trabalho de analisar essa configuração e calcular o resultado final é feito pela classe Python.

class CheckableFormFiller(tgalice.dialog_manager.form_filling.FormFillingDialogManager):
    SIGNS = {
        'janeiro': 'Capricórnio',
        ...
    }

    def handle_completed_form(self, form, user_object, ctx):
        response = tgalice.dialog_manager.base.Response(
            text='Obrigado, {}! Agora sabemos: você tem {} anos, e você {}. n'
                 'Você realmente teve sorte! As estrelas dizem a você: {}'.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 é responsável pelo preenchimento da "formulário", enquanto o método da classe derivada handle_completed_form diz o que fazer quando está pronto.

Além desse fluxo principal do diálogo, o usuário também precisa ser saudado, e uma ajuda deve ser fornecida no comando "ajuda" e sair da habilidade com o comando "sair". Para isso, em tgalice também há um modelo, então o gerenciador de diálogo completo é composto por partes:

dm = tgalice.dialog_manager.CascadeDialogManager(
    tgalice.dialog_manager.GreetAndHelpDialogManager(
        greeting_message=DEFAULT_MESSAGE,
        help_message=DEFAULT_MESSAGE,
        exit_message='Adeus, volte ao habilidade "Horóscopo da TI" novamente!'
    ),
    CheckableFormFiller(`form.yaml`, default_message=DEFAULT_MESSAGE)
)

CascadeDialogManager funciona simplesmente: tenta aplicar à condição atual do diálogo todas as suas partes em ordem e escolhe a primeira apropriada.

Como resposta a cada mensagem, o gerenciador de diálogo retorna um objeto Python. Response, que pode ser convertido em texto puro, ou em uma 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. Todo esse trabalho é gerenciado por outra classe, DialogConnector, portanto, o script que inicia a habilidade no Yandex Functions fica 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 deste código cria uma conexão com a interface S3 de Object Storage. Como essa conexão é usada diretamente pode ser lido no código tgalice.
A última linha cria a função alice_handler — aquela que pedimos ao Yandex.Cloud para acionar quando definimos o parâmetro --entrypoint=main.alice_handler.

E é isso. Makefiles para compilação, armazenamento de objetos tipo S3 para guardar o contexto, e uma biblioteca em Python tgalice. Junto com funções serverless e a expressividade do Python, isso é suficiente para desenvolver uma habilidade de qualidade.

Você pode se perguntar por que foi necessário criar tgalice? Весь скучный код, перекладывающий JSON’ы из запроса в ответ и из хранилища в память и обратно, лежит в ней. Там же лежит применялка регулярок, функция для понимания того, что «феврарь» похоже на «февраль», и прочее NLU для бедных. По моей задумке, этого уже должно быть достаточно, чтобы можно было набрасывать прототипы навыков в yaml-файлах, не слишком отвлекаясь на технические детали.

Se você deseja um NLU mais avançado, pode integrar ao seu projeto Rasa ou DeepPavlov, mas para configurá-los serão necessárias algumas manobras complicadas, especialmente em serverless. Se você realmente não quer codificar, vale a pena usar um construtor visual como Aimylogic. Ao criar tgalice, pensei em algum caminho intermediário. Vamos ver o que resulta disso.

Bem, atualmente entre no chat dos desenvolvedores de habilidades de Alice, leia documentação, e crie habilidades incríveis habilidades!

Fonte: habr.com

Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS 🔥 Compre hospedagem confiável para sites com proteção contra DDoS, servidores VPS VDS | ProHoster