Tworzenie stanu zachowującego umiejętności dla Alisy na funkcjach serverless Yandex.Cloud i Pythonie

Zacznijmy od wiadomości. Wczoraj Yandex.Cloud ogłosił uruchomienie usługi obliczeń bezserwerowych Yandex Cloud Functions. Oznacza to, że piszesz tylko kod swojego serwisu (na przykład, aplikacji internetowej lub chatbota), a Chmura sama tworzy i obsługuje maszyny wirtualne, na których działa, a nawet replikuje je, gdy wzrasta obciążenie. Nie musisz w ogóle myśleć, to bardzo wygodne. A opłata jest naliczana tylko za czas obliczeń.

Jednak niektórzy mogą w ogóle nie płacić. To są deweloperzy zewnętrznych umiejętności Alisy, czyli wbudowanych w nią chatbotów. Każdy deweloper może napisać, hostować i zarejestrować taką umiejętność, a od dziś nie trzeba ich nawet hostować – wystarczy wrzucić ich kod do chmury w postaci tego właśnie funkcji bezserwerowej.

Ale jest kilka niuansów. Po pierwsze, twój kod w Pythonie może wymagać pewnych zależności, a wciągnięcie ich do Chmury nie jest trywialne. Po drugie, każdy normalny chatbot potrzebuje gdzieś przechowywać stan dialogu (dlatego jest stateful); jak najłatwiej to zrobić w funkcji bezserwerowej? Po trzecie, jak w ogóle można szybko i brudnie napisać umiejętność dla Alisy lub jakiegokolwiek bota z niezerową fabułą? O tych niuansach jest właściwie artykuł.

Tworzenie stanu zachowującego umiejętności dla Alisy na funkcjach serverless Yandex.Cloud i Pythonie

Przygotowanie moralne

Dla niecierpliwych: potrzebne zależności zbieram plikiem make przed załadowaniem funkcji do chmury, stan dialogu przechowuję w Yandex Object Storage (obsługuje API S3), a do zarządzania dialogiem używam własnej biblioteki tgalice. W rezultacie mamy taki demo-umiejętność. A teraz omówmy to wszystko trochę dokładniej.

Dla tych, którzy są mniej niecierpliwi: zanim zanurzymy się w mojej historii o trudnościach, bardzo polecam zacząć od prostego. A mianowicie, stworzyć jednoplikiem funkcję echo-bota, zarejestrować ją i przetestować w konsoli dewelopera Dialogów; szczegóły – w blogu Dialogów i dokumentacji Chmury. A dopiero potem zaczynać.

Jeśli chcesz od razu poczuć, o co tu chodzi, poproś Alisę ‘włącz umiejętność „techno horoskop”‘. Powinien nastąpić mniej więcej taki dialog:

Alisa: Cześć! Jesteś w umiejętności „Techno horoskop”. Powiedz „Start”, aby dowiedzieć się, co przewidują ci gwiazdy.
Użytkownik: Start
A: Proszę podaj swoje imię
U: Wasilij
A: Teraz powiedz mi rok swojego urodzenia. Tylko cztery cyfry, nic więcej.
U: dwa
A: Proszę spróbować ponownie. Podaj rok swojego urodzenia — cztery cyfry.
J: 2002
A: Wspaniale! Teraz podaj miesiąc swojego urodzenia.
J: Luty
A: Doskonale! Na koniec podaj mi datę swojego urodzenia — tylko dzień, jedna lub dwie cyfry.
J: 18
A: Dziękuję, Wasisuali! Teraz wiemy: masz 17 lat i jesteś Wodnikiem. To ci się oczywiście udało! Gwiazdy mówią ci: a co mówią gwiazdy, dowiesz się, uruchamiając umiejętność.

Problem w tym, że aby prowadzić nawet tak prosty dialog, trzeba pamiętać imię i datę urodzenia użytkownika, a w środowisku funkcji bezserwerowych nie jest to trywialne. Nie da się przechować kontekstu w pamięci operacyjnej ani jako pliku na dysku, ponieważ Yandex.Cloud może uruchomić funkcję na kilku maszynach wirtualnych jednocześnie i swobodnie przełączać się między nimi. Będziesz musiał skorzystać z jakiegoś zewnętrznego magazynu. Wybrano Object Storage, który jest stosunkowo niedrogim i prostym magazynem bezpośrednio w Yandex.Cloud (tj. prawdopodobnie szybkim). Jako bezpłatną alternatywę można spróbować na przykład darmowy fragment chmurowej Mongi gdzieś daleko. Zarówno dla Object Storage (obsługuje interfejs S3), jak i dla Mongo istnieją wygodne opakowania w Pythonie.

Inny problem — aby mieć dostęp do Object Storage, MongoDB i innych baz lub magazynów danych, potrzebne są zewnętrzne zależności, które musisz wgrać na Yandex Functions razem z kodem swojej funkcji. I chciałbyś to robić wygodnie. Całkowicie wygodnie (jak na heroku), niestety, nie uda się, ale jakiś podstawowy komfort można stworzyć, pisząc skrypt do budowy środowiska (plik make).

Jak uruchomić umiejętność-horoskop

  1. Przygotuj się: wejdź na jakąkolwiek maszynę z Linuxem. W zasadzie da się również pracować z Windows, ale uruchomienie pliku make może wymagać trochę magii. W każdym razie, potrzebujesz zainstalowanego Pythona w wersji 3.6 lub wyższej.
  2. Skopiuj z gita przykład umiejętności horoskopowej.
  3. Zarejestruj się w Yandex.Cloud: https://cloud.yandex.ru
  4. Stwórz dwa buckety w Object Storage, nadaj im dowolną nazwę {BUCKET NAME} i tgalice-test-cold-storage (ta druga nazwa jest teraz twardo zakodowana w main.py moim przykładzie). Pierwszy bucket będzie potrzebny tylko do wdrożenia, drugi — do przechowywania stanów dialogu.
  5. Utwórz konto serwisowe, nadaj mu rolę editor, i zdobądź do niego statyczne poświadczenia {KEY ID} i {KEY VALUE} — będziemy je używać do rejestrowania stanu rozmowy. To wszystko jest potrzebne, aby funkcja z Yandex.Cloud mogła uzyskać dostęp do przechowywania z Yandex.Cloud. Mam nadzieję, że kiedyś autoryzacja stanie się automatyczna, ale na razie — tak.
  6. (Nieobowiązkowe) zainstalować interfejs wiersza poleceń yc. Funkcję można stworzyć również przez interfejs internetowy, ale CLI jest dobre tym, że wszelkie nowości pojawiają się w nim szybciej.
  7. Teraz można, właściwie, przygotować zbiór zależności: uruchomić w wierszu poleceń z folderu z przykładem umiejętności make all. Zainstaluje się mnóstwo bibliotek (głównie, jak zwykle, niepotrzebnych) w folderze dist.
  8. Ręcznie załadować do Object Storage (do koszyka {BUCKET NAME}) otrzymany w poprzednim kroku archiwum dist.zip. Jeśli chcesz, można to zrobić także z wiersza poleceń, na przykład używając AWS CLI.
  9. Utwórz funkcję bezserwerową przez interfejs internetowy lub używając narzędzia yc. Dla narzędzia polecenie będzie wyglądać tak:

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

Przy ręcznym tworzeniu funkcji wszystkie parametry wypełniane są analogicznie.

Teraz utworzoną przez Ciebie funkcję można testować przez konsolę dewelopera, a potem dopracować i opublikować umiejętność.

Tworzenie stanu zachowującego umiejętności dla Alisy na funkcjach serverless Yandex.Cloud i Pythonie

Co tam pod maską

Plik Make w rzeczywistości zawiera dość prosty skrypt do instalowania zależności i pakowania ich w archiwum dist.zip, mniej więcej tak:

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

Reszta to kilka prostych narzędzi, zapakowanych w bibliotekę tgalice. Proces wypełniania danych o użytkowniku opisywany jest w pliku konfiguracyjnym form.yaml:

form_name: 'horoscope_form'
start:
  regexp: 'start|begin'
  suggests:
    - Start
fields:
  - name: 'name'
    question: Proszę podać swoje imię.
  - name: 'year'
    question: Teraz proszę powiedzieć mi rok swojego urodzenia. Tylko cztery cyfry, nic więcej.
    validate_regexp: '^[0-9]{4}$'
    validate_message: Proszę spróbować ponownie. Podaj rok swojego urodzenia - cztery cyfry.
  - name: 'month'
    question: Świetnie! Teraz podaj miesiąc swojego urodzenia.
    options:
      - styczeń
      - luty
      - marzec
      - kwiecień
      - maj
      - czerwiec
      - lipiec
      - sierpień
      - wrzesień
      - październik
      - listopad
      - grudzień
    validate_message: To, co podałeś, nie przypomina miesiąca. Proszę podać miesiąc swojego urodzenia, bez innych słów.
  - name: 'day'
    question: Doskonale! Na koniec, proszę podać dzień swojego urodzenia - tylko cyfrę, jedną lub dwie.
    validate_regexp: '[0123]?d$'
    validate_message: Proszę spróbować ponownie. Musisz podać dzień swojego urodzenia (na przykład dwudziesty); to jedna lub dwie cyfry.

Zarządzanie rozbiórką tej konfiguracji i obliczaniem ostatecznego wyniku przejmuje klasa Pythona

class CheckableFormFiller(tgalice.dialog_manager.form_filling.FormFillingDialogManager):
    SIGNS = {
        'styczeń': 'Koziorożec',
        ...
    }

    def handle_completed_form(self, form, user_object, ctx):
        response = tgalice.dialog_manager.base.Response(
            text='Dziękujemy, {}! Teraz wiemy, że masz {} lat, a ty {}. n'
                 'To na pewno szczęśliwy zbieg okoliczności! Gwiazdy mówią ci: {}'.format(
                form['fields']['name'],
                2019 - int(form['fields']['year']),
                self.SIGNS[form['fields']['month']],
                random.choice(FORECASTS),
            ),
            user_object=user_object,
        )
        return response

Dokładniej, klasa bazowa FormFillingDialogManager zajmuje się wypełnianiem "formularza", a metoda klasy podrzędnej handle_completed_form mówi, co robić, gdy jest gotowa.

Poza tym podstawowym wątkiem dialogu użytkownika należy również przywitać oraz wydać pomoc przy komendzie „pomoc” i wyjść z umiejętności przy komendzie „wyjście”. W tym celu tgalice również istnieje szablon, więc kompletny menedżer dialogu składa się z fragmentów:

dm = tgalice.dialog_manager.CascadeDialogManager(
    tgalice.dialog_manager.GreetAndHelpDialogManager(
        greeting_message=DEFAULT_MESSAGE,
        help_message=DEFAULT_MESSAGE,
        exit_message='Do widzenia, wróć do umiejętności "IT Horoscope" jeszcze raz!'
    ),
    CheckableFormFiller(`form.yaml`, default_message=DEFAULT_MESSAGE)
)

CascadeDialogManager działa w sposób prosty: próbuje zastosować do bieżącego stanu dialogu wszystkie swoje składniki po kolei i wybiera pierwszą odpowiednią.

Jako odpowiedź na każdą wiadomość menedżer dialogu zwraca obiekt Pythona Odpowiedź, który można następnie skonwertować na czysty tekst lub wiadomość w Alisie lub Telegramie — w zależności od tego, gdzie bot jest uruchomiony; zawiera on również zmieniony stan dialogu, który należy zachować. Całą tę potrzebę obsługuje jeszcze jedna klasa, DialogConnector, dlatego bezpośredni skrypt do uruchomienia umiejętności na Yandex Functions wygląda następująco:

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

Jak widać, większość tego kodu tworzy połączenie z interfejsem S3 Object Storage. Jak to połączenie jest następnie używane, można przeczytać w kodzie tgalice.
Ostatnia linia tworzy funkcję alice_handler — tę właśnie, którą kazaliśmy wywoływać Yandex.Cloud, gdy ustawialiśmy parametr --entrypoint=main.alice_handler.

To właściwie wszystko. Pliki Make do budowy, S3-podobne Object Storage do przechowywania kontekstu, oraz biblioteka w Pythonie tgalice. W połączeniu z funkcjami bezserwerowymi i ekspresyjnością Pythona to wystarczy do opracowania umiejętności zdrowego człowieka.

Możesz zapytać, po co potrzeba było tworzyć tgalice? Весь скучный код, перекладывающий JSON’ы из запроса в ответ и из хранилища в память и обратно, лежит в ней. Там же лежит применялка регулярок, функция для понимания того, что «феврарь» похоже на «февраль», и прочее NLU для бедных. По моей задумке, этого уже должно быть достаточно, чтобы можно было набрасывать прототипы навыков в yaml-файлах, не слишком отвлекаясь на технические детали.

Jeśli chcesz bardziej zaawansowanego NLU, możesz do swojego projektu dodać Rasa lub DeepPavlov, ale do ich konfiguracji będą potrzebne dodatkowe zabiegi, zwłaszcza na serverless. Jeśli nie chcesz zupełnie kodować, warto skorzystać z wizualnego konstruktora typu Aimylogic. Tworząc tgalice, myślałem o jakimś pośrednim rozwiązaniu. Zobaczymy, co z tego będzie.

A teraz dołączcie do czatu deweloperów umiejętności alisowych, czytajcie dokumentację, i twórzcie wspaniałe umiejętności!

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster