65.9K
CodeProject 正在变化。 阅读更多。
Home

Microsoft Bot Framework 介绍

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (50投票s)

2016年6月13日

CPOL

17分钟阅读

viewsIcon

192172

downloadIcon

2163

本文介绍了使用 Microsoft Bot Framework 创建有用机器人最重要的细节。

目录

  1. 引言
  2. 背景
  3. 机器人连接器
    1. 第一个示例
    2. 使用模拟器
    3. 持久化
  4. 机器人生成器
    1. 表单和实际对话
    2. 响应系统消息
    3. 真实语言理解
    4. LUIS
    5. 监督学习
    6. 集成
    7. 自动化创建
  5. 真实解决方案的架构
  6. 结论
  7. 历史

引言

在今年的 Build 大会上,微软公布了其应对 Google 和 Apple 在移动领域主导地位的部分策略:智能机器人。该策略本质上是将服务与特定应用程序解耦。我们不再需要使用特定的应用程序或网站来访问某些服务,而是可以直接打开 Skype、Telegram 或其他程序/服务来启动通信渠道。

起初,这可能看起来是冗余和倒退。但实际上,这种系统提供了许多优势。最大的好处是对话可以尽可能自然。想象一个能够进行对话的 Google 搜索框。此外,服务之间可以相互通信,而无需了解 API 细节,甚至无需知道彼此的端点。最后,可以将智能服务放置在顶层,为机器人提供信息,从而减少人为方面的重复工作。

后者是许多有趣演示的基础。集成了 Cortana 的 Skype 能够了解我们的旅行日程,可以联系另一个机器人,发起对话,并向酒店机器人提供必要的个人信息,如会员 ID、旅行日期和房间偏好。

背景

现在,像预订房间、订披萨或搜索特定信息这样的事情可能很有趣,但对于客户来说可能过于琐碎,无法提供真正的价值。然而,在物联网和家庭自动化领域工作,我将尝试向您展示一些围绕 Microsoft Bot Framework 的更interesting的场景。

最坏的情况下,拥有一个机器人解决方案只是又一个需要维护的项目。在最好的情况下,机器人层位于我们的解决方案和客户之间,可以是语音识别、聊天、服务连接,甚至是命令行接口。现实情况通常介于这两个极端之间。

那么接下来会讲什么呢?我们将从理解微软提供有用机器人的解决方案的基础知识开始。然后,我们将更深入地研究 Bot Builder,它为我们提供了更强大的抽象。由于语言处理至关重要,因此必须快速介绍 LUIS。最后,我们将看一个使用 Bot Framework 通过聊天或语音实现家庭自动化的真实项目的部分内容。

机器人连接器

机器人连接器是将我们的机器人连接到微软基础架构的中心组件。该基础架构随后提供与各种通道的连接,例如 Skype、电子邮件、文本或各种其他可能性。我们无需为这些不同的通道调整我们的应用程序。下图说明了这一概念。

Bot Connector Architecture

机器人连接器还以 C# 项目模板/库的形式提供。但是,不需要包含该库,也不需要使用项目模板。整个架构是基于 REST 的,也就是说,对框架或语言没有限制。唯一的限制由端点名称和公开的行为决定。

微软在托管我们机器人的要求方面也非常慷慨。当然,我们可以在 Microsoft Azure 中托管它,但并非必须。实际上,只要端点可以从机器人连接器服务访问,我们就可以开始工作了。

说到这里,让我们动手玩转 Microsoft Bot Framework。

第一个示例

与其以更学术的方式逐一了解 API 并发现其功能,不如我们先看一个示例。为了使示例正常工作,我们必须从微软下载并安装项目模板。

  1. 安装 Visual Studio 2015 Update 1 或更高版本。
  2. 所有 VS 扩展都应更新到最新。
  3. 下载并安装 Bot Application 模板
    1. aka.ms/bf-bc-vstemplate 下载文件。
    2. 将文件保存到 %USERPROFILE%\Documents\Visual Studio 2015\Templates\ProjectTemplates\Visual C#
  4. 使用 Visual C# 模板中的新 Bot Application 模板。

The Bot Connector Template

一旦我们基于此模板创建一个新项目,我们就会得到一个快速的回显服务器,它只回复发送字符的数量。以下代码包含端点定义和核心功能。

[BotAuthentication]
public class MessagesController : ApiController
{
    public async Task<Message> Post([FromBody]Message message)
    {
        if (message.Type == "Message")
        {
            var length = (message.Text ?? String.Empty).Length;
            return message.CreateReplyMessage($"You sent {length} characters");
        }
        else
        {
            return HandleSystemMessage(message);
        }
    }

