2016 年 12 月

第 31 卷,第 13 期

Microsoft Bot Framework - 使用 Bot Framework 可随时随地访问应用程序数据

作者:Srikantan Sankaran

在上一期中,我介绍了 Microsoft Bot Framework,以及如何利用它应对如今许多组织面临的挑战,即让客户能够通过语音和短消息以交互方式访问服务 (msdn.com/magazine/mt788623)。在上一篇文章中,我介绍了一个业务应用场景,即使用 Microsoft Flow 和 Azure 逻辑应用等技术,整合在保险服务提供商组织部署的各种业务线应用程序中的信息。此内容包含结构化数据和非结构化数据,通过 Azure 搜索编制索引后即可供客户端应用程序使用。

图 1 在上一篇文章中也出现过,展示了实现此应用场景的解决方案的体系结构。

解决方案的体系结构
图 1:解决方案的体系结构

在本文中,我将生成和部署一个智能机器人应用程序,其可以使用通过 Azure 搜索收集到的信息。此智能机器人应用程序将部署为 Azure Web 应用,并通过 Microsoft Bot Framework 连接器服务发布。通过连接器服务,使用者可以从 Skype 通道访问此智能机器人应用程序,从而能够查看保险单申请状态、下载已开具的保险单、安排现场查访等。使用者将能够使用其在申请保险单时注册的 Microsoft 帐户在此智能机器人应用程序中验证自己的身份,并能使用 Skype 客户端上的消息服务或语音命令与此智能机器人应用程序进行交互。此智能机器人应用程序与 LUIS 服务(Azure 认知服务提供的多项服务之一)集成,以解释使用者的对话,从而在 Azure 搜索上执行查询。

我将按以下顺序介绍生成此解决方案所需的步骤。代码片段取自 Visual Studio 2015 解决方案,可与本文一起下载。若要继续学习本文,应执行以下操作:

  • 创建并发布 LUIS 模型,从而使用 LUIS 服务提供的 UI 捕获需要智能机器人应用程序支持的不同对话。
  • 使用 Visual Studio 2015 模板新建一个智能机器人应用程序。然后,可以添加本文随附的解决方案中的代码块,或参照本文其余部分提及的已下载解决方案文件。MessageController.cs 文件是智能机器人应用程序的入口点,这是一种特殊类型的 .NET MVC 应用程序。
  • 使用 Bot Framework SDK 中提供的 LUIS 对话框,将 LUIS 功能嵌入智能机器人应用程序中。此步操作是在解决方案的 PolicyInfoDialog.cs 文件中实现。
  • 使用 Azure 搜索 SDK,实现用来调用 Azure 搜索 API 的代码,并执行由 LUIS 服务解释的搜索请求。此步操作是在解决方案的 SearchService.cs 文件中实现。
  • 当用户首次发出请求时,实现 FormFlow 对话框,以交互方式捕获用户在 Skype 通道中的 Microsoft 帐户。此步操作是在随附的解决方案的 SigninForm.cs 文件中实现。
  • 使用 Azure 搜索 SDK,根据在用户首次访问智能机器人应用程序时捕获的 Microsoft 帐户检索用户配置文件信息。此步操作也是在 SearchService.cs 中实现。
  • 使用 Bot State Service 保留用户配置文件信息。智能机器人应用程序的用户地址和联系信息存储在 Bot State Service 中,用于安排现场查访。此步操作也是在 PolicyInfoDialog.cs 文件中实现。
  • 测试智能机器人应用程序,具体方法为在开发计算机上将其作为服务进行本地运行,并使用 Bot Framework Channel Emulator 评估用户与服务的交互情况。
  • 在本地测试智能机器人应用程序后,直接从 Visual Studio 将其部署到 Azure Web 应用中。
  • 使用 Microsoft Bot Framework 连接器服务注册智能机器人应用程序,然后在其中配置部署到 Azure 的智能机器人应用程序的 URL。
  • 将在注册智能机器人应用程序期间获取的智能机器人应用程序名称、应用 ID 和机密信息添加到 Visual Studio 解决方案中,然后将其重新发布到 Azure。
  • 在智能机器人开发者门户中为智能机器人应用程序启用跨通道(如 Skype 和 Slack)访问权限。

