Skip to content

Latest commit

 

History

History
960 lines (676 loc) · 55.9 KB

File metadata and controls

960 lines (676 loc) · 55.9 KB

十四、运用我们的知识:Alexa 技能包

本书的目标之一是强调贯穿其中的思想、技术和技能适用于许多类型的应用。在本章中,通过创建一个简单的 Alexa 技能,我们演示了如何应用我们的意图分类、实体提取和对话构建知识来创建自然语言语音体验。我们通过使用 Node.js 的 Alexa Skills Kit SDK,以最简单的方式创建一个 Alexa 技能,因为我们已经有了一个机器人服务后端,你可能不可避免地会问我们是否可以将 Alexa 与这个后端集成。答案是响亮的是。一旦我们有了 Alexa 技能的基础,我们将展示如何通过直线和机器人框架机器人来驱动 Alexa 技能。

介绍

Alexa 是亚马逊的智能个人助理。第一个支持 Alexa 的设备是 Echo 和 Echo Dot,随后是支持屏幕的 Echo Show 和 Spot。亚马逊也在探索一个名为 Lex 的聊天机器人平台。Alexa 技能是通过声明一组意图和插槽(实体的另一个名称)并编写一个 webhook 来处理传入的 Alexa 消息而开发的。来自 Alexa 的消息将包括解析的意图和槽数据。我们的 webhook 用包含语音和用户界面元素的数据进行响应。在 Echo 和 Echo Dot 的第一次迭代中,没有物理屏幕,因此唯一的用户界面是用户手机上的 Alexa 应用。该应用的主要用户界面元素是一张卡片,与我们在 Bot Builder SDK 中遇到的英雄卡片没有太大区别。例如,从 Alexa 到我们的 webhook 的消息将如下所示。请注意,本节中介绍的消息格式是伪代码,因为实际消息要详细得多。

{
    "id": "0000001",
    "session": "session00001",
    "type": "IntentRequest",
    "intent": {
        "intent": "QuoteIntent",
        "slots": [
            {
                "type": "SymbolSlot",
                "value": "apple"
            }
        ]
    }
}

响应如下所示:

{
    "speech": "The latest price for AAPL is 140.61",
    "card": {
        "title": "AAPL",
        "text": "The latest price for Apple (AAPL) is $140.61.",
        "img": "https://fakebot.ngrok.io/img/d5fa618b"
    }
}

我们可能希望允许额外的功能,如播放音频文件。为了与财务场景保持一致,我们可能会为用户播放音频简报内容。完成此任务的消息类似于以下内容:

{
    "speech": "",
    "directives": [
        {
            "type": "playAudio",
            "parameters": {
                "href": "https://fakebot.ngrok.io/audio/audiocontent1",
                "type": "audio/mpeg"
            }
        }
    ]
}

此外,系统可能想要提供用户是否取消了音频回放或收听了整个剪辑的指示。更一般地说,系统可能需要一种方式将事件发送到我们的 webhook。在这些情况下,传入的消息可能如下所示:

{
    "id": "0000003",
    "session": "session00001",
    "type": " AudioFinished"
}

如果我们获得了像 Echo Show 设备提供的屏幕的使用,更多动作和行为的潜力就会增长。例如,我们现在可以播放视频。或者我们可以向用户展示一个带有图像和按钮的用户界面。如果我们显示一个项目列表,也许我们希望设备在项目被点击时发送一个事件。然后,我们将创建一个用户界面呈现指令,因此,我们之前的报价响应现在可能会包括一个用户界面元素,如下所示:

{
    "speech": "The latest price for AAPL is 140.61",
    "card": {
        "title": "AAPL",
        "text": "The latest price for Apple (AAPL) is $140.61.",
        "img": "https://fakebot.ngrok.io/img/d5fa618b"
    },
    "directives": [
        {
            "type": "render",            
            "template": "single_image_template",
            "param": {
                "title": "AAPL",
                "subtitle": "Apple Corp.",
                "img": "https://fakebot.ngrok.io/img/largequoteaapl"
            }
        }
    ]
}

指令的伟大之处在于它们是声明性的;由设备决定如何处理它们。例如,Echo Show 和 Echo Spot 设备可以以稍微不同但一致的方式呈现模板。当 Echo 和 Echo Dot 收到不支持的指令(如播放视频)时,它们可能会忽略或引发错误。

创造新技能

创建一个新的 Alexa 技能需要访问亚马逊开发者帐户进行技能注册,并访问亚马逊网络服务(AWS)帐户来托管技能代码。要开始,导航到 https://developer.amazon.com 并点击开发者控制台链接。如果您有帐户,请登录。否则,点击创建您的亚马逊开发者账户。我们将被要求提供电子邮件和密码、我们的联系信息以及开发人员或公司名称;我们还需要接受应用分发协议,并回答一些关于我们的技能是否会接受付款或显示广告的问题。我们可以将最后两个问题的答案都选择为。此时,我们将被带到仪表板(图 14-1 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig1_HTML.jpg

图 14-1

仪表盘上没什么

单击 Alexa 技能工具包标题项目。我们现在将被放置在 Alexa 技能工具包开发者控制台中,技能列表为空。单击创建技能后,我们必须输入技能名称。之后,我们必须选择一个模型来增加技能。有几种类型的带有预构建的自然语言模型的技能可供选择,但是在这种情况下,我们选择构建我们自己的模型,因此我们选择自定义技能。 1 选择自定义类型后,点击创建技能按钮。我们现在看到了技能仪表盘(图 14-2 )。仪表板包括创建技能语言模型的能力,以及配置、测试甚至发布技能的能力。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig2_HTML.jpg

图 14-2

新的自定义技能仪表板

在页面右侧有一个方便的技能构建清单区域,我们将跟随它。我们将从设置技能的调用名称开始。当用户想要在他们的 Alexa 设备上调用技能时,这是用来标识技能的短语。例如,在“Alexa,请金融机器人引用苹果”话语中,金融机器人是调用名称。点击调用名称检查表项,加载屏幕进行设置(图 14-3 )。输入名称后,点击保存模型

img/455925_1_En_14_Chapter/455925_1_En_14_Fig3_HTML.jpg

图 14-3

设置技能调用名称

