在 Yandex.Cloud 和 Python 的无服务器功能上为 Alice 创建有状态技能

我们先从新闻说起。 昨天 Yandex.Cloud 宣布推出无服务器计算服务 Yandex 云功能。 这意味着:您只需编写服务的代码(例如,Web 应用程序或聊天机器人),云本身会在其运行的地方创建和维护虚拟机,甚至在负载增加时复制它们。 你根本不用考虑,非常方便。 并且付款仅适用于计算时间。

然而,有些人可能根本不付钱。 这些是开发商 爱丽丝的外在技能,即内置的聊天机器人。 任何开发人员都可以编写、托管和注册这样的技能,从今天开始,技能甚至不需要托管——只需以以下形式将他们的代码上传到云端即可 相同的无服务器功能.

但有一些细微差别。 首先,您的宠物代码可能需要一些依赖项,将它们拖到云中并不是一件容易的事。 其次,任何正常的聊天机器人都需要将对话的状态存储在某处(因此是有状态的); 如何以最简单的方式在无服务器功能中做到这一点? 第三,如何为 Alice 甚至某种具有非零情节的机器人编写快速肮脏的技能? 关于这些细微差别,其实有文章。

在 Yandex.Cloud 和 Python 的无服务器功能上为 Alice 创建有状态技能

道德准备

对于不耐烦的人:我在将函数上传到云之前使用 makefile 收集必要的依赖项,将对话框的状态存储在 Yandex 对象存储中(它支持 S3 API),并使用我自己的库来管理对话框 特加利斯. 结果,原来 这样 演示技能。 现在让我们更详细地分析这一切。

对于不太耐心的人,在深入研究我的复杂故事之前,我强烈建议从简单的开始。 即创建一个单文件echobot函数,在Dialog开发者控制台中注册并测试; 详细信息 - 在 博客对话 и 云文档。 然后才开始。

如果你想立即感受到我们为何而战,就去问爱丽丝吧。开启技能“IT占星”'。 应进行以下对话:

爱丽丝:你好! 您处于“IT 占星”技能中。 说“开始”,看看星星向您承诺了什么。
用户:开始
答:请说出您的姓名。
于:瓦西苏利
A:现在告诉我你的出生年份。 只有四位数,仅此而已。
于:第二
答:请再试一次。 注明您的出生年份 - 四位数。
于:2002年
一个了不起的! 现在说出您的出生月份。
于:二月
一个了不起的! 最后,告诉我你的出生日期——只是一个数字,只是一两位数字。
于:18年
答:谢谢瓦西里! 现在我们知道:你今年17岁,你是水瓶座。 当然,你很幸运! 星星告诉你 а что говорят звёзды, вы узнаете, запустив навык.

问题是,即使要维护这样一个简单的对话框,您也需要记住用户的姓名和出生日期,而在无服务器环境中,这并不是一件小事。 将上下文存储在 RAM 中或作为磁盘上的文件是行不通的,因为Yandex.Cloud可以同时在多个虚拟机上运行该功能,并在它们之间任意切换。 您必须使用某种外部存储。 对象存储被选为直接在 Yandex.Cloud 中相当便宜且简单的存储(也就是说,可能很快)。 作为免费的替代方案,您可以尝试例如免费的作品 多云蒙吉 在很远的某个地方。 对象存储(支持 S3 接口)和 Mongo 都有方便的 Python 包装器。

另一个问题是,为了访问对象存储、MongoDB 和任何其他数据库或数据存储,您需要一些外部依赖项,您需要将这些依赖项与函数代码一起上传到 Yandex Functions。 我想舒服地做这件事。 它非常方便(就像在heroku上一样),可惜它不起作用,但是您可以通过编写脚本来构建环境(make文件)来创建一些基本的舒适感。