可以从 GitHub 存储库 (bit.ly/2cOfANh) 中下载用于智能机器人应用程序的 Visual Studio 2015 解决方案,以及此应用程序中使用的 LUIS 模型导出文件。

针对不同的用例应用场景创建和定型 LUIS 模型

图 2 展示了我执行的以下规划:用户可能会如何与智能机器人应用程序交谈(通过 LUIS 语音样本)、如何将对话解释为要通过 Azure 搜索执行的特定 LUIS 意向或操作,以及如何从 LUIS 语音样本中提取关键字(LUIS 实体)用作执行搜索时的查询参数。

图 2:映射语音样本和意向

语音样本或对话 已映射的意向或操作 限定符或参数
LUIS 无法解释(或无法为之定型)的一些内容。 无(默认) 不适用
获取(或显示)我的所有保险单申请。 GetPolicyRequests 默示针对已登录的智能机器人应用程序用户
我有一个保险单申请。,我的保险单申请获准了吗? GetPendingPolicyRequests 默示针对未处于“已批准”状态的保险单申请
为我获取编号为 VehPolicy001 的保险单申请的详细信息。 GetPolicyRequestDetails 保险单申请 ID – VehPolicy001
获取我的人寿保险单申请的状态。 GetPolicyRequestStatusByType 保险单申请类型 – 人寿保险
获取今年开具的编号为 Policy0101 的保险单。 GetPolicyDocument 文件名 – Policy0101;上次修改时间 – 今年
安排明天进行现场查访。 ScheduleSiteVisit 查访日期 – 明天

LUIS 服务在 luis.ai 中提供了一个 Web 应用程序,可以在其中直观地创建 LUIS 应用程序模型来解决当前问题。图 3 展示了为此应用场景配置的 LUIS 模型。

适用于我的应用场景的 LUIS 模型
图 3:适用于我的应用场景的 LUIS 模型

 上一步中提及的意向、实体和语音样本全都可以在 LUIS 应用程序中创建。使用“意向”菜单可以添加意向,使用“实体”菜单可以创建实体。请注意,有两种特殊种类的实体:

  • 分层实体: 在此示例中,将“保险单”创建为父级分层实体,将各种保险单类型(如人寿保险、车辆保险、房屋保险等)创建为子级分层实体。
  • 预建实体: 我就使用了 LUIS 提供的一个预建实体,名为“Datetime”。输入参数(如保险单生成年份或安排的现场查访日期)由 LUIS 引擎映射到这些预建实体。

名为“Keyword”的实体用于在 Azure 搜索编制的保险单索引中执行全文搜索。

现在,选择“新建语音样本”选项卡,为上述每个对话创建语音样本,然后为它们添加标签。

对于每个语音样本,标识或标记意向和相关实体。例如,在图 3 中,我将语音样本“获取我的人寿保险单申请的状态”映射到意向“GetPolicyRequestStatusByType”,并将文本“人寿保险”映射到分层实体“Insurance:life”。

重复执行这一步操作,添加其他变体(例如,当保险单类型为“车辆保险”、“房屋保险”或“普通保险”时)。此外,还请注意以不同方式提出同一申请的变体。不过,你无需捕获同一消息的所有可能传达方式的排列组合。例如,当我新添加一个包含“显示状态...”短语的语音样本时(如图 4 所示),LUIS 可以正确地将其解释为上一语音样本“获取状态….”的变体。 LUIS 针对意向和相关实体提出正确的建议,即使提问方式不同,也是如此。

为 LUIS 语音样本添加标签和处理变体
图 4:为 LUIS 语音样本添加标签和处理变体

捕获所有语音样本后,LUIS 应用程序会提供“定型模型”选项。这样一来,LUIS 中的 AI 引擎会生成经过优化的模型,以供客户端应用程序使用。只要为新语音样本添加标签或更改现有语音样本,就应该重复执行这一步操作。

在你发布 LUIS 模型后,客户端应用程序就可以使用 REST API 调用它了。借助此应用程序中的发布对话框,你还可以测试模型,以确保其能正常运行。图 5 展示了如何快速测试 LUIS 模型,具体方法为传入对话短语,然后在浏览器中查看输出。(为方便起见,图 5 并排展示了三个不同的语音样本调用及相应结果的快照。)