    // Handle System Message
}

端点监听在 /message。API 对 POST 请求敏感。提供的参数是从请求正文中序列化而来的,请求正文以 JSON 字符串的形式发送。

我们现在如何与我们的机器人交流?

使用模拟器

一种方法是使用浏览器和 PostMan 等工具。当然还有其他方法,但所有这些方法都包括提供正确的方案和信息,就像机器人连接器会做的那样。这很繁琐。但是,有一个简单的方法:有人编写了一个小型的 WPF 应用程序,它可以像机器人连接器一样序列化消息。此外,它还可以按照预期反序列化响应。

简而言之,这个应用程序允许我们在不实际将其链接到机器人连接器的情况下使用我们的机器人。这对于调试目的非常有用,并且无疑提高了生产力。该应用程序称为 Bot Framework Emulator。可以从微软主页下载,通过 aka.ms/bf-bc-emulator 以 click once 应用程序的形式提供。

The Microsoft Bot Framework Emulator

在应用程序上设置断点与 Web 应用程序中的任何其他调试步骤相同。一旦收到消息,就会调用我们的应用程序代码,我们可以采取行动。

Debugging a Microsoft Bot framework application

让我们使用模拟器来演示 Bot Framework 缓解的一个特定场景:在一次消息中存储状态,以便在对话中持久化。

持久性

保留状态是机器人平台最受需求的功能之一。毕竟,我们需要某种方式来利用从先前消息中收集的信息来推断当前消息中缺失的信息。这种对话感知对于提供令人满意的用户体验至关重要。

Microsoft Bot Framework 提供了不同类型的持久性。我们可以按用户、按通道或按用户在通道中存储数据。机制始终相同,类似于使用 cookie。传入的消息包含适用于该消息的先前设置的条目,而传出的消息包含已更改的条目。

让我们通过调整示例来看看这是如何实现的。在这种情况下,我们使用 GetBotPerUserInConversationData 扩展方法来检索 length 属性的已存储数据。调用 SetBotPerUserInConversationData 扩展方法会将一些数据存储在持久性层中。

public async Task<Message> Post([FromBody]Message message)
{
    if (message.Type == "Message")
    {
        var previousLength = message.GetBotPerUserInConversationData<Int32?≫("length");
        var previousMessage = previousLength.HasValue ? $"(previously sent {previousLength.Value} characters)" : String.Empty;
        var length = (message.Text ?? String.Empty).Length;
        var reply = message.CreateReplyMessage($"You sent {length} characters {previousMessage}");
        reply.SetBotPerUserInConversationData("length", length);
        return reply;
    }

    // ...
}

当然,模拟器中也复制了持久性层。因此,我们可以轻松地测试代码。

Bot Connector Persistence Example

有了持久性,我们就可以构建更强大的对话方案,这些方案可以记住事物,并且只需询问缺失的部分即可通过询问缺失的部分来完成一些必需的知识图。但等等,在我们开始构建一个在 Bot Framework 之上提供此类有用功能的库之前,我们应该先看看 Bot Builder。

机器人生成器

Bot Builder 是微软让我们勾勒出真正强大对话的秘密武器。它提供了许多方便的类和扩展方法。在接下来的部分中,我们将看看它提供的一些功能。

我们可以从 NuGet 获取 Bot Builder。最新版本可在此处找到 这里。请注意,本文参考的是 bot builder 的 v1 版本(因此链接到 v1.2.3)。显然,API 的某些部分在更高版本中发生了变化,但概念仍然相同。

表单和实际对话

在表单元素出现之前,Web 一直是静态的。然而,收集和传输信息的能力是我们今天所知的 Web 的独特卖点。Google 或 Facebook 等公司都强调了收集信息的重要性。

为了使我们的机器人真正高效且有用,我们需要一种方法来收集缺失的信息或以有序的方式收集数据。在这里,表单的概念再次派上用场。

基本思想很简单:考虑一个需要填写的类型。我们只需要遍历每个字段,查看字段的类型,并尝试在用户的帮助下构造该类型。该过程可能是递归的。

让我们考虑以下类

public class CarSelection
{
    public MultimediaEquipment Multimedia;
    public String Name;
    public CarModel Model;
    public List<CupHolderChoice> CupHolders;
}

在这里,我们使用了从简单的 enum 字段到类列表的广泛范围。以下子类型用于建模前面显示的类。

public enum SeatChoice
{
    Leather,
    Cloth
}

public enum CoreSystem
{
    VideoWithGps,
    VideoOnly,
    GpsOnly,
    Basic
}

public enum CarModel
{
    Basic,
    Standard,
    Premium
}

public enum CupHolderChoice
{
	Tiny,
	Standard,
	Large,
	American
}

public class MultimediaEquipment
{
    public CoreSystem System;
    public Int32? VideoDevices;
}

填写此类型的对话会是什么样子?

Conversation covering a form using the Microsoft Bot Builder

这里的想法是利用 Microsoft Bot Builder 来完成繁重的工作。代码很简单,只需将类装饰为 [Serializable] 并将消息处理程序更改为如下所示

public async Task<Message> Post([FromBody]Message message)
{
    if (message.Type == "Message")
    {
        return await Conversation.SendAsync(message, BuildDialog);
    }

    // ...
}

我们在这里使用以下两个辅助函数。

private static IDialog<CarSelection> BuildDialog()
{
    return Chain.From(() => FormDialog.FromForm(BuildForm));
}

private static IForm<CarSelection> BuildForm()
{
    return new FormBuilder<CarSelection>()
        .Message("Simple conversation demo!")
        .Build();
}

要使用的主要类是 Microsoft Bot Builder 提供的 Conversation 助手。在这里,我们需要通过指定收到的消息来识别要响应的机器人。我们还需要将回调传递给对话创建者,对话创建者又需要另一个回调来创建表单。代码中有相当多的回调。

尽管如此,一旦所有这些回调都正确连接,我们就设置了一个相当轻松呈现的表单,该表单由机器人网络覆盖。一旦一切就绪,我们就可以通过回调获得结果,例如

private static IForm<CarSelection> BuildForm()
{
    return new FormBuilder<CarSelection>()
        .Message("Simple conversation demo!")
        .OnCompletionAsync((session, car) => Task.FromResult(false))
        .Build();
}

现在我们已经了解了 Bot Builder,是时候揭开这些系统消息的神秘面纱了。

响应系统消息

有一点还没有讨论过,那就是消息的类型。到目前为止,所有传入的消息都被视为聊天消息,但这只是一种消息,尽管是最常见的一种。还有一些其他类型被定义为固定为系统消息。这些消息由机器人连接器胶合层用于通知我们有关某些事件。

这些消息背后的想法是让我们能够根据我们的需求定制连接器。例如,我们可能希望在用户连接或断开连接时在数据库中更改某些内容或从数据库中获取一些数据到连接器。

Bot Framework System Message

默认情况下,模板已经提供了以下样板代码。

private Message HandleSystemMessage(Message message)
{
    if (message.Type == "Ping")
    {
        var reply = message.CreateReplyMessage();
        reply.Type = message.Type;
        return reply;
    }
    else if (message.Type == "DeleteUserData")
    {
    }
    else if (message.Type == "BotAddedToConversation")
    {
    }
    else if (message.Type == "BotRemovedFromConversation")
    {
    }
    else if (message.Type == "UserAddedToConversation")
    {
    }
    else if (message.Type == "UserRemovedFromConversation")
    {
    }
    else if (message.Type == "EndOfConversation")
    {
    }

    return null;
}

尽管我们可以实现对 Ping 消息的不同响应,但我们基本上应该使用提供的代码。这可以确保在收到来自机器人连接器的 ping 时将我们的机器人标记为活动状态。此外,我们可以记录此标准调用。

其他类型的消息被故意留空。我们应该做的一件事是,一旦机器人或用户已从对话中移除或对话已结束,就移除过期的数据。

真实语言理解

机器人通常的问题是它们被视为“愚蠢”。我们输入一些文本片段,如果稍微偏离常规,就会收到错误。容忍度和文本理解水平肯定低于任何对正常对话的期望。

微软早就意识到了这个问题,并构建了一个解决方案来解决它:它被称为语言理解智能服务,简称 LUIS。它是微软认知服务的一部分,该服务可能在它们之前的名称 Project Oxford 下为人所知。其核心是机器学习解决方案,带有语言知识层。

LUIS

为了使用 LUIS,我们需要在官方 LUIS 网站上有一个帐户。在这里,我们可以创建新项目、导入项目或编辑现有项目。项目只是以 JSON 文件的形式存储,提供了极大的灵活性,并且可以生成项目,例如从 Excel 表格中。

下图概述了 LUIS 的作用。它允许我们将语言识别最重要的部分外包给外部服务。

LUIS Services

对于一个简单的演示,我们登录到 LUIS 网站 并创建一个新项目,使用英语作为语言。语言选择很重要。文本识别只有在具有自然语言处理 (NLP) 能力的层时才可能。然而,这一层将受制于语法规则和语言特定规则,这些规则需要选择特定的区域设置。

对于我们的简单演示,我们将构建一个快速的酒店搜索识别 API。我们的目标是检测搜索酒店到某个城市的意图。我们想知道酒店搜索是否是故意的(或不是),以及我们应该搜索哪个城市。这构成了 LUIS 的两个最重要的构建块:意图和实体。意图可以包含实体,也可以不包含实体;没有意图就无法检测到实体。

学习带有或不带实体的意图以相同的方式工作。它们提供所谓的“话语”(utterances),代表给定意图的样本。由于新的 LUIS 项目在 NLP 层之外没有任何知识,因此我们还需要提供话语的映射,即意图是什么,以及包含哪些实体(如果有的话)。

监督学习

理解 LUIS 的关键是理解监督学习。当我们在面对问题、提出解决方案、并由真实答案确认或纠正的循环中不断进行时,就会出现监督学习。然而,这种方法的缺点是,监督者不仅要做很多工作,而且被认为是全知全能的。即使我们可以使用用户输入来改进系统,系统也不会从用户输入中学习。在这种情况下,我们告诉系统哪些用户陈述被正确检测到,哪些应该被评估为不同(以及如何评估)。

所以,让我们用之前开始的示例来尝试一下。首先,我们给系统一个最基本的东西来上线。通过一个意图和一个实体,我们需要提供三个话语

stays in brasov.
find me rooms in london.
room in berlin.

这足以训练系统并发布 API。我们之后看到的查询测试器还提供了用于从我们的示例机器人连接器进行 RESTful 调用的 URL。

Making a REST Call with LUIS

单击链接后,我们将看到响应。此响应是我们自己类模型的基石。然后,这些类将携带从 LUIS 查询接收到的数据。

可以从示例查询的响应中的 JSON 生成用于承载模型的类。假设我们使用了以下查询:“hotels in berlin”。对 https://api.projectoxford.ai/luis/v1/application?id={id}&subscription-key={key}&q=hotels%20in%20berlin 的标准 GET 请求会产生

{
  "query": "hotels in berlin",
  "intents": [
    {
      "intent": "HotelSearch",
      "score": 0.999999046
    },
    {
      "intent": "None",
      "score": 0.126684889
    }
  ],
  "entities": [
    {
      "entity": "berlin",
      "type": "Location",
      "startIndex": 10,
      "endIndex": 15,
      "score": 0.779263258
    }
  ]
}

在 Visual Studio 中粘贴此内容并选择“从 JSON 生成类”的特殊选项,我们可以得到一组可以使用的类(尽管进行一些重构会很好)。

Pasting JSON as classes in Visual Studio 2015

在机器人连接器使用 LUIS API 之前,我们需要将其集成到消息接收器中。

集成

在完成所有工作后,我们应该像这样扩展机器人连接器以借助 LUIS 评估用户消息。然后,我们就可以使用模拟器提供一些用户定义的用户输入了。这里我们使用以下代码,其中 T 将被设置为相应的 JSON 定义类

public static async Task<T> RequestAsync<T>(string input)
{
    var strEscaped = Uri.EscapeDataString(input);
    var url = $"https://api.projectoxford.ai/luis/v1/application?id={id}&subscription-key={key}&q={strEscaped}";

    using (var client = new HttpClient())
    {
        var response = await client.GetAsync(url);

        if (response.IsSuccessStatusCode)
        {
            var content = await response.Content.ReadAsStringAsync();
            return JsonConvert.DeserializeObject<T>(content);
        }
    }

    return default(T);
}

应用程序 ID(id)和订阅密钥(key)需要全局定义。

在我们的例子中,将 LUIS 与所示代码集成可能就像调整消息接收器一样简单:

public async Task<Message> Post([FromBody]Message message)
{
    if (message.Type == "Message")
    {
        var response = await Luis.RequestAsync<LuisResponse>(message.Text);
        var intent = response.Intents.FirstOrDefault(m => m.Score > 0.5);
        var city = response.Entities.FirstOrDefault();

        if (intent.Intent == "HotelSearch" && city != null)
        {
            return message.CreateReplyMessage($"Looking for hotels in {city.Entity}?");
        }

    	return message.CreateReplyMessage("Sorry, I didn't understand ...");
    }
    
    // ...
}

现在让我们来聊聊,看看系统有什么能力。

Me: Find me hotels in Berlin.
Bot: Sorry, I didn't understand ...
Me: Show me rooms in Munich.
Bot: Sorry, I didn't understand ...
Me: Hotels in Brussels.
Bot: Sorry, I didn't understand ...

这看起来并不太好,但是请记住,系统只知道最基本的内容。我们现在可以回到 LUIS 网站,查看“建议”。建议是之前注意到的用户查询,这些查询尚未被识别。因此,系统还不确定它是否推断出了正确的答案。因此,我们看到的所有建议都需要进行验证。我们可以接受给定的答案,也可以纠正它。

LUIS Utterance Suggestions

自动化创建

现在我们可以将一切都交给用户,我们只需要偶尔检查一下是否有新的东西需要考虑。然而,即使我们可以从糟糕的用户体验开始,我们通常也想避免这种行为——即使只是很短的时间。相反,我们想立即提出一个体面的解决方案。

这里的关键是要有一个团队(或至少一个专人)来填充一个数据库,其中包含所有可能的话语,以识别每一个先前识别的实体和意图。这个数据库,可以像 CSV 文件或 Excel 电子表格一样简单,然后被转换为 LUIS 项目。LUIS 项目文件只是 JSON,可能看起来像这样。

对于 Excel 电子表格,我们可以使用一些工具来执行转换。假设我们有一个电子表格,其中包含一个工作表,列表示话语、其意图、所有实体以及更多数据。

我们可以使用优秀的 EEPlus 库 来执行从原始电子表格到 LUIS 项目的转换。我们只需要确保将其转换为一个对象模型,该模型可以被序列化为为 LUIS 项目指定的 JSON 格式。

生成的项目可以轻松导入。

LUIS Import Project

这种工作流程使得保存和恢复项目成为可能,而不会依赖于 LUIS 网站。

真实解决方案的架构

此时的问题无疑是:一个使用 Microsoft Bot Framework 的真实应用程序是什么样的?在本节中,我们将查看一个充当代理以允许用户与他们的智能家居解决方案通信的应用程序的相关部分。智能家居解决方案的细节将被隐藏,然而,概述的架构和显示的 C# 代码处于预生产阶段,离实现不远了。

解决方案的架构如下图所示。我们可以看到应用程序由几个部分组成,每个部分都值得讨论。由于文章是关于 Microsoft Bot Framework 的,我们将只涉及智能家居代理。整个项目可能会在另一篇文章中更详细地描述(取决于兴趣)。

SmartHome Bot Architecture

智能家居代理是一个简单的基于 Node.js 的 Web 服务器,运行在用户的本地环境中。它负责与智能家居服务(它们知道如何与设备通信)进行通信,并为用户提供一个网页来输入所需的凭据、帐户和相关通道。

智能家居代理与托管在 Microsoft Azure 中的智能家居机器人服务建立 P2P 连接。连接基于 WebSocket 协议,允许机器人服务将通道中的用户与智能家居(或更具体地说,智能家居代理)相关联。连接必须使用证书进行加密,双方都使用证书进行身份验证和验证。

智能家居机器人服务使用微软的机器人连接器模板。它为了方便托管在 Azure 中,也可以如前所述部署在本地。然后使用机器人连接器来建立与各种通道的耦合。该框架还用于为我们提供持久性以及许多有用的服务和信息。

在我们的应用程序中,我们使用 Bot Framework 的全部功能,包括 Bot Builder 库。某些交互可能需要填写表单(添加新的智能家居规则,根据条件触发操作),而其他交互可以在一行中完成。如果收到的信息不足,我们可能希望从对话中生成的上下文中推断出缺失的部分。因此,我们需要相当多的用户特定于之前消息的字典。

执行关键操作的侵入性操作需要用户进行确认。否则,我们可能会因误解而触发这些操作。更温和的操作可以偶然调用。读取传感器数据从未被认为是有害的。

机器人服务的主要任务是将用户意图转换为智能家居代理的 API 调用。

结论

Microsoft Bot Framework 为我们提供了大量的可能性和选择自由。结合强大的认知服务,我们手中拥有了一项非常有趣的技术。

我个人认为该框架是一个可能的颠覆者。它能够在许多服务相关产品中扮演核心角色。现在的问题不再是技术的适用性,而是相关的业务案例。

历史

  • v1.0.0 | 初始发布 | 2016.06.13
  • v1.0.1 | 修正了一些拼写错误 | 2016.06.19
  • v1.0.2 | 更新了 NuGet 链接 | 2016.08.23
© . All rights reserved.