Erstellung eines stateful Skills für Alice auf serverlosen Funktionen von Yandex.Cloud und Python

Lass uns mit den Nachrichten beginnen. Gestern hat Yandex.Cloud den Start des Dienstes für serverlose Berechnungen angekündigt Yandex Cloud Functions. Das bedeutet: Du schreibst nur den Code deines Dienstes (zum Beispiel eine Webanwendung oder einen Chatbot), und die Cloud erstellt und verwaltet selbst die virtuellen Maschinen, auf denen er läuft, und repliziert sie sogar, wenn die Last steigt. Man muss überhaupt nicht nachdenken, sehr praktisch. Und du zahlst nur für die Berechnungszeit.

Allerdings kann es einige geben, die gar nichts zahlen müssen. Das sind die Entwickler externer Alice-Skills, also der in sie integrierten Chatbots. Einen solchen Skill zu schreiben, zu hosten und zu registrieren kann jeder Entwickler, und ab heute brauchst du noch nicht einmal mehr zu hosten – es reicht, den Code in die Cloud als die besagte serverlose Funktion.

hochzuladen. Doch es gibt ein paar Feinheiten. Erstens kann dein Python-Code einige Abhängigkeiten benötigen, und sie in die Cloud zu bringen, ist nicht trivial. Zweitens benötigt jeder normale Chatbot irgendwo einen Speicher für den Dialogzustand (stateful deshalb); wie kann man das in einer serverlosen Funktion am einfachsten umsetzen? Drittens, wie kann man schnell und einfach einen Skill für Alice oder irgendeinen Bot mit einer nicht trivialen Handlung schreiben? Um diese Feinheiten geht es in diesem Artikel.

Erstellung eines stateful Skills für Alice auf serverlosen Funktionen von Yandex.Cloud und Python

Moralische Vorbereitung

Für die Ungeduldigen: Die notwendigen Abhängigkeiten stelle ich mit einer Make-Datei bereit, bevor ich die Funktion in die Cloud lade, den Dialogzustand speichere ich im Yandex Object Storage (der API S3 unterstützt), und zur Verwaltung des Dialogs verwende ich meine eigene Bibliothek tgalice. Am Ende ergibt sich ein solches Demo-Skill. Nun lass uns das alles etwas detaillierter betrachten.

Für die etwas weniger Ungeduldigen: Bevor du in meine Geschichte über die Schwierigkeiten eintauchst, empfehle ich dringend, mit etwas Einfachem zu beginnen. Nämlich, eine Ein-Datei-Echo-Funktion zu erstellen, sie zu registrieren und in der Entwicklerkonsole für Dialoge zu testen; Details findest du im Dialogblog und der Cloud-Dokumentation. Und erst dann weitermachen.

Wenn du sofort erleben möchtest, wofür wir hier kämpfen, bitte Alice, ‘aktiviere den Skill ‚IT-Horoskop‘‘. Es sollte ungefähr der folgende Dialog passieren:

Alice: Hallo! Du befindest dich im Skill ‚IT-Horoskop‘. Sage ‚Start‘, um zu erfahren, was die Sterne für dich bereithalten.
Nutzer: Start
A: Bitte nenne deinen Namen
N: Wasisualiy
A: Jetzt sagen Sie mir bitte, in welchem Jahr Sie geboren wurden. Nur vier Ziffern, nichts weiter.
J: zwei
A: Bitte versuchen Sie es noch einmal. Nennen Sie das Jahr Ihrer Geburt – vier Ziffern.
J: 2002
A: Wunderbar! Nennen Sie jetzt den Monat Ihrer Geburt.
J: Februar
A: Ausgezeichnet! Nennen Sie mir schließlich das Datum Ihrer Geburt – nur die Zahl, insgesamt ein oder zwei Ziffern.
J: 18
A: Danke, Vasisuali! Jetzt wissen wir: Sie sind 17 Jahre alt und Wassermann. Damit haben Sie natürlich Glück! Die Sterne sagen Ihnen: was die Sterne sagen, erfahren Sie, indem Sie das Skill starten.

Das Problem ist, dass man selbst für so einen einfachen Dialog den Namen und das Geburtsdatum des Benutzers im Gedächtnis behalten muss, und in einer serverlosen Umgebung ist das nicht trivial. Den Kontext im Arbeitsspeicher oder in einer Datei auf der Festplatte zu speichern, ist nicht möglich, da Yandex.Cloud die Funktion auf mehreren virtuellen Maschinen gleichzeitig starten und willkürlich zwischen ihnen wechseln kann. Man muss ein externes Speichersystem verwenden. Object Storage wurde gewählt, da es ein recht kostengünstiger und einfacher Speicher direkt in Yandex.Cloud ist (also wahrscheinlich schnell). Als kostenlose Alternative kann man beispielsweise ein kostenloses Stück Cloud-Mongo irgendwo weit weg versuchen. Sowohl für Object Storage (es unterstützt das S3-Interface) als auch für Mongo existieren praktische Python-Wrappers.

Ein weiteres Problem ist, dass man für den Zugriff auf Object Storage, MongoDB oder jede andere Datenbank oder Datenspeicher bestimmte externe Abhängigkeiten benötigt, die man zusammen mit dem Code seiner Funktion auf Yandex Functions hochladen muss. Und man würde das gern bequem machen. Ganz bequem (wie bei Heroku) wird es leider nicht möglich sein, aber einen grundlegenden Komfort kann man schaffen, indem man ein Skript zur Erstellung der Umgebung (Make-Datei) schreibt.

So starten Sie das Horoskop-Skill

  1. Vorbereitung: Melden Sie sich auf einem beliebigen Linux-Rechner an. Grundsätzlich kann man wahrscheinlich auch unter Windows arbeiten, aber beim Starten einer Make-Datei wird es dann einige Tricks erfordern. Und auf jeden Fall benötigen Sie eine installierte Python-Version von 3.6 oder höher.
  2. Klonen Sie sich von GitHub ein Beispiel für das Horoskop-Skill.
  3. Registrieren Sie sich bei Y. Cloud: https://cloud.yandex.ru
  4. Erstellen Sie zwei Buckets in Object Storage, benennen Sie sie nach Belieben {BUCKET NAME} und tgalice-test-cold-storage (dieser zweite Name ist derzeit im main.py meinem Beispiel fest verdrahtet). Der erste Bucket wird nur für das Deployment benötigt, der zweite – zum Speichern der Dialogzustände.
  5. Erstellen Servicekonto, ihm die Rolle zu geben Editor, und statische Anmeldeinformationen zu erhalten {KEY ID} und {KEY VALUE} — diese werden wir verwenden, um den Zustand des Dialogs zu speichern. All dies ist notwendig, damit die Funktion aus Я.Облака auf den Speicher aus Я.Облака zugreifen kann. Irgendwann, hoffe ich, wird die Authentifizierung automatisiert, aber bis dahin - so.
  6. (Optional) installieren Befehlszeileninterface yc. Eine Funktion kann auch über die Weboberfläche erstellt werden, aber das CLI hat den Vorteil, dass neue Funktionen dort schneller verfügbar sind.
  7. Jetzt können wir das Abhängigkeitsbündel vorbereiten: Führen Sie aus dem Ordner mit dem Beispielschnittstelle in der Befehlszeile aus make all. Eine Menge Bibliotheken (hauptsächlich, wie üblich, unnötige) wird in den Ordner dist.
  8. Manuell in das Object Storage hochladen (in den Bucket {BUCKET NAME}) das Archiv, das im vorherigen Schritt erstellt wurde dist.zip. Bei Wunsch kann dies auch über die Befehlszeile erfolgen, zum Beispiel unter Verwendung von AWS CLI.
  9. Eine serverlose Funktion über die Weboberfläche oder unter Verwendung des Dienstprogramms erstellen yc. Der Befehl für das Dienstprogramm würde so aussehen:

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

Bei der manuellen Erstellung der Funktion werden alle Parameter ähnlich ausgefüllt.

Jetzt kann die von Ihnen erstellte Funktion über die Entwicklerkonsole getestet und dann verbessert und veröffentlicht werden.

Erstellung eines stateful Skills für Alice auf serverlosen Funktionen von Yandex.Cloud und Python

Was unter der Haube steckt

Die Make-Datei enthält tatsächlich ein ziemlich einfaches Skript zur Installation von Abhängigkeiten und deren Verpackung in ein Archiv dist.zip, ungefähr so:

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

Der Rest sind einige einfache Werkzeuge, zusammengefasst in einer Bibliothek tgalice. Der Prozess der Dateneingabe über den Benutzer wird durch die Konfiguration beschrieben form.yaml:

form_name: 'horoskop_form'
start:
  regexp: 'start|beginnen'
  suggests:
    - Start
fields:
  - name: 'name'
    question: Bitte nenne deinen Namen.
  - name: 'year'
    question: Jetzt nenne mir bitte dein Geburtsjahr. Nur vier Ziffern, nichts weiter.
    validate_regexp: '^[0-9]{4}$'
    validate_message: Bitte versuche es erneut. Nenne das Jahr deiner Geburt - vier Ziffern.
  - name: 'month'
    question: Großartig! Jetzt nenne den Monat deiner Geburt.
    options:
      - Januar
      ...
      - Dezember
    validate_message: Das, was du gesagt hast, klingt nicht nach einem Monat. Bitte nenne den Monat deiner Geburt, ohne weitere Worte.
  - name: 'day'
    question: Ausgezeichnet! Nenne mir schließlich das Datum deiner Geburt - nur die Zahl, insgesamt ein oder zwei Ziffern.
    validate_regexp: '[0123]?d$'
    validate_message: Bitte versuche es erneut. Du musst die Zahl deiner Geburt nennen (zum Beispiel den zwanzigsten); es sind ein oder zwei Ziffern.

