内容 Bot连接器的背景 使用模拟器持久性的第一个示例 机器人构建器 形式和真实的对话反应系统消息,真实的语言理解路易斯监督学习集成自动创建 一个真实解决方案的架构总结了历史 介绍 在今年的Build大会上,微软透露了部分应对谷歌和苹果在移动领域主导地位的战略:智能机器人。这种策略本质上是将服务从特定应用程序中解耦。我们将不再使用特定的应用程序或网站来访问某些服务,而只是打开Skype、Telegram或其他一些程序或服务来启动通信通道。 首先,这看起来像是冗余和倒退。然而,事实上,这个系统提供了很多优点。最大的好处是对话应该是尽可能自然的。想象一个能够进行对话的谷歌搜索框。此外,服务可以在不知道API细节甚至不知道彼此端点的情况下彼此通信。最后,智能服务实际上可以被放置在顶部,为机器人提供一些信息,减少人工方面的重复。 后者是许多有趣演示的基础。与Cortana集成的Skype能够了解我们的旅行计划,能够联系另一个机器人,发起对话,并向酒店机器人提供必要的信息,比如我们的会员ID、旅行日期和房间偏好。 背景 现在,像预订房间、点披萨或搜索特定信息这样的事情似乎很有趣,是的,但为顾客提供真正的价值可能微不足道。然而,在物联网和家庭自动化领域工作时,我将尝试向您展示一些围绕微软Bot框架的更有趣的场景。 在最坏的情况下,拥有一个bot解决方案是另一个需要维护的项目。在最好的情况下,机器人层正好位于我们的解决方案和客户之间,可能用于语音识别、聊天、服务连接,甚至是命令行接口。现实通常介于这两个极端之间。 那么接下来的内容是什么呢?我们将从基本知识开始,了解微软的解决方案,以提供有用的机器人。然后,我们将进一步了解为我们提供更强大抽象的Bot构建器。由于语言处理的本质是对LUIS的快速介绍。最后,我们将看一看使用Bot框架通过聊天或语音实现家庭自动化的真实项目的一部分。 机器人连接器 bot连接器是将我们的bot连接到微软基础设施的中心组件。然后,基础设施提供与各种渠道的连接,如Skype、邮件、文本或各种其他可能的渠道。我们不需要为这些不同的通道调整我们的应用程序。下面的图片说明了这个概念。 bot连接器也以c#项目模板/库的形式提供。但是,不需要包含库,也不需要使用项目模板。整个架构是基于rest的。对框架或语言没有限制。唯一的约束是由端点名称和公开的行为提供的。 微软也非常慷慨的托管要求或我们的机器人。当然,如果我们可以在微软Azure上托管它,但我们没有必要这样做。实际上,只要可以从bot连接器服务访问端点,我们就可以继续工作。 说了这么多,让我们动手玩一下微软的机器人框架。 第一个样品 我们将先看一个示例,而不是以更学术化的方式浏览API和发现功能。为了使示例工作,我们必须从Microsoft下载并安装项目模板。 安装Visual Studio 2015 Update 1或更高版本。所有VS扩展都应该是最新的。下载及安装Bot应用程式模板: 从aka.ms/bf-bc-vstemplate下载文件。保存文件到%USERPROFILE%\Documents\Visual Studio 2015\Templates\ProjectTemplates\Visual c#。 使用Visual c# templates中的新Bot应用程序模板。 一旦我们基于这个模板创建了一个新项目,我们就会得到一个快速回送服务器,它只回复发送字符的数量。下面的代码包含端点定义和核心功能。 隐藏,复制Code

[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)的浏览器。当然还有其他方法,但所有这些方法都包括提供正确的方案and信息作为机器人连接器。这是乏味的。不过,有一种简单的方法:有人编写了一个小型WPF应用程序,它已经像bot连接器那样序列化了消息。此外,它还按预期反序列化响应。 简而言之,这个应用程序允许我们使用机器人,而不需要实际将其链接到机器人连接器。这对于调试来说非常好,当然也提高了工作效率。该应用程序称为Bot框架仿真器。可以通过aka从微软主页下载。ms/bf-bc-模拟器的形式,单击一次应用程序。 在应用程序上设置断点的工作原理与web应用程序中的任何其他调试步骤相同。一旦收到消息,我们就会调用应用程序代码并采取行动。 让我们使用仿真器来展示一个由Bot框架缓解的特定场景:存储一条消息的状态,以便在对话期间持久化。 持久性 保存状态的主题是bot平台最需要的功能之一。毕竟,我们需要以某种方式获取在以前的消息中收集的信息,以推断当前消息中缺失的信息片段。这种对话意识对于为用户提供充分的体验非常重要。 Microsoft Bot框架提供了不同类型的持久性。我们可以在通道中存储每个用户、每个通道或每个用户的数据。其机制总是相同的,这类似于使用cookie。传入消息包含适用于此消息的先前设置的条目,而传出消息包含更改过的条目。 让我们通过调整示例来看看它是怎样的。在本例中,我们使用GetBotPerUserInConversationData扩展方法来检索长度属性存储的数据。对SetBotPerUserInConversationData扩展方法的调用将一些数据存储在持久层中。 隐藏,复制Code

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框架之上构建一个提供如此有用功能的库之前,我们应该先看看Bot构建器。 机器人构建器 机器人建造者是微软的秘密武器,让我们描绘出真正强大的对话。它附带了几个方便的类和扩展方法。在接下来的部分中,我们将了解一些提供的功能。 我们可以从NuGet获得Bot Builder。这里有最新版本。请注意,本文引用的是bot构建器的v1版本(因此链接到v1.2.3)。显然,API的某些部分在后来的版本中发生了变化,但是概念仍然是一样的。 形式和真实的对话 在表单元素出现之前,web是静态的。然而,正如我们今天所知,收集和传输信息的能力是网络的独特卖点。只有谷歌或Facebook这样的公司才会强调收集信息的重要性。 为了让我们的机器人真正高效和有用,我们需要一种方法来收集缺失的信息或有序的收集数据。这里,表单的概念又派上了用场。 基本思想很简单:考虑一个需要填充的类型。我们只需要检查每个字段,查看字段的类型,并尝试在用户的帮助下构造类型。这个过程可以是递归的。 让我们考虑以下类: 隐藏,复制Code

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

在这里,我们使用了从简单枚举字段到类列表的各种类型。下面的子类型用于建模前面显示的类。 隐藏,收缩,复制Code

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;
}

填充这种类型的对话是什么样子的呢? 这个想法是使用微软的机器人构建器来完成这项艰巨的工作。代码是简单的装饰类为[Serializable]和改变我们的消息处理程序看起来像: 隐藏,复制Code

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

    // ...
}

其中我们使用以下两个辅助函数。 隐藏,复制Code

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构建器提供的Conversation助手。在这里,我们需要通过指定接收到的消息来标识要响应的机器人。我们还需要将回调传递给对话框创建器,反过来,对话框创建器也需要一个回调来创建表单。代码中有相当多的回调。 然而,一旦所有这些回调都正确地连接起来,我们就可以设置一个相当轻松的表单——由bot网络覆盖。一旦一切就绪,我们可以通过回调获得结果,例如: 隐藏,复制Code

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

既然我们已经看到了机器人构建器,现在是时候揭开这些系统消息的神秘面纱了。 对系统消息作出反应 还没有讨论的一件事是消息类型。直到现在所有的信息电子邮件被认为是聊天信息,但这只是其中一种——即使是最常见的——类型的信息。其他一些类型也被定义为固定的系统消息。bot连接器胶水使用这些消息通知我们某些事件。 这些消息背后的想法是让我们有能力塑造符合我们需要的连接器。例如,当用户连接或断开连接时,我们可能希望更改数据库中的某些内容,或者从数据库向连接器获取一些数据。 默认情况下,模板已经提供了以下样板代码。 隐藏,收缩,复制Code

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消息实现不同的响应,但实际上应该使用给定的代码。当从bot连接器发出ping时,该命令确保将我们的bot标记为活动的。此外,我们可以记录这个标准调用。 其他类型的消息被有意地保留为空。我们肯定应该做的一件事是,一旦机器人或用户从会话中删除,或一旦会话结束,就删除过期的数据。 真正的语言理解 机器人的问题通常是,他们被认为是“愚蠢的”。我们输入一些文本片段,如果它有一点偏离常规,我们就会得到一个错误。这种容忍和文本理解的程度显然低于正常对话的预期。 微软不久前意识到这个问题,并建立了一个解决方案来解决它:它被称为语言理解智能服务out short LUIS。这是微软“认知服务”的一部分,人们可能对其前身“牛津项目”(project Oxford)很熟悉。它的核心是一个带有语言知识层的机器学习解决方案。 路易斯 为了使用LUIS,我们需要在官方LUIS网站上有一个帐户。在这里,我们可以创建新项目、导入项目或编辑现有项目。项目只是以JSON文件的形式存储,提供了很大的灵活性和从Excel表格生成项目的可能性。 下面的图片概括了路易斯的一切。它允许我们将语言识别中最重要的部分外包给外部服务。 作为一个简单的演示,我们登录到LUIS网站并使用英语创建一个新项目。语言的选择很重要。只有知道如何进行自然语言处理(NLP)的层才能进行文本识别。然而,这一层将绑定文法规则和需要选择特定语言环境的语言细节。 在我们的简单演示中,我们将构建一个快速的酒店搜索识别API。我们的目标是检测在城市中搜索酒店的意图。我们想知道酒店搜索是否意味着(或不是),以及我们应该在哪个城市搜索。这为我们提供了LUIS的两个最重要的构建模块:Intent和entity。意图可能包含实体,也可能不包含实体,没有意图无法检测到实体。 学习意图与我们的无实体以同样的方式工作。提供所谓的话语,它代表了一个给定意图的样本。由于一个新的LUIS项目开始时除了NLP层之外没有任何知识,所以我们还需要提供输入话语的映射,即。,目的是什么,如果有的话包含哪些实体。 监督式学习 理解路易斯的关键是理解监督学习。当我们不断地遵循一个循环,面对一个问题,提出一个解决方案,并被真实的答案确认或纠正,监督学习就被给出了。然而,这种方法的问题在于,主管不仅要做很多事情,而且还被认为是无所不知的。系统没有从用户输入中学习,即使我们可以使用用户输入来改进系统。在这种情况下,我们告诉系统哪些用户语句被正确地检测到了,哪些语句应该以不同的方式进行评估(以及如何评估)。 让我们用之前的样本来试一下。首先,我们给系统一个运行的最小值。有了单一的意图和实体,我们需要提供三种话语: 隐藏,复制Code

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

对于培训系统和发布API来说,这已经足够了。我们随后看到的查询测试器还为我们提供了URL,以便从示例bot连接器进行一些RESTful调用。 单击链接后,我们将看到响应。这个响应形成了我们自己的类模型的基础。然后,这些类将携带从查询LUIS接收到的数据。 携带模型的类可以从示例查询的响应中的JSON生成。让我们假设使用了以下查询:“柏林的酒店”。一个标准的GET请求到https://api.projectoxford.ai/luis/v1/application 隐藏,复制Code

{
  "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生成类”将其粘贴到一起,就得到了一组可以使用的类(尽管进行一些重构会很好)。 在使用LUIS API之前通过bot连接器,我们需要将其集成到消息接收器中。 集成 完成所有工作后,我们应该扩展bot连接器,如下所示,在LUIS的帮助下评估用户消息。然后,我们准备使用模拟器提供一些用户定义的输入。这里我们使用以下代码,其中T将被设置为对应的json定义的类: 隐藏,复制Code

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)需要全局定义。 在我们的情况下,路易斯的集成与显示的代码可能很容易调整消息接收器成为: 隐藏,复制Code

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 ...");
    }
    
    // ...
}

现在让我们来谈一谈,看看这个系统能做些什么。 隐藏,复制Code

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的网站,看一看“建议”。这些建议是以前注意到的用户查询,但是还没有被看到。因此,系统还不能确定它是否推断出正确的答案。因此,我们看到的都是需要验证的建议。我们要么接受给出的答案,要么改正它。 自动创建 现在我们可以把一切留给用户,我们只需要偶尔检查一下是否有什么新东西需要考虑。然而,即使我们可以从一个糟糕的用户体验开始,我们通常想要避免这样的行为——即使它只是很短的一段时间。相反,我们想马上拿出一个像样的解决方案。 这里的关键是要有一个团队(或者至少是一个专门的人)用所有可能的话语填充一种数据库,以识别每个以前认识的实体和意图。然后将这个数据库(它可以像CSV文件或Excel电子表格那样简单)转换为LUIS项目。LUIS项目文件仅为JSON,可能如下所示。 对于Excel电子表格,我们可以使用一些工具来进行转换。假设我们有一个电子表格,它包含一个单独的表格,其中的列表示话语、意图、所有实体和更多的数据。 我们可以使用优秀的EEPlus库来执行从原始电子表格到LUIS项目的转换。我们只需要确保将其转换为一个对象模型,该对象模型可以以为LUIS项目指定的JSON形式序列化。 生成的项目可以很容易地导入。 这样的工作流程使得不依赖于LUIS网站就可以保存和恢复项目。 一个真正的解决方案的架构 现在的问题是:使用Microsoft Bot框架的真正应用程序是什么样子的?在本节中,我们将查看作为代理的应用程序中的相关部分,该应用程序允许用户与他们的智能家居解决方案通信。智能家居解决方案的细节将被隐藏,然而,概述的架构和显示的代码仍处于预生产阶段,离实现不远了。 解决方案的体系结构如下图所示。我们可以看到这个应用程序由几个部分组成,每个部分都值得讨论。由于本文是关于Microsoft Bot框架的,所以我们只讨论智能家居代理。整个项目可能会在另一篇文章中更详细地描述(取决于兴趣)。 智能家居代理是一个简单的节点。在用户的本地环境中运行的基于js的web服务器。它负责与智能家居服务通信(智能家居服务知道如何与设备通信),并为用户提供一个网页,供用户输入所需的凭证、帐户和相关通道。 智能家庭代理建立了一个到托管在微软Azure中的智能家庭机器人服务的P2P连接。该连接基于websocket协议,并允许bot服务将通道中的用户与智能家居(或者更具体地说,智能家居代理)关联起来。连接必须对使用证书进行身份验证和验证的双方进行加密。 智能家居机器人服务使用的是微软的机器人连接器模板。为了方便,它被托管在Azure中,也可以像前面讨论的那样部署在本地。然后使用bot连接器来建立与各个通道的耦合。框架还用于为我们提供持久性和大量有用的服务和信息。 在我们的应用程序中,通过包含Bot构建器库,我们使用了各种各样的Bot框架。一些交互可能需要填写表单(添加新的智能家居规则,根据条件触发操作),其他交互可以在一行中完成。如果没有收到足够的信息,我们可能想要从对话中产生的上下文中推断缺失的部分。因此,我们需要前一条消息的一些特定于用户的字典。 可能执行关键操作的侵入性操作需要在用户端进行确认。否则,我们可能会引起误解而采取这些行动。可以在偶然情况下调用更宽大的行动。读取传感器数据永远不会被认为是有害的。 bot服务的主要任务是将用户意图转换为智能家居代理的API调用。 结论 微软的Bot框架给了我们很多选择的可能性和自由。加上强大的认知服务,我们拥有了一项非常有趣的技术。 就我个人而言,我认为这个框架可能会改变游戏规则。它有能力在许多服务相关的产品中扮演中心的角色。现在真正的问题不是技术的适用性,而是相关的业务案例。 历史 | 19.06.2016 v1.0.2 |更新NuGet链接| 23.08.2016