如何开始占星技能

  1. 准备好:转到一台装有 Linux 的机器。 原则上,您也可以使用 Windows,但是您必须启动 makefile。 无论如何,您至少需要安装 3.6 版本的 Python。
  2. 从 github 克隆 占星技能的例子.
  3. 在Ya.Cloud注册: https://cloud.yandex.ru
  4. 为自己创建两个桶 对象存储,用任何名字称呼他们 {BUCKET NAME} и tgalice-test-cold-storage (这个中间名现在被硬编码到 main.py 我的例子)。 第一个存储桶仅用于部署,第二个存储桶用于存储对话框状态。
  5. 创建 服务帐户,给他一个角色 editor,并获取它的静态凭证 {KEY ID} и {KEY VALUE} - 我们将使用它们来记录对话的状态。 所有这些都是为了让Ya.Cloud 中的功能可以访问Ya.Cloud 中的存储。 我希望有一天,授权将变得自动,但目前 - 如此。
  6. (可选)安装 命令行界面 yc。 您还可以通过 Web 界面创建函数,但 CLI 很好,因为各种创新都可以更快地出现在其中。
  7. 事实上,现在您可以准备依赖项的组装:从包含技能示例的文件夹中在命令行上运行 make all。 一堆库(大部分是不必要的)将安装在该文件夹中 dist.
  8. 将笔填充到对象存储中(放入桶中 {BUCKET NAME}) 上一步获得的存档 dist.zip。 如果需要,您还可以从命令行执行此操作,例如使用 命令行界面.
  9. 通过 Web 界面或使用实用程序创建无服务器函数 yc。 对于该实用程序,命令将如下所示:

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

手动创建函数时,所有参数都以相同的方式填充。

现在您创建的功能可以通过开发者控制台进行测试,然后最终确定并发布技能。

在 Yandex.Cloud 和 Python 的无服务器功能上为 Alice 创建有状态技能

引擎盖下有什么

makefile 实际上包含一个相当简单的脚本,用于安装依赖项并将它们放入存档中。 dist.zip,像这样:

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

剩下的就是一些封装在库中的简单工具 tgalice。 config描述了填写用户数据的过程 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: Пожалуйста, попробуйте ещё раз. Вам нужно назвать число своего рождения (например, двадцатое); это одна или две цифры.

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

更准确地说,基类 FormFillingDialogManager 正在忙着填写“表格”,子类的方法 handle_completed_form 告诉她准备好后该做什么。

除了这个主要的对话流程之外,还必须向用户打招呼,并在“帮助”命令上提供帮助,并在“退出”命令上释放技能。 为此在 tgalice 还有一个模板,因此整个对话框管理器由以下部分组成:

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 工作原理很简单:它尝试依次将其所有组件应用于对话的当前状态,并选择第一个相关组件。

作为对每条消息的响应,对话管理器返回一个 python 对象 Response,然后可以转换为纯文本,或者转换为 Alice 或 Telegram 中的消息 - 取决于机器人运行的位置; 它还包含需要保存的对话框的更改状态。 所有这个厨房都是由另一个班级负责的, DialogConnector,因此在 Yandex Functions 上启动技能的直接脚本如下所示:

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

如您所见,大部分代码都会创建与对象存储 S3 接口的连接。 这个连接是如何直接使用的,可以阅读 在 tgalice 代码中.
最后一行创建一个函数 alice_handler - 我们在设置参数时命令拉取 Yandex.Cloud 的那个 --entrypoint=main.alice_handler.

事实上,仅此而已。 用于构建的 Makefile、用于上下文存储的类似 S3 的对象存储以及一个 Python 库 tgalice。 再加上Python的无服务器特性和表现力,这足以培养一个健康人的技能。

您可能会问为什么需要创建 tgalice? 所有将 JSON 从请求传输到响应、从存储传输到内存并返回的无聊代码都在其中。 还有正则表达式应用程序、用于理解“二月”与“二月”类似的函数,以及其他面向穷人的 NLU。 按照我的想法,这应该已经足够能够在 yaml 文件中勾勒出技能原型,而不会被技术细节过多分散注意力。

如果你想要更严肃的 NLU,你可以将其运用到你的技能中 Rasa или 深帕夫洛夫,但设置它们需要额外用手鼓跳舞,尤其是在无服务器上。 如果您根本不想编码,您应该使用视觉类型构造函数 爱美乐。 在创建 tgalice 时,我想到了某种中间路径。 让我们看看发生了什么。

嗯,现在加入 Aliy技能开发者聊天, 读 文件并创造出令人惊叹的 技能!

来源: habr.com

添加评论