Die Arbeit zur Analyse dieser Konfiguration und zur Berechnung des endgültigen Ergebnisses übernimmt die Python-Klasse

class CheckableFormFiller(tgalice.dialog_manager.form_filling.FormFillingDialogManager):
    SIGNS = {
        'Januar': 'Steinbock',
        ...
    }

    def handle_completed_form(self, form, user_object, ctx):
        response = tgalice.dialog_manager.base.Response(
            text='Danke, {}! Jetzt wissen wir: Du bist {} Jahre alt, und du bist {}. n'
                 'Das klingt wirklich nach Glück! Die Sterne sagen dir: {}'.format(
                form['fields']['name'],
                2019 - int(form['fields']['year']),
                self.SIGNS[form['fields']['month']],
                random.choice(FORECASTS),
            ),
            user_object=user_object,
        )
        return response

Genauer gesagt, die Basisklasse FormFillingDialogManager beschäftigt sich mit dem Ausfüllen des «Formulars», während die Methode der Unterklasse handle_completed_form festlegt, was zu tun ist, wenn es fertig ist.

Darüber hinaus muss der Hauptdialog des Benutzers auch begrüßt werden, und eine Hilfe auf den Befehl «hilfe» ausgegeben werden, sowie beim Befehl «ausgang» aus dem Skill ausgestiegen werden. Zu diesem Zweck gibt es auch eine Vorlage, sodass der komplette Dialogmanager aus Teilen zusammengesetzt ist: tgalice dm = tgalice.dialog_manager.CascadeDialogManager( tgalice.dialog_manager.GreetAndHelpDialogManager( greeting_message=DEFAULT_MESSAGE, help_message=DEFAULT_MESSAGE, exit_message='Auf Wiedersehen, komm doch wieder zum Skill "IT-Horoskop"!' ), CheckableFormFiller(`form.yaml`, default_message=DEFAULT_MESSAGE) )

CascadeDialogManager

arbeitet einfach: Er versucht, nacheinander alle seine Bestandteile auf den aktuellen Dialogstatus anzuwenden und wählt die erste geeignete aus. Als Antwort auf jede Nachricht gibt der Dialogmanager ein Python-Objekt zurück.

Als Antwort auf jede Nachricht gibt der Dialogmanager ein Python-Objekt zurück. Response, das dann in reinen Text oder in eine Nachricht in Alice oder Telegram konvertiert werden kann – je nachdem, wo der Bot läuft; es enthält auch den geänderten Zustand des Dialogs, der gespeichert werden muss. Dies wird von einer weiteren Klasse erledigt, DialogConnector, weshalb das direkt ausführbare Skript zum Starten des Skills auf Yandex Functions so aussieht:

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

Wie Sie sehen, stellt der Großteil dieses Codes eine Verbindung zum S3-Interface für Object Storage her. Wie diese Verbindung genau verwendet wird, können Sie im Code von tgalice.
Die letzte Zeile erstellt die Funktion alice_handler – genau die, die wir Yandex.Cloud aufgerufen haben, als wir den Parameter --entrypoint=main.alice_handler.

Das ist es auch schon. Makefiles für den Aufbau, S3-ähnliches Object Storage zur Speicherung des Kontexts und eine Python-Bibliothek. tgaliceIn Kombination mit serverlosen Funktionen und der Ausdrucksstärke von Python ist das ausreichend für die Entwicklung eines Skills für gesunde Menschen.

Sie könnten fragen, warum es notwendig war, das zu erstellen tgalice? Весь скучный код, перекладывающий JSON’ы из запроса в ответ и из хранилища в память и обратно, лежит в ней. Там же лежит применялка регулярок, функция для понимания того, что «феврарь» похоже на «февраль», и прочее NLU для бедных. По моей задумке, этого уже должно быть достаточно, чтобы можно было набрасывать прототипы навыков в yaml-файлах, не слишком отвлекаясь на технические детали.

Wenn Sie ernsthaftere NLU wollen, können Sie Ihrem Skill Rasa oder DeepPavlov, aber für deren Einrichtung sind zusätzliche Anstrengungen erforderlich, besonders bei serverlosen Funktionen. Wenn Sie auf keinen Fall programmieren möchten, sollten Sie einen visuellen Builder wie Aimylogicverwenden. Bei der Erstellung von tgalice dachte ich an einen gewissen Mittelweg. Mal sehen, was daraus wird.

Und heute treten Sie der Entwickler-Community für Alice-Skillsbei, lesen Sie Dokumentation, und erstellen Sie großartige Skills!

Quelle: habr.com

60GB SSD 8Gb DDR4