测试 LUIS 模型
图 5:测试 LUIS 模型

请注意,LUIS 推荐的意向分配有最大概率值,在列表中最先显示。从短语中提取的实体也会在响应中返回。如果语音样本中标识了多个实体,也会在响应中返回。你可以了解 LUIS 模型是如何解释语音样本短语中类似“今年”这样的词语,将其映射到预建实体“Datetime”,然后返回客户端应用程序可以直接使用的值“2016”的。

创建智能机器人应用程序

现在,LUIS 模型已经准备就绪,可以用于智能机器人应用程序了。从 aka.ms/bf-bc-vstemplate 下载 Visual Studio 2015 模板,创建应用程序。(请访问 bit.ly/2dYrwyW,了解与生成智能机器人应用程序相关的基础知识,以及如何使用智能机器人仿真器在本地生成和测试智能机器人应用程序。)

使用此模板创建的项目是 MVC 项目的变体形式。此项目的入口点是 MessageController.cs,负责处理 Skype 或 Slack 等通道传入的消息并发回响应。

适用于 Bot Framework 的 LUISDialog 用于将消息从智能机器人应用程序隐式传递给 LUIS 模型。对于在 LUIS 模型中实现的每个意向,将实现可以在智能机器人应用程序运行时直接调用的相应方法。这样一来,就无需为了解释对话并识别意向和实体而使用自定义代码调用 LUIS REST API 了。

LUIS 应用程序 ID 和密钥值用于修饰类级别属性,即 LuisModel。LuisIntent 属性用于实现用户请求中意向的方法。这就是在智能机器人应用程序中集成 LUIS 所需的一切。图 6 展示了如何通过代码实现 LUIS 集成。

图 6:实现 LUIS 集成

[LuisModel("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 
  "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx")]
[Serializable]
  public class PolicyInfoDialogs : LuisDialog<object>
  {
    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult result)
    {
      // Called when the incoming utterance is not recognized (default bin)
    }
    [LuisIntent("GetPolicyRequests")]
    public async Task GetPolicyRequests(IDialogContext context, LuisResult result)
    {
      //
    }
        ----------------------
      }

此时,每个方法的实现都非常简单;我可以十分轻松地就返回消息,指明调用的是哪一个 LuisIntent 实现。

在 MessageController.cs 中,添加以下代码片段,以便捕获智能机器人应用程序用户传入的消息,并将其传递给 LuisDialog:

if (activity.Type == ActivityTypes.Message)
  {
    await Conversation.SendAsync(activity, () => new PolicyInfoDialog());
  }

运行智能机器人应用程序,启动 Bot Framework Channel Emulator(可以从 aka.ms/bf-bc-emulator 下载)。键入之前为之定型 LUIS 模型的语音样本,并确保响应消息指明已调用映射到 LUIS 意向的正确方法。

与搜索集成

现在,我将使用 NuGet 程序包管理器将用于 .NET 的 Azure Search SDK 添加到 Visual Studio 解决方案中。解决方案中的 SearchService.cs 文件使用此 SDK,并封装与 Azure 搜索服务的所有集成。它实现可执行以下操作的方法:在 Azure 搜索中执行查询,返回客户的 UserProfile,并检索 Policy­Requests 和 PolicyDocuments。

适用于不同应用场景的搜索命令是使用 Lucene 查询语法生成,这在 PolicyInfoDialog.cs 文件中实现。示例如下: 

检索对话中智能机器人应用程序用户的所有保险单申请:

command += "CustomerId:(" + msftid + ")";
Retrieve all policy requests for the bot user that haven’t yet been approved:
command += "CustomerId:(" + msftid + ") -PolicyStatus:(Approved)";
Query policy requests for the bot user based on the policy request Id:
public async Task GetPolicyRequestDetails(IDialogContext context, LuisResult result)
  {
    ----------------------
      string  command += "CustomerId:(" + msftid + ") AND PolicyRequestId:(" +
        result.Entities[0].Entity + ")";
    ----------------------
  }

上面的代码片段展示了 LUIS 引擎是如何通过 LuisResult(结果)传递 PolicyRequestId 实体(用于执行搜索查询的输入参数)的。

图 7 展示了如何为获取保险单而实现基于属性的搜索和全文搜索。

请注意,不妨参阅我的上一篇文章,将图 7 代码中的属性名称和值联系起来。此外,还可以单击后面部分中的链接,下载本文随附数据库中使用的所有表的架构。 

图 7:为获取保险单而实现基于属性的搜索和全文搜索

foreach (EntityRecommendation curEntity in request.Entities)
  {
    switch (curEntity.Type)
      {
        case "PolicyNumber": // Look for documents with this filename
          {
            command += " filename:(" + request.Entities[0].Entity + ") ";
            break;
          }
        case "keyword": // Search within documents for this keyword expression
          {
            command += " " + request.Entities[0].Entity + " ";
            break;
          }
        case "builtin.datetime.date":
        // Retrieve based on the year a document was generated
          {
            char[] delimiterChars = { '.', ':' };
        List<string> allvalues = curEntity.Resolution.Values.ToList<string>();
             string val = allvalues[0];
             string[] vals = val.Split(delimiterChars);
             command += " lastmoddate:(" + vals[0] + ") ";
             break;
          }
        default:
          {
            break;
          }
        }
    }

设置返回给用户的响应的格式

可以为智能机器人应用程序返回的响应消息中的文本设置样式,或添加超链接/媒体内容等。这有助于提升智能机器人应用程序的易用性。下面列出了几个例子…

若要将字体样式设置为粗体,请只将 Azure 搜索返回的动态数据括在双星号 (**) 内:

foreach (SearchResult<PolicyRequest> result in eachGroup)
  {
    searchResponse += (counter) + ". **" + result.Document.PolicyRequestId + "**" +
      " on the insured :**" + result.Document.InsuredIdentifier + "**
      for an amount of :**" +
    result.Document.Currency + " " + result.Document.AssessedValue + "**
      has status: **" +
    result.Document.PolicyStatus + "**\n\n";
    counter++;
  }

若要向 Azure 搜索返回的保险单添加超链接,请向保险单 URL 添加 Link 属性:

foreach (SearchResult<PolicyDocument> result in results)
 {
   searchResponse += counter + ". Link: [" + result.Document.filename + "](" +
     result.Document.fileurl + ") \n";
   counter++;
 }

识别智能机器人应用程序用户

Bot Framework 根据对话上下文提供用户的通道 ID 和显示名称,但不提供用于登录通道(如 Skype)的用户名。因此,当用户开始与智能机器人应用程序进行交互时,应明确获取此类信息。前面提到的 FormFlow 对话框就用于提示用户提供此类信息。用户配置文件信息是根据帐户名称从 Azure 搜索中进行检索的。

为确保与智能机器人应用程序交互的用户是 FormDialog 中共享的 Microsoft 帐户名的所有者,可以在 Microsoft 帐户登录页中集成交互式登录,并仅在经过身份验证和验证后才提供对智能机器人应用程序的访问权限。这不在本文的讨论范围内。只需使用 SigninForm 对话框提示用户输入帐户名,并假设用户具有所声称的身份:

public static IForm<SigninForm> BuildForm()
  {
    return new FormBuilder<SigninForm>()
      .Message("I would need some information before we get started.")
      .Field(nameof(MicrosoftAccount))
      .Build();
  }

在 Bot State 中存储用户配置文件

如果用户在与智能机器人应用程序的对话刚开始时就被识别出来,用户配置文件对象会立即从 Azure 搜索中检索出来。此类信息存储在 Bot State 中,以便在用户申请安排现场查访时使用(其中一项示例用途)。用户的地址和联系号码在确认查访期间得到验证。

Bot Framework 提供了一个 API,按用户和对话将此类信息存储在 privateConversationData 上下文中,如图 8 所示。

图 8:存储用户配置文件

private async Task SignUpComplete(IDialogContext context, IAwaitable<SigninForm> result)
  {
----------------------------------------------
----------------------------------------------
  UserProfile profile =
    await LoadBotUserState(profileIdentityForm.MicrosoftAccount);
  if (profile == null)
  {
  message = $"Sorry, I could not locate your profile in our system.
    Please retry with the right Microsoft Account";
  }
  else
  {
    context.PrivateConversationData.SetValue<bool>("ProfileComplete", true);
    context.PrivateConversationData.SetValue<string>(
      "FirstName", profile.FirstName);
    context.PrivateConversationData.SetValue<string>("LastName", profile.LastName);
    context.PrivateConversationData.SetValue<string>("Address", profile.Address);
    context.PrivateConversationData.SetValue<string>("Phone", profile.Phone);
    context.PrivateConversationData.SetValue<string>(
      "CustomerId", profile.CustomerId);
    message = $"Thanks {profile.LastName},{profile.FirstName}
      for identifying yourself! \n\n
      Let me know how I could assist you.";
  }
    await context.PostAsync(message);
  }
    context.Wait(MessageReceived);
  }

对于每个请求,智能机器人应用程序都会与 Bot State 核实,以确保验证发出请求的用户的身份,并获得用户配置文件信息。

在本地使用 Bot Channel Emulator

智能机器人应用程序需要使用 Azure 搜索命名空间、访问密钥和索引名称才能执行用户请求。当智能机器人应用程序运行时,这些参数及其在 web.config 文件中设置的值都存储在 SearchParameters.cs 文件中。现在,可以运行 Visual Studio 解决方案了。

启动 Bot Channel Emulator,然后输入要为之定型 LUIS 模型的对话。图 9 展示了典型的用户交互应用场景。当智能机器人应用程序在本地环境中成功运行后,就可以将其发布到 Azure Web 应用中了。

在仿真器中运行智能机器人应用程序
图 9:在仿真器中运行智能机器人应用程序

将智能机器人应用程序部署到 Azure

在 Azure 门户中创建 Azure Web 应用,然后在“应用程序设置”中,添加 Visual Studio 解决方案的 web.config 文件中的 appSetting 键和值。

在将智能机器人应用程序发布到 Azure Web 应用之前,必须先在 dev.botframework.com/bots/new 注册此智能机器人应用程序。此过程会生成智能机器人应用程序 ID、Microsoft 应用 ID和密钥。将这些值添加到 Visual Studio 2015 中此智能机器人应用程序的 web.config 中,然后将此应用重新发布到 Azure。

在注册期间,对于重定向 URL 参数,请使用 Azure 中智能机器人应用程序的已启用 SSL 的 URL,然后添加“/api/messages”作为后缀。

跨通道启用智能机器人应用程序

dev.botframework.com/bots 的智能机器人应用程序注册表中,可以跨其他通道启用智能机器人应用程序。图 10 展示了跨 Skype 和 Slack 通道启用的智能机器人应用程序。为智能机器人应用程序启用 Slack 需要执行其他一些步骤。当你选择将智能机器人应用程序添加到此通道时,向导会启动,引导你执行所有相应步骤。

跨多个通道注册智能机器人应用程序
图 10:跨多个通道注册智能机器人应用程序

图 11 展示了从 Skype 访问的智能机器人应用程序。

从 Skype 访问智能机器人应用程序
图 11:从 Skype 访问智能机器人应用程序

总结

Microsoft Bot Framework 和其他支持技术(如 LUIS 和 Azure 搜索)为生成直观的智能机器人应用程序奠定了良好的基础,以便客户能够与业务线应用程序进行交互。每个服务在整个解决方案体系结构中所起到的作用确保了可以完善或改进各个组件,不受其他组件的影响。例如,可以将 LUIS 模型调整为支持更多语音样本,方便用户与智能机器人应用程序进行交互,同时核心智能机器人应用程序或 Azure 搜索中的内容不会有任何变更。部署后,无需额外更改任何代码,即可为智能机器人应用程序启用跨通道访问权限,还可以随时间推移更多通道,以扩增智能机器人应用程序的覆盖面和使用率。


Srikantan Sankaran是总部位于班加罗尔的印度 DX 团队中的一名技术宣传员。他与印度的众多独立软件开发商合作,帮助他们在 Microsoft Azure 上生成和部署解决方案。请通过 sansri@microsoft.com 与他联系。

衷心感谢以下 Microsoft 技术专家对本文的审阅: Sandeep Alur 和 Hemanth Kathuria
Sandeep Alur 是总部位于班加罗尔的印度 DX 团队的主要宣传员。

Hemanth Kathuria 是印度服务团队的高级顾问。