在我们开始建立我们的自然语言模型,或交互模型之前,我们需要启用正确的接口。回想一下,我们谈到了向设备发送指令的能力,比如播放音频文件或呈现用户界面元素。我们必须在我们的技能中明确地启用这些特性。点击左侧导航窗格中的接口链接。在该界面中,启用音频播放器显示界面视频 App (图 14-4 )。我们将在本章练习中尝试所有这些。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig4_HTML.jpg

图 14-4

启用 Alexa 界面

我们现在已经准备好开发 Alexa 交互模型了。

亚历克莎·NLU 和自动语音识别

您可能已经注意到,当我们第一次创建技能时,我们的技能模型中有三个内置的意图。这些显示在左侧窗格中。启用各种接口后,我们现在有大约 16 个意图。随着 Alexa 系统增加更多的功能,越来越多的意图将被添加到所有的技能中。

这突出了 Alexa 交互模型和语言理解智能服务(LUIS)之间的第一个区别,在第 3 章中进行了深入探讨。LUIS 是一个通用的自然语言理解(NLU)平台,几乎可以在任何自然语言应用中使用。Alexa 是一个围绕数字助理设备的特定生态系统。为了在所有 Alexa 技能之间创造一致的体验,亚马逊为所有技能提供了一套通用的内置意图,前缀为亚马逊。(图 14-5 )。为了获得最佳的用户体验,我们的技术应该尽可能多地实现这些功能,否则就会失败。亚马逊将在技能审查过程中审查所有这些。顺便说一句,我们在本书中不涉及技能审查和认证;Amazon 围绕这个过程提供了大量详细的文档。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig5_HTML.jpg

图 14-5

内置的 Alexa 意图

如果列出的 16 个还不够,亚马逊提供了总共 133 个内置意图供我们的技能利用。熟悉 Amazon 提供的集合对我们很有用,因为这个列表会独立于我们的技能继续发展。当然,编写自定义技能意味着添加自定义意图。当我们创建一个金融机器人技能时,我们将创建一个报价意图,这将允许我们获得一个公司或一个符号的报价。要添加新的自定义意图,请单击左侧意图标题旁边的添加按钮。选中创建自定义意向复选框,输入名称,点击创建自定义意向按钮(图 14-6 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig6_HTML.jpg

图 14-6

添加报价内容自定义意图

我们被带到意图屏幕,在那里我们可以输入示例话语(图 14-7 )。请注意,该意图被添加到左侧窗格中,如果我们选择从模型中删除该意图,它旁边会有一个垃圾桶按钮。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig7_HTML.jpg

图 14-7

填充引用内容的示例话语

接下来,我们需要能够提取我们想要报价的公司或符号的名称。在路易斯,我们将为此创建一个新的实体;在 Alexa 的世界里,这被称为。我们将创建一个名为 QuoteItem 的自定义插槽类型,并给出一些公司名称或符号的示例。我们首先通过点击左侧窗格中插槽类型标题旁边的添加按钮来添加新的插槽类型(图 14-8 )。注意有 96 种内置插槽类型!这些包括从日期和数字到演员,体育,甚至视频游戏的一切。有一个公司插槽类型可以满足我们的目的,但我们选择继续使用自定义插槽类型作为练习。选择创建自定义插槽类型单选按钮,输入名称,点击创建自定义插槽类型按钮。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig8_HTML.jpg

图 14-8

添加新的插槽类型

接下来,我们输入 QuoteItem 插槽类型的各种值(图 14-9 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig9_HTML.jpg

图 14-9

向自定义插槽类型添加新值

当然,这是一个有限的集合,但是现在已经够用了。公司名称和股票代码的范围非常大,我们不希望在示例窗值中输入所有的公司名称和股票代码。然而,我们提供的例子越多,NLU 引擎在正确识别 QuoteItems 方面就会越好,自动语音识别(ASR)引擎也会越好。这后一点的原因是,语音识别系统,如 Alexa,Google Home 和微软的 Cortana 都可以用不同的话语进行准备。启动是 ASR 过程中的一个重要步骤,因为它向引擎提供了关于技能词汇的清晰提示。这使得 ASR 系统能够理解上下文并更好地转录用户的话语。

让我们回到报价内容。在 Alexa 的 NLU 中,我们必须明确地添加插槽类型。在示例话语下面,intent 用户界面允许我们添加槽位。为该插槽命名,然后单击+按钮。现在,我们能够分配插槽类型(图 14-10 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig10_HTML.jpg

图 14-10

将 QuoteItem 插槽类型添加到 quote item

最后,我们必须正确标记每个话语中的时间段。我们可以通过在样本话语界面中选择一个单词或一组连续单词来做到这一点。我们将看到一个弹出窗口,其中显示了您可以分配给所选子字符串的目的槽。在为每个选项选择 QuoteItem 后,我们的 quote 内容将如图 14-11 所示。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig11_HTML.jpg

图 14-11

报价内容现在准备好了

我们将增加一个意向。我们希望能够使用“获取 401k 账户信息”或“什么是罗斯个人退休帐户?”等语句来询问特定账户类型的信息我们把这个意图叫做 GetAccountTypeInfoIntent 。在创建意向之前,让我们创建支持的插槽类型。与添加 QuoteItem 插槽类型的方式相同,让我们添加一个 AccountType 自定义插槽类型。

创建后,输入一组不同的帐户类型和不同的表达方式。例如,401k 也可以称为 401(k)。注意,我们还指定了每个帐户类型的单词拼写(图 14-12 )。其原因是 ASR 系统可以将用户输入转录为单词,而不是数字。请注意,对于我们的应用来说,帐户类型集很可能是一个封闭集,因此这与我们的 Note 内容中 QuoteItem 的开放概念呈现了不同的用例。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig12_HTML.jpg

图 14-12

使用同义词创建自定义插槽类型

现在我们可以创建一个名为GetAccountTypeInformationIntent的新的定制意图。添加 AccountType 作为意向槽。然后我们可以输入一些示例语句。结果如图 14-13 所示。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig13_HTML.jpg

图 14-13

最终确定 GetAccountTypeInformationIntent

至此,我们已经完成了交互模型的初稿。单击保存模型按钮,然后单击构建模型按钮。建立模型将利用我们提供的所有数据来训练系统。注意,在任何时候,我们都可以使用左侧窗格中的 JSON 编辑器链接看到模型 JSON 格式。JSON 封装了添加到模型中的所有内容。图 14-14 显示了其中的一部分。共享模型最简单的方法是共享这个 JSON 内容。当然,也有命令行工具来进一步自动化这个过程。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig14_HTML.jpg

图 14-14

我们刚刚创建的 Alexa 交互模型的摘录

为了本章的目的,这是我们将涵盖的关于 Alexa 的 NLU。明确地说,我们做得不公平。系统丰富,值得学习。

进入 Node 的 Alexa 技能包

回到仪表板,技能构建清单的最后一步是设置端点。端点是接收来自 Amazon 的传入消息并用语音、卡片和指令进行响应的代码。

这里我们可以采取两种方法。首先,我们可以自己托管一个端点,给 Amazon 提供 URL,解析每个请求,并做出相应的响应。使用这种方法,我们获得了控制权,但是必须自己实现验证和解析逻辑。我们还将拥有部署任务。

第二种选择是使用无服务器计算,这种选择目前非常普遍。 2 这让我们能够在云端创建代码,根据需求运行和扩展。在 AWS 上,这是 Lambda。在 Azure 中,等价的应该是函数。亚马逊为此提供了 Node.js 的亚马逊 Alexa 技能工具包 SDK(https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs)。在这一节中,我们将深入探讨在 AWS Lambda 上运行 Alexa 的技巧。

使用 Alexa 技能工具包 SDK 构建的技能结构如下所示。我们在代码中注册了所有想要处理的意图。emit 函数向 Alexa 发送响应。SDK 的 GitHub 站点上记录了许多不同的 emit 重载。 3

const handlers = {
    'LaunchRequest': function () {
        this.emit('HelloWorldIntent');
    },

    'HelloWorldIntent': function () {
        this.emit(':tell', 'Hello World!');
    }
};

最后,我们向 Alexa SDK 注册技能和处理程序。

const Alexa = require('alexa-sdk');

exports.handler = function(event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(handlers);
    alexa.execute();
};

这段代码足以运行一个基本技能,该技能在启动时或者当hello world ent意图匹配时以“hello world”响应。从概念上讲,我们在为我们的金融技能创建代码时将遵循相同的方法。不过,在我们继续之前,我们如何将我们的技能与 AWS Lambda 联系起来?

首先,我们需要一个 AWS 帐户。我们可以在这里创建一个 AWS 自由层账户: https://aws.amazon.com/free/ 。免费层是入门和熟悉 AWS 的最佳方式。点击创建免费账户。我们将被要求提供电子邮件地址、密码和 AWS 帐户名称(图 14-15 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig15_HTML.jpg

图 14-15

创建新的 AWS 帐户

接下来,我们将输入我们的个人联系信息。我们将需要输入我们的支付信息用于身份验证目的(您在免费层时不会被收费)并验证我们的电话号码。完成后,我们将进入 AWS 管理控制台。此时,我们可以在“所有服务”列表中找到 Lambda 并导航到它。

现在我们可以开始创建一个 Lambda 函数。点击“创建功能”,选择蓝图,找到并选择 alexa-skill-kit-sdk-factskill,然后点击配置按钮。我们为该功能指定一个对我们帐户的功能列表唯一的名称,将角色设置为从模板创建新角色,为角色指定一个名称,并选择简单微服务权限模板(图 14-16 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig16_HTML.jpg

图 14-16

创建新的 Lambda 函数

在数据输入字段下面,我们将看到我们的 Lambda 代码。运行时应该设置为 Node.js 6.10,尽管可以肯定 Amazon 可能会随时更新。我们暂时保留代码不变。点击创建功能按钮后,将进入功能配置界面(图 14-17 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig17_HTML.jpg

图 14-17

功能配置屏幕

我们可以在此屏幕上执行许多操作。首先,右上角显示了 Lambda 标识符。我们需要马上向 Alexa 展示这个技能。我们还看到该函数可以访问 CloudWatch 日志(所有 Lambda 日志都被发送到 CloudWatch)和 DynamoDB(亚马逊托管的云 NoSQL 数据库)。Alexa 技能可以使用 DynamoDB 来存储技能状态。

在 Designer 部分,我们需要设置一个触发器来调用我们的新函数。出于我们的目的,找到并点击 Alexa 技能包触发器。一旦您这样做,配置触发器部分将出现在下面。从 Alexa 技能仪表板输入技能 ID。看起来应该是amzn 1 . ask . skill . 5d 364108-7906-4612-a465-9f 560 b 0 BC 16 f。输入 ID 后,点击添加触发,保存功能配置。此时,Lambda 函数已准备好从我们的技能中调用。

在此之前,我们在设计器中选择函数(在本例中,srozga-finance-skill-function,如图14-17);我们将会看到代码编辑器。对于如何将代码加载到 Lambda 中,我们有几种不同的选择。一种选择是在编辑器中手动编写代码;另一个选择是上传一个包含所有代码的 zip 文件。在真正的应用中做这种手工劳动很快就会变得很累;你可以利用 AWS 4 并要求 CLI 5 从命令行部署一个技能。现在,我们将简单地使用编辑器。用以下代码替换编辑器中的代码:

'use strict';

const Alexa = require('alexa-sdk');
const handlers = {
    'LaunchRequest': function () {
        this.emit(':tell', 'Welcome!');
    },
    'QuoteIntent': function () {
        this.emit(':tell', 'Quote by company.');
    },
    'GetAccountTypeInformationIntent': function () {
        this.emit(':tell', 'Getting account type.');
    }
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(handlers);
    alexa.execute();

};

在我们离开之前,从屏幕的右上角复制 Lambda 函数的 Amazon 资源名称(ARN)。标识符是这样的:arn:AWS:lambda:us-east-1:526347705809:function:srozga-finance-skill-function

让我们切换回我们的技能的 Alexa 技能配置屏幕。在右侧窗格中选择端点链接。选择AWSλARN复选框,并在默认区域文本框中输入λARN(图 14-18 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig18_HTML.jpg

图 14-18

Alexa 技能λARN 端点配置

单击保存端点按钮。如果这里有问题,你可能没有正确添加 Lambda 功能的 Alexa 技能包触发器。

此时,我们可以使用顶部的导航面板导航到测试部分。默认情况下,该技能不支持测试。切换复选框。现在,我们可以从 Alexa 测试界面,任何连接到开发者帐户的 Echo 设备,或 EchoSim 等第三方工具测试技能。 6 如果您想与测试应用通话,可能会提示您允许麦克风接入。

我们可以通过说话或打字来发送输入话语,我们将收到 lambda 函数的响应,如图 14-19 所示。请确保以“询问{调用名称}”作为开场白。注意,这个接口提供了原始的输入和输出 JSON 内容。花一些时间来检查它;它包含了我们在本章前面提到的许多信息。例如,传入的请求包括来自我们的交互模型的已解析的意图和位置。输出包含回声设备要说话的 SSML。输出还指示会话应该结束。稍后我们将更深入地探讨会话。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig19_HTML.jpg

图 14-19

成功!

现在我们看到了传入的 JSON 和插槽格式,我们可以扩展代码来提取插槽值。在意图处理程序的上下文中, this.event.request 对象包含已解析的意图和槽值。从那里,它只是一个简单的问题,提取的价值和做一些事情。以下代码提取插槽值,并将它们包含在 Alexa 语音响应中:

'use strict';

const Alexa = require('alexa-sdk');
const handlers = {
    'LaunchRequest': function () {
        this.emit(':tell', 'Welcome!');
    },
    'QuoteIntent': function () {
        console.log(JSON.stringify(this.event));
        let intent = this.event.request.intent;
        let quoteitem = intent.slots['QuoteItem'].value;
        this.emit(':tell', 'Quote for ' + quoteitem);
    },
    'GetAccountTypeInformationIntent': function () {
        console.log(JSON.stringify(this.event));
        let intent = this.event.request.intent;
        let accountType = intent.slots['AccountType'].value;
        this.emit(':tell', 'Getting information for account type ' + accountType);
    }
};

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(handlers);
    alexa.execute();
};

14-20 显示了一个输入“询问金融机器人什么是 ira”的交互示例。如果你说出这句话,它会被理解为“问财务机器人什么是个人退休帐户”,确保“个人退休帐户”是个人退休帐户类型的同义词之一。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig20_HTML.jpg

图 14-20

从 Alexa 请求中成功提取 AccountType 槽值

注意,如果我们向技能发送内置 Amazon 意图应该处理的东西,比如“取消”,技能可能会返回一个错误。这是因为我们还没有处理一些内置的意图。此外,我们不包括未处理的意图逻辑。通过添加以下处理程序,我们可以轻松处理这两种情况:

    'AMAZON.CancelIntent': function() {
        this.emit(':tell', 'Ok. Bye.');
    },
    'Unhandled': function() {
        this.emit(':tell', "I'm not sure what you are talking about.");
    }

现在,告诉技能“取消”会导致一个再见消息(图 14-21 )。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig21_HTML.jpg

图 14-21

当要求技能取消时,我们承诺的时髦信息

太好了。这很好,但是我们如何将一个对话框模型化为一个 Alexa 技能呢?Node.js 的 SDK 包含了状态的概念。把它想象成用户当前的对话框。对于每个状态,我们为该状态支持的每个意图提供一组处理程序。本质上,我们通过使用一组状态名和处理程序来编码一个对话图。该技能的代码如下:

'use strict';

const Alexa = require('alexa-sdk');
const defaultHandlers = {
    'LaunchRequest': function () {
        this.emit(':ask', 'Welcome to finance skill!  I can get your information about quotes or account types.', 'What can I help you with?');
    },
    'GetAccountTypeInformationIntent': function () {
        this.handler.state = 'AccountInfo';
        this.emitWithState(this.event.request.intent.name);
    },
    'QuoteIntent': function () {
        this.handler.state = 'Quote';
        this.emitWithState(this.event.request.intent.name);
    },
    'AMAZON.CancelIntent': function () {
        this.emit(':tell', 'Ok. Bye.');
    },
    'Unhandled': function () {
        console.log(JSON.stringify(this.event));
        this.emit(':ask', "I'm not sure what you are talking about.", 'What can I help you with?');
    }
};

const quoteStateHandlers = Alexa.CreateStateHandler('Quote', {
    'LaunchRequest': function () {
        this.handler.state = '';
        this.emitWithState('LaunchRequest');
    },
    'AMAZON.MoreIntent': function () {
        this.emit(':ask', 'More information for quote item ' + this.attributes.quoteitem, 'What else can I help you with?');
    },
    'AMAZON.CancelIntent': function () {
        this.handler.state = '';
        this.emitWithState(this.event.request.intent.name);
    },
    'QuoteIntent': function () {
        console.log(JSON.stringify(this.event));
        let intent = this.event.request.intent;
        let quoteitem = null;
        if (intent && intent.slots.QuoteItem) {
            quoteitem = intent.slots.QuoteItem.value;
        } else {
            quoteitem = this.attributes.quoteitem;
        }
        this.attributes.quoteitem = quoteitem;
        this.emit(':ask', 'Quote for ' + quoteitem, 'What else can I help you with?');
    },
    'GetAccountTypeInformationIntent': function () {
        this.handler.state = '';
        this.emitWithState(this.event.request.intent.name);
    },
    'Unhandled': function () {
        console.log(JSON.stringify(this.event));
        this.emit(':ask', "I'm not sure what you are talking about.", 'What can I help you with?');
    }
});

const accountInfoStateHandlers = Alexa.CreateStateHandler('AccountInfo', {
    'LaunchRequest': function () {
        this.handler.state = '';
        this.emitWithState('LaunchRequest');
    },
    'AMAZON.MoreIntent': function () {
        this.emit(':ask', 'More information for account ' + this.attributes.accounttype, 'What else can I help you with?');
    },
    'AMAZON.CancelIntent': function () {
        this.handler.state = '';
        this.emitWithState(this.event.request.intent.name);
    },
    'GetAccountTypeInformationIntent': function () {
        console.log(JSON.stringify(this.event));
        let intent = this.event.request.intent;
        let accounttype = null;
        if (intent && intent.slots.AccountType) {
            accounttype = intent.slots.AccountType.value;
        } else {
            accounttype = this.attributes.accounttype;
        }
        this.attributes.accounttype = accounttype;
        this.emit(':ask', 'Information for ' + accounttype, 'What else can I help you with?');
    },
    'QuoteIntent': function () {
        this.handler.state = '';
        this.emitWithState(this.event.request.intent.name);
    },
    'Unhandled': function () {
        console.log(JSON.stringify(this.event));
        this.emit(':ask', "I'm not sure what you are talking about.", 'What can I help you with?');
    }
});

exports.handler = function (event, context, callback) {
    const alexa = Alexa.handler(event, context, callback);
    alexa.registerHandlers(defaultHandlers, quoteStateHandlers, accountInfoStateHandlers);
    alexa.execute();
};

注意,这个技能有两种状态:Quote 和 AccountInfo。在这些状态的上下文中,每个意图可能产生不同的行为。如果用户询问处于报价状态的帐户,该技能将重定向到默认状态,以决定如何处理该请求。同样,如果用户在 AccountInfo 状态下询问报价,也会发生类似的逻辑。图 14-22 给出了对话框的图示。注意,在代码中,如果我们想保持会话打开,我们使用 this.emit(':ask') ,如果我们只是想说话和回答并关闭会话,我们使用 this.emit(':tell') 。如果会话保持开放,我们就不必用 ask finance bot 为 Alexa 的每个发言做准备。“这是隐式的,因为用户和我们的技能之间的会话保持开放。 7 还有另一种方法可以利用 ResponseBuilder 构建响应。我们可以在 SDK 文档中读到它,我们将在练习 14-1 中使用它来构建带有渲染模板指令的响应。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig22_HTML.jpg

图 14-22

我们技能中的对话和过渡的说明

继续运行这个示例,熟悉流程背后的思想。重要的是,我们利用两个字段进行状态存储: this.handler.state 作为当前状态的名称,以及 this.attributes ,它充当用户对话数据存储。将 this.attributes 视为 Bot Builder 中的privateconversiondata字典。默认情况下,当会话结束时,这些值不会保持不变,但 Node.js 的 Alexa 技能工具包支持 DynamoDB 集成状态存储。这将使我们的技能能够在用户再次调用该技能时继续与用户进行交互。

其他选项

在此过程中,我们很方便地忽略了一些其他选项。我们技能的技能开发控制台包含帐户链接和权限链接。帐户链接是通过由 Alexa 管理的 OAuth 流程将用户重定向到授权体验的过程。Alexa 存储令牌并将它们作为每个请求的一部分发送到我们的端点。以这种方式管理的部分原因是原始 Echo 没有屏幕。作为一种启示,授权是通过 Alexa 移动应用进行的,因此 Alexa 服务器需要拥有整个 OAuth 流。

权限屏幕允许我们请求访问用户设备上的某些数据,如设备地址或 Alexa 购物清单(图 14-23 )。

你可以在 Alexa 文档中找到关于这两个主题的更多信息。 8

img/455925_1_En_14_Chapter/455925_1_En_14_Fig23_HTML.jpg

图 14-23

Alexa 权限屏幕

练习 14-1

连接真实数据并渲染图像

在第 11 章中,我们集成了一个名为 Intrinio 的服务来获取财务数据并将其呈现在图像中。本练习的目标是将您的 Alexa 技能代码连接到相同的服务,并在支持屏幕显示的 Echo 设备上呈现图像。

  1. 使用上一节中的代码作为起点。重温第 11 章的代码,确保您的报价状态报价内容处理程序从 Intrinio 中检索报价数据,并以语音方式响应最新价格。

  2. 将第 11 章的 HTML-to-image 生成代码整合到你的 Alexa 技能中。记得在 Lambda 函数中将必要的包添加到package.json文件中。

  3. 访问 https://developer.amazon.com/docs/custom-skills/display-interface-reference.html 来熟悉如何渲染显示模板。具体来说,您将使用 BodyTemplate 7 来呈现上一步生成的图像。

  4. 要使用 Node.js SDK for Alexa Skills Kit 渲染模板,您需要使用响应生成器( https://github.com/alexa/alexa-skills-kit-sdk-fornodejs#response-vs-responsebuilder )。SDK 有助手生成模板 JSON ( https://github.com/alexa/alexa-skills-kit-sdk-fornodejs#display-interface )。

  5. 测试 Alexa 测试实用程序、EchoSim 和真实 Echo 设备(如果有)的功能。在没有显示器的设备中,代码的行为是什么?

您现在应该能够在支持显示的 Echo 设备上呈现您的财务报价图像,并且您应该已经获得了使用几种方法测试 Alexa 技能的实践经验。

连接到 Bot 框架

到目前为止,我们展示的功能只是 Alexa 技能包功能的一小部分,但足以让人们对将这本书的概念应用到新兴语音平台上表示赞赏。将 Alexa 技能连接到机器人框架机器人的过程遵循类似于我们在第 8 章中为 Twilio 实现语音机器人的方法。我们将展示如何实现这种连接的代码,给出我们现有的 Alexa 技能工具包交互模型。在深入研究代码之前,我们将讨论解决方案的几个实现决策。

围绕 Bot 框架和 Alexa 技能包集成的实施决策

通常,我们不建议使用 Bot 框架来实现独立的 Alexa 技能。如果需求确实建议使用单一平台,那么局限于 Alexa 交互模型和运行在 AWS Lambda 函数上的 Node.js 的 Alexa Skills Kit SDK 就足够了。在我们的产品应该支持多种自然语言文本和语音接口的情况下,我们可能希望考虑一个平台来运行我们的业务逻辑,而 Bot 框架非常适合这种方法。一旦我们开始将 Alexa 技能连接到 Bot 框架,几个重要的实现决策就会随之而来。这些适用于所有类型的系统,而不仅仅是 Alexa。

自然语言理解

在我们当前努力的背景下,我们应该利用哪个 NLU 平台:LUIS 还是 Alexa 的交互模型?如果我们要使用 Alexa 的交互模型,我们必须通过直接的线路调用将 Alexa intent 和 slot 对象传递到我们的 bot 实现中。然后,我们可以构建一个自定义识别器来检测该对象的存在,并将其转换为 Bot Builder SDK 中正确的意图和实体响应对象。非常清楚地说,这就是识别器的用处所在:机器人不关心意图数据来自哪里。

另一方面,如果我们选择利用 LUIS,我们必须找到一种方法将来自 Alexa 的原始输入传递给机器人。实现这一点的方法是将整个用户输入标记为一个 AMAZON。文字插槽类型。 9 这允许开发者将原始的用户输入传递到技能代码中。这并不意味着我们的技能互动模式变得不存在。记住,Alexa 为它的 ASR 使用交互模型,所以我们想要给出尽可能多的我们期望在我们的技能词汇表中的话语和输入类型的例子。我们需要在 Alexa 交互模型中包含我们所有的 LUIS 话语。

一般来说,由于机器人可能比 Alexa 支持更多的通道,维护一个 NLU 系统,如 LUIS,是一个更容易维护的方法。没有办法完全脱离。我们仍然需要确保我们的机器人正确处理内置意图,如停止和取消。在下面的代码示例中,为了方便起见,我们将假设整个 NLU 模型都存在于 Alexa 中,并演示一个自定义识别器方法。

通道无关对话与通道特定对话

当我们开发一个处理多个通道的机器人时,我们必须决定一个对话框实现是否可以处理所有通道,或者每个通道是否应该有自己的对话框实现。每一种都有其论据,尽管如果你从模型视图控制器(MVC)模式的角度来考虑,我们可以提出一个优雅的解决方案。如果我们认为一个对话框是控制器,是我们与模型对话的 API,那么我们就有了一个问题,什么扮演视图的角色。

我们希望创建能够基于通道呈现消息的独立代码段。尽管 bot 服务试图抽象通道,我们还是会遇到特定于通道的行为。例如,我们将把 Alexa 与文本频道区别对待。一种方法是创建在对话框中使用的默认视图渲染器,并添加特定于通道的视图渲染器,以支持偏离默认的行为或图像。一种更通用的方法是简单地为语音和文本通道使用不同的视图呈现器。图 14-24 显示了来自语音信道的消息情况下该方法的示例流程。

img/455925_1_En_14_Chapter/455925_1_En_14_Fig24_HTML.jpg

图 14-24

来自语音通道(如 Alexa)的消息流示例,以及它通过我们的系统一直到视图呈现器的流程

Alexa 构造

Bot Builder SDK 很好地抽象了文本对话的概念,但将概念直接映射到 Alexa 并不简单。我想起了几个例子。

首先,当语音发声被发送到 Alexa 服务时,它可以包括初始语音串加上重新提示语音串。如果 Alexa 提出问题,就会向用户发出重新提示,而用户没有及时响应。Bot 生成器活动包含语音属性,但不包含重新提示属性。在我们的示例代码中,我们利用自定义通道数据字段来发送此信息。

第二个例子是 Alexa 渲染模板。虽然我们在这里没有涉及到它们,但是 Alexa 支持许多(最新统计有 7 个)模板来在支持显示的 Echo 设备上显示内容。每个模板都是一个不同的 JSON 结构,代表一个用户界面。尽管我们可以尝试使用 hero card 对象将这些模板传递给连接器,但更简单的方法是在渲染器中生成 JSON 并发送通道数据。指示 Echo 设备播放视频也面临类似的困境。

所有这些问题的解决方案是尝试尽可能多地使用 Bot Builder SDK 对象进行渲染,并仅在必要时使用通道数据。如图 14-24 所示,我们甚至可以利用 Bot Builder SDK 对象,并将它们转换为连接器层上特定于通道的结构。不过,总的来说,在 Alexa 渲染器中为每个响应生成 Alexa 通道数据更容易。

回拨支持

大多数通道可以发送与用户消息无关的事件。例如,脸书发送关于推荐、应用移交、结账和支付等事件。这些是需要在 bot 中处理的特定于通道的消息,有时在对话的结构之外。Alexa 对此类事件并不陌生。当视频或音频文件在 Echo 设备上播放时,关于进度、中断和错误的各种事件被发送到技能。由我们的机器人代码来正确解释这些事件。

进行这种交互的一个好方法是创建自定义识别器,它可以识别不同类型的消息,然后将这些消息定向到正确的对话框。对于需要 JSON 响应的事件,对话框应该使用通道数据发送有效载荷。

样本整合

让我们深入研究一下示例集成是什么样子的。我们将实现分成三个部分:连接器、识别器和机器人。完整的示例代码可以在本书的 GitHub repo 中的chapter14-alexa-skill-connector-bot文件夹下找到。

连接器由一个 HTTP 处理程序组成,Alexa 将向该处理程序发送消息。处理程序的目标是解决对话,调用机器人,等待机器人的响应,并将消息发送回 Alexa。这里有一点代码,所以让我们一步一步来看。

消息进入处理程序。我们提取请求体和用户 ID。然后,我们创建用户 ID 的 MD5 散列。这样做的原因是 Alexa 用户 id 比 Bot 框架支持的要长。哈希帮助我们保持长度可控。

const cachedConversations = {};

exports.handler = function (req, res, next) {
    const reqContents = req.body;
    console.log('Incoming message', reqContents);

    const userId = reqContents.session.user.userId;
    const userIdHash = md5(userId);

    ...
};

接下来,我们要么检索该用户的缓存会话,要么创建一个新会话。请注意,我们将对话存储在内存中,因此每次服务器重启都会创建新的直线对话。在生产中,我们将使用一个持久存储,使用诸如 Cosmos DB 或 Azure Table Storage 之类的服务。Alexa 还包括一个标志,通知我们一个会话是否刚刚开始。如果我们没有缓存的会话或者会话是新的,我们创建一个新的直接线路会话并缓存它。

const cachedConv = cachedConversations[userId];
let p = Promise.resolve(cachedConv);
if (reqContents.session.new || !cachedConv) {
    p = startConversation(process.env.DL_KEY).then(conv => {
        cachedConversations[userId] = { id: conv.conversationId, watermark: null, lastAccessed: moment().format() };
        console.log('created conversation [%s] for user [%s] hash [%s]', conv.conversationId, userId, userIdHash);
        return cachedConversations[userId];
    });
}

p.then(conv => {
    ...
});

检索到对话后,我们向机器人发布一个活动。请注意,由于我们决定传递已解析的 Alexa 交互模型意图和槽,我们只需通过 sourceEvent 属性中的通道数据传递 Alexa 消息。

postActivity(process.env.DL_KEY, conv.id, {
    from: { id: userIdHash, name: userIdHash }, // required (from.name is optional)        
    type: 'message',
    text: '',
    sourceEvent: {
        'directline': {
            alexaMessage: reqContents
        }
    }
}).then(() => {
    ...
});

如果 Alexa 发送了 SessionEndedRequst,我们会自动回复一个 HTTP 200 状态代码。

if (reqContents.request.type === 'SessionEndedRequest') {
    buildAndSendSessionEnd(req, res, next);
    return;
}
function buildAndSendSessionEnd(req, res, next) {
    let responseJson =
        {
            "version": "1.0"
        };
    res.send(200, responseJson);
    next();
}

否则,我们使用直接线路轮询机制来尝试从 bot 获取活动响应。六秒钟后我们超时。一旦确定了响应活动,我们就从活动中提取一些特定于 Alexa 的信息,并构建对 Alexa 的响应。如果消息超时,我们将发回一个 HTTP 504 状态代码。

let timeoutAttempts = 0;
const intervalSleep = 500;
const timeoutInMs = 10000;
const maxTimeouts = timeoutInMs / intervalSleep;
const interval = setInterval(() => {

    getActivities(process.env.DL_KEY, conv.id, conv.watermark).then(activitiesResponse => {
        const temp = _.filter(activitiesResponse.activities, (m) => m.from.id !== userIdHash);
        if (temp.length > 0) {
            clearInterval(interval);
            const responseActivity = temp[0];
            console.log('Bot response:', responseActivity);

            conv.watermark = activitiesResponse.watermark;
            conv.lastAccessed = moment().format();
            const keepSessionOpen = responseActivity.channelData && responseActivity.channelData.keepSessionOpen;
            const reprompt = responseActivity.channelData && responseActivity.channelData.reprompt;
            buildAndSendSpeech(responseActivity.speak, keepSessionOpen, reprompt, req, res, next);
        } else {
            // no-op
        }
        timeoutAttempts++;

        if (timeoutAttempts >= maxTimeouts) {
            clearInterval(interval);
            buildTimeoutResponse(req, res, next);
        }
    });
}, intervalSleep);

就这样!构建响应消息的代码如下。

function buildTimeoutResponse(req, res, next) {
    res.send(504);
    next();
}

function buildAndSendSpeech(speak, keepSessionOpen, reprompt, req, res, next) {
    let responseJson =
        {
            "version": "1.0",
            "response": {
                "outputSpeech": {
                    "type": "PlainText",
                    "text": speak
                },
                // TODO REPROMPT
                "shouldEndSession": !keepSessionOpen
            }
        };
    if (reprompt) {
        responseJson.reprompt = {
            outputSpeech: {
                type: 'PlainText',
                text: reprompt
            }
        };
    }
    console.log('Final response to Alexa:', responseJson);
    res.send(200, responseJson);
    next();
}

function buildAndSendSessionEnd(req, res, next) {
    let responseJson =
        {
            "version": "1.0"
        };
    res.send(200, responseJson);
    next();
}

直线功能与我们在第 9 章中展示的功能相同。

机器人方面的消息会发生什么?首先,它会击中我们的自定义识别器。识别器首先确保我们正在获取 Alexa 消息,并且它是 IntentRequest、LaunchRequest 或 SessionEndedRequest 请求。如果是 IntentRequest,我们将 Alexa intent 和 slots 解析为 LUIS 的 intent 和 entities。正如注释所指出的,slots 对象的格式不同于 LUIS entities 对象。如果我们要在一个机器人中混合两个 NLU 系统来使用相同的对话框,我们必须确保格式是标准化的。如果请求是 LaunchRequest 或 SessionEndedRequest,我们只需将这些字符串作为 bot 意图传递。

exports.recognizer = {
    recognize: function (context, done) {
        const msg = context.message;

        // we only look at directline messages that include additional data
        if (msg.address.channelId === 'directline' && msg.sourceEvent) {

            const alexaMessage = msg.sourceEvent.directline.alexaMessage;

            // skip if no alexaMessage
            if (alexaMessage) {
                if (alexaMessage.request.type === 'IntentRequest') {
                    // Pass IntentRequest into the dialogs.
                    // The odd thing is that the slots and entities structure is different. If we mix LUIS/Alexa
                    // it would make sense to normalize the format.
                    const alexaIntent = alexaMessage.request.intent;
                    const response = {
                        intent: alexaIntent.name,
                        entities: alexaIntent.slots,
                        score: 1.0
                    };
                    done(null, response);

                    return;
                } else if (alexaMessage.request.type === 'LaunchRequest' || alexaMessage.request.type === 'SessionEndedRequest') {
                    // LaunchRequest and SessionEndedRequest are simply passed through as intents
                    const response = {
                        intent: alexaMessage.request.type,
                        score: 1.0
                    };
                    done(null, response);
                    return;
                }
            }
        }
        done(null, { score: 0 });
    }
};

让我们回到机器人代码。我们首先注册我们的自定义 Alexa HTTP 处理程序、自定义识别器和默认响应。请注意我们使用的自定义直线数据。如果我们向技能询问它不支持的内容,会话将终止。

server.post('/api/alexa', (req, res, next) => {
    alexaConnector.handler(req, res, next);
});

const bot = new builder.UniversalBot(connector, [
    session => {
        let response = 'Sorry, I am not sure how to help you on this one. Please try again.';
        let msg = new builder.Message(session).text(response).speak(response).sourceEvent({
            directline: {
                keepSessionOpen: false
            }
        });
        session.send(msg);
    }
]);

bot.recognizer(alexaRecognizer);

接下来,我们创建 QuoteDialog 对话框。请注意以下几点:

  • 它从实体中读取报价项目,就像我们的 Alexa 技能代码一样。

  • 它通过 speak 属性发送响应,但也在自定义直接线路通道数据中包含重新提示。

  • 在这个对话框的上下文中,如果机器人检测到亚马逊。MoreIntent,调用 MoreQuoteDialog 对话框。

  • 在 MoreQuoteDialog 对话框执行后,它将控制权交还给 QuoteDialog。

bot.dialog('QuoteDialog', [
    (session, args) => {
        let quoteitem = args.intent.entities.QuoteItem.value;
        session.privateConversationData.quoteitem = quoteitem;

        let response = 'Looking up quote for ' + quoteitem;
        let reprompt = 'What else can I help you with?';
        let msg = new builder.Message(session).text(response).speak(response).sourceEvent({
            directline: {
                reprompt: reprompt,
                keepSessionOpen: true
            }
        });
        session.send(msg);
    }
])
    .triggerAction({ matches: 'QuoteIntent' })
    .beginDialogAction('moreQuoteAction', 'MoreQuoteDialog', { matches: 'AMAZON.MoreIntent' });

bot.dialog('MoreQuoteDialog', session => {

    let quoteitem = session.privateConversationData.quoteitem;
    let response = 'Getting more quote information for ' + quoteitem;
    let reprompt = 'What else can I help you with?';
    let msg = new builder.Message(session).text(response).speak(response).sourceEvent({
        directline: {
            reprompt: reprompt,
            keepSessionOpen: true
        }
    });
    session.send(msg);
    session.endDialog();
});

对于 GetAccountTypeInformationIntent 意图,重复了相同的模式。最后,我们添加了一些处理程序来支持诸如取消技能和处理 LaunchRequest 和 SessionEndedRequest 事件之类的事情。

bot.dialog('CloseSession', session => {
    let response = 'Ok. Good bye.';
    let msg = new builder.Message(session).text(response).speak(response).sourceEvent({
        directline: {
            keepSessionOpen: false
        }
    });
    session.send(msg);
    session.endDialog();
}).triggerAction({ matches: 'AMAZON.CancelIntent' });

bot.dialog('EndSession', session => {
    session.endConversation();
}).triggerAction({ matches: 'SessionEndedRequest' });

bot.dialog('LaunchBot', session => {
    let response = 'Welcome to finance skill!  I can get your information about quotes or account types.';
    let msg = new builder.Message(session).text(response).speak(response).sourceEvent({
        directline: {
            keepSessionOpen: true
        }
    });
    session.send(msg);
    session.endDialog();
}).triggerAction({ matches: 'LaunchRequest' });

这就完成了我们与 Alexa 的整合。如果我们运行代码,我们将看到与我们之前开发的 Lambda 技能类似的行为。在机器人代码和连接器代码中有许多未处理的意图和意外情况,但我们正在将 Alexa 技能工具包与微软的机器人框架集成。

练习 14-2

将数据和引用图像整合到 Bot Builder 代码中

在练习 14-1 中,我们将 Lambda 函数代码连接到数据,并生成一个图像来在支持屏幕显示的 Echo 设备上呈现报价。在本练习中,我们将把这两个组件都移植到我们的 Bot Builder 代码中。

  1. 利用前一节的代码作为起点。

  2. 从 Lambda 函数中提取适当的图像生成代码,并将其添加到您的 bot 中。确保安装了必要的 Node.js 包。

  3. 在对话框中生成显示模板,并将其添加到您的自定义频道数据中。您可以将 Node.js 的 Alexa Skills Kit SDK 作为依赖项来使用模板构建器类型。

  4. 确保连接器将通道数据模板正确地转换为最终响应,并返回给 Alexa。

  5. 运行你的集成 Alexa 技能和机器人框架机器人,并使用你在练习 14-2 中使用的相同方法进行测试。

  6. 如何修改 bot 代码,以便您可以通过 bot 框架模拟器利用您的 Bot?在您从本书中获得所有知识之后,您应该能够创建一个 LUIS 应用来完成这一体验。

让这个工作起来感觉真好!开发语音聊天机器人可能非常有趣,尤其是在 Alexa 这样丰富的生态系统上。

结论

这一章使我们能够结合这本书的知识来利用亚马逊的 Alexa 平台,另外,将它与 Bot Builder SDK 集成。现代的对话界面可以简化为 NLU 意图和实体加上一个对话引擎来驱动对话。无论是 Alexa 还是其他类似 Google Assistant 的通道,所有这些系统都有共同的核心理念。有些人会在语音和文本交流之间画出一个足够清晰的界限,以此来论证处理这两种交流的不同方式的必要性。虽然语音和文本通信确实截然不同,足以保证不同的前端体验,但处理一般对话想法的能力在 Bot Builder SDK 中得到了很好的发展。我们可以将不同的 NLU 系统连接起来,将它们自己的意图传递到我们的 Bot 框架中,这个想法非常强大。这意味着进入我们的机器人的消息可以不仅仅是文本。它可以是任何一种复杂的物体,只是受到我们想象力的限制。当然,运行一个连接到许多特定接口的通用系统总会有一定程度的开销,但是,正如我们希望在本章中所展示的那样,构建连接层所需的额外工作是我们力所能及的。

Footnotes [1](#Fn1_source)

了解不同类型的 Alexa 技能: https://developer.amazon.com/docs/ask-overviews/understanding-the-different-types-of-skills.html

  2

无服务器计算的真正含义: https://www.infoworld.com/article/3093508/cloud-computing/what-serverless-computing-really-means.html

  3

Node.js 的 Alexa 技能包:Response vs . Response builder:https://github.com/alexa/alexa-skills-kit-sdk-for-nodejs#response-vs-responsebuilder

  4

AWS CLI: https://aws.amazon.com/cli/

  5

Alexa 技能工具包(ASK) CLI: https://developer.amazon.com/docs/smapi/quick-start-alexa-skills-kit-command-line-interface.html

  6

EchoSim 是基于浏览器的 Alexa 界面。它有助于测试开发技能。随着 Alexa 测试工具在最近几个月有了实质性的改进,EchoSim 工具的有效性还有待观察; https://echosim.io

  7

Alexa 会话是一个有趣的话题,值得更多的检查。更多信息可在线查询 https://developer.amazon.com/alexa-skills-kit/big-nerd-ranch/alexa-voice-user-interfaces-and-sessions

  8

账户关联文档: https://developer.amazon.com/docs/custom-skills/link-an-alexa-user-with-a-user-in-your-system.html 。使用设备地址 API: https://developer.amazon.com/docs/custom-skills/device-address-api.html 。使用 Alexa 的待办事项和购物清单: https://developer.amazon.com/docs/custom-skills/access-the-alexa-shopping-and-to-do-lists.html

  9

围绕字面槽类型及其使用有很多争论。一段时间以来,亚马逊一直试图反对这种插槽类型。很容易理解为什么。自然语言模型和 Alexa 通过使用模型来启动自动语音识别引擎的能力只与模型的内容一样好。如果一些 NLU 被卸载到一个单独的系统,NLU 和语音识别就会受到影响。也就是说,即使亚马逊支持替代方案,插槽类型仍未被移除。 https://developer.amazon.com/post/Tx3IHSFQSUF3RQP/Why-a-Custom-Slot-is-the-Literal-Solution

  10

模型视图控制器: https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller