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

Microsoft Bot Framework 的演变

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (36投票s)

2017年3月27日

CPOL

10分钟阅读

viewsIcon

73264

downloadIcon

758

一篇关于人工智能聊天机器人应用程序开发的有趣文章。

世代

聊天机器人是一种可以模拟与人类对话的软件程序。最早也是最著名的聊天机器人之一(在网络出现之前)是 Eliza,一个假装是心理治疗师并用其他问题回答问题的程序。

Red 和 Andrette 是两个早期程序的名称,它们可以自定义来回答寻求产品服务的用户的问题。这种程序有时被称为虚拟代表或虚拟服务代理。

概述

基于 Microsoft Bot Framework 的发展演变,我个人将其分为三代:

  1. Gen X - 核心(代表机器人编码的基础)
  2. Gen Y - 协作(与外部商业服务接口)
  3. Gen Z - 认知(在业务逻辑中涉及人工智能)

它以图表形式表示为

Generation Layers

用例

为了说明 Microsoft Bot Framework 的增量代,我选择使用“股票价格播报员”聊天机器人用例。

股票价格播报员是一个简单的业务用例,最终用户查询给定股票代码的最新价格。例如,如果最终用户想知道 IBM 的收盘/最新股票价格,此应用程序将返回价格值作为结果。

GenX

GenX 涵盖了聊天机器人基础及其基本通信框架的开发。

概念

Microsoft Bot Connector 是一种通信服务,可帮助您将 Bot 连接到 Skype、短信、电子邮件等任何通信渠道。如果您编写一个对话式 Bot 或代理,并在互联网上公开一个与 Microsoft Bot Framework 兼容的 API,Bot Framework Connector 服务会将 Bot 的消息转发给用户,并将用户消息发回给您的 Bot。

本质上,建议开发人员编写自己的具有 Bot Connector 功能的 Bot App Service。此自定义服务旨在完全利用 Microsoft Bot Framework API。

GenX Concepts

Bot Framework Emulator 是此软件包的一部分,用于模拟客户端组件。

实现

代码级别实现按以下序列图草拟。

GenX Implementation

实现从 Microsoft Service 的基础类开始,即 System.Web.HttpApplication。一旦所有核心 ASP.NET 对象创建完成,就会创建 HttpApplication 对象来处理请求。如果您的系统中有一个 global.asax(继承自 HttpApplication),那么将创建 global.asax 文件的对象。

当 ASP.NET 页面第一次附加到应用程序时,会创建一个新的 HttpApplication 实例。为了最大化性能,HttpApplication 实例可能会被重用于多个请求。这在生命周期图中有所描述。

GenX Implementation

HttpConfiguration 类位于 System.Web.Http 库中,这是一个用于构建 Web API 的框架。

代码示例

作为起始点,当请求基于 Web 应用程序中的第一个资源时,会调用 Application_StartApplication_Start 方法在应用程序生命周期中只被调用一次。

此方法用于执行启动任务,例如将数据加载到缓存中并初始化 static 值。

    /// It's an optional file that contains code for responding 
    /// to application-level events raised by ASP.NET or by HttpModules
    /// by Ganesan Senthilvel
    public class WebApiApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            GlobalConfiguration.Configure(WebApiConfig.Register);
        }
    }

这里的 RouteConfig.cs 文件与任何 MVC 项目中的相同,它为 MVC 框架设置路由。WebApiConfig.cs 文件是我们的 Web API 路由配置发生的地方。

我们的 WebApiConfig static 类看起来像下面的源代码

    /// It's used for any Web API related configuration, including Web-API 
    /// specific routes, Web API services, and other Web API settings
    /// by Ganesan Senthilvel
    public static class WebApiConfig
    {
        /// This method registers a dependency property with the 
        /// specified property name, property type, owner type, property metadata, 
        /// and a value validation callback for the property.
        public static void Register(HttpConfiguration config)
        {
            // Json settings
            config.Formatters.JsonFormatter.SerializerSettings.NullValueHandling = 
                                                          NullValueHandling.Ignore;
            config.Formatters.JsonFormatter.SerializerSettings.ContractResolver = 
                              new CamelCasePropertyNamesContractResolver();
            config.Formatters.JsonFormatter.SerializerSettings.Formatting = 
                              Formatting.Indented;
            JsonConvert.DefaultSettings = () => new JsonSerializerSettings()
            {
                ContractResolver = new CamelCasePropertyNamesContractResolver(),
                Formatting = Newtonsoft.Json.Formatting.Indented,
                NullValueHandling = NullValueHandling.Ignore,
            };

            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }

如果您注意到,我们没有像 MVC RouteConfig 类中那样调用 Routes.MapRoutes,而是像上面那样调用了 Config.Routes.MapHttpRoutes

进入 Configuration 部分,MicrosoftAppIdMicrosoftAppPassword 是在您的 Bot 注册到 Microsoft Bot Framework Connector 时生成的。MicrosoftAppIdMicrosoftAppPassword 用于验证对话,并允许开发人员配置他们的 Bot 以便在他们希望显示的渠道上可见。您指定的 BotId 用于目录和开发人员门户中的 URL。

Bot Framework 注册页面中的 AppIdAppPassword 必须记录在项目的 web.config 中。

接下来是 Post 方法,Bot 模板的核心功能都在 Controllers\MessagesController.cs 文件中的 Post 函数中。在这种情况下,代码获取用户的消息文本,然后使用 CreateReplyMessage 函数创建回复消息。方法上的 BotAuthentication 装饰用于通过 HTTPS 验证您的 Bot Connector 凭据。

        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        public async Task<message> Post([FromBody]Message message)
        {
            if (message.Type == "Message")
            {
                // calculate something for us to return
                int length = (message.Text ?? string.Empty).Length;

                // return our reply to the user
                return message.CreateReplyMessage("ServBot::You sent the message: " +
                                    message.Text + " with " +
                                    length + " characters at " + DateTime.Now);
            }
            else
            {
                return HandleSystemMessage(message);
            }

执行 GenX 代码库后,结果是

GenX Result

GenY

GenY 涵盖了连接另一个服务器组件、提交请求以获取相关信息、将响应检索回 Bot 组件的额外步骤。它是 GenX 的一个扩展,带有外部通信功能。

概念

作为 Bot 应用程序开发的下一级,其功能扩展到连接其他服务以执行核心逻辑。在之前的 GenX 示例的基础上,我们将展示 GenY 的概念。

在自定义的 MyStockBot MyController 对象中,Post 方法接收来自最终用户的消息并回复。因此,它是构建核心业务逻辑的正确容器。

在此方法中,实例化了股票交易所价格查找器以接口股票价值查询过程。此应用服务器连接执行核心业务逻辑并将匹配结果返回到 Post 方法。反过来,结果将返回到最终用户层。

GenY Concepts

实现

Microsoft Bot Framework 有三个主要的 SDK 组件可供构建

  1. Bot Builder - GitHub 上托管的开源 SDK,提供构建基于 Node.js、.NET 或 REST API 的机器人所需的一切,以创建出色的对话
  2. 开发者门户 - 让我们将机器人无缝连接到 Skype、Slack、Facebook Messenger、Kik、Office 365 邮件和其他流行服务,进行文本/短信交流
  3. Bot 目录 - 用户将能够发现、试用并将其机器人添加到他们最喜欢的对话体验中

编写机器人的开发者都面临着同样的问题:机器人需要基本的 I/O;它们必须具备语言和对话能力;它们必须高效、响应迅速且可扩展;它们必须连接到用户——最好是用户选择的任何对话体验和语言。

GenY Implementation

作为 Bot App 执行的核心逻辑,组件图从 Http.ApiController 基类开始。应用程序的对象派生自 ApiController 并命名为 MessageController

MessageController 类的 Post 方法接收 Message 对象,并根据 MessageType 成员切换操作。如果 Type 是预定义 System 数据的一部分,如 pingBotAddedUserAdded 等,那么它们将在名为 HandleSystemMessage 的单独方法中处理。自定义消息将单独处理/加工。

在我们的示例中,非系统消息被处理以计算字符数并返回给调用者。

代码示例

作为 GenX Bot 编码的扩展,GenY 通过外部服务的协作努力而添加。它主要通过扩展 ApiController 类,即 MessageController 来实现。

如前所述,MessageController 中有两个主要的异步方法,即

  1. Post
  2. HandleSystemMessage

PostApiController 对象的主要入口。当从最终用户收到消息/请求时,它就会被触发。消息上下文在方法参数中传递。此方法应该处理传入请求并将输出响应发送给最终用户。

HandleSystemMessage 是传入最终用户请求的非“Message”类型参数的处理方法。如上面的序列图所示,Handler 节点处理任何类型的系统消息并采取适当的操作,如 BotAddedUserAdded 等。

namespace StockBot
{
    /// MessageController class processes incoming requests, 
    /// handles user input and interactions, and executes 
    /// the appropriate application logic.
    /// by Ganesan Senthilvel
    [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// Invoking GetStockRateAsync to retrieve stock
        /// value for the given StockSymbol
        private async Task<string> GetStock(string StockSymbol)
        {
            try
            {
                String retStockValue = await YahooBot.GetStockRateAsync(StockSymbol);
                if (retStockValue.Trim() == "")
                {
                    return string.Format
                    ("This \"{0}\" is not an valid stock symbol", StockSymbol);
                }
                else
                {
                    return string.Format
                    ("Stock symbol : {0} has Name: {1} with Price : {2} as on : {3}",
                        StockSymbol,
                        retStockValue.Split(',')[3],
                        Convert.ToDouble(retStockValue.Split(',')[1]),
                        retStockValue.Split(',')[2]);
                }
            }
            catch(System.FormatException ex)
            {
                return string.Format
                ("This \"{0}\" is not an valid stock symbol", StockSymbol);
            }
        }
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        public async Task<message> Post([FromBody]Message message)
        {
            if (message.Type == "Message")
            {
                // Parse the last word on the sentence
                String stockRate = await GetStock(
                    message.Text.Split(' ').Select(s=>s.Trim()).Last());
                return message.CreateReplyMessage(stockRate);
            }
            else
            {
                return HandleSystemMessage(message);
            }
        }

        /// Bot's Incoming message is handled in this method
        /// based on Message Type
        private Message HandleSystemMessage(Message message)
        {
            if (message.Type == "Ping")
            {
                Message reply = message.CreateReplyMessage();
                reply.Type = "Ping";
                return reply;
            }
            else if (message.Type == "DeleteUserData")
            {
                // Implement user deletion here
                // If we handle user deletion, return a real message
                Message reply = message.CreateReplyMessage
                                ("ServBot::You opted to delete User Data");
                return reply;
            }
            else if (message.Type == "BotAddedToConversation")
            {
                Message reply = message.CreateReplyMessage
                ("ServBot::You opted to connect the conversation");
                return reply;
            }
            else if (message.Type == "BotRemovedFromConversation")
            {
                Message reply = message.CreateReplyMessage
                ("ServBot::You opted to disconnect the conversation");
                return reply;
            }
            else if (message.Type == "UserAddedToConversation")
            {
                Message reply = message.CreateReplyMessage
                ("ServBot::You opted to add User into conversation");
                return reply;
            }
            else if (message.Type == "UserRemovedFromConversation")
            {
                Message reply = message.CreateReplyMessage
                ("ServBot::You opted to remove User from conversation");
                return reply;
            }
            else if (message.Type == "EndOfConversation")
            {
                var hours = DateTime.Now.Hour;
                String partDay = (hours > 16) ? "Evening" : 
                (hours > 11) ? "Afternoon" : "Morning";
                Message reply = message.CreateReplyMessage
                ("ServBot::Good " + partDay + " User!!" +
                    Environment.NewLine + "Local Time is: " + 
                    DateTime.Now.ToString());
                return reply;
            }
            return null;
        }
    }
}

GenY 模式下,添加了外部应用服务来查找给定上市股票的股价。在我们的示例中,它是在名为 YahooBot 的单独类中开发的,用于从雅虎金融服务检索当前股价。

从我们的主入口 MessageController 类中,开发了一个新的异步方法 GetStock 来调用 AppServer YahooBotGetStock 方法将 StockSymbol 作为输入参数,并返回合并后的价格输出作为返回值。

执行 GenY 代码库后,结果是

GenY Result

GenZ

GenZ 是 Microsoft Bot App 开发的高级阶段,集成了认知服务。它通过利用预构建的 Azure 服务,具备高智能能力。

概念

在编程概念方面,GenZGenY AppServer FinStock 集成之前,能够理解最终用户的自由文本输入。它让最终用户能够以自然提问的方式查询 Bot App,而不是通过预定义的股票输入参数。

GenZ Concepts

逻辑上,MyStockBot 类中有两个步骤的过程,如上图所示。

实现

GenZ 认知 BotApp 开发的逐步实现,已仔细编写在下面的流程图中。编码执行过程大致分为两个部分

  1. Microsoft LUIS Azure 应用设置
  2. 我的自定义 .NET 应用设置

基本上,自定义的 .NET 代码应该在 Microsoft Azure LUIS 门户中完成初始设置后编写。

GenZ Implementation

根据 luis.ai 的说法,LUIS 是一种语言理解智能服务,它提供了一种快速有效的方式为应用程序添加语言理解能力。借助 LUIS,您可以使用 Bing 和 Cortana 预先存在的、世界级的、预构建的模型,只要它们符合您的目的,以及当您需要专门的模型时。

LUIS 会指导您快速构建它们。LUIS 是 Microsoft 认知服务的一部分,在此处可找到。详细信息已在 MSDN 此处发布。

GenZ LUIS Set up

代码示例

GenZ 编码方面,智能调用在源代码行中被调用

Rootobject StLUIS = await GetEntityFromLUIS(message.Text);

除此之外,GenZ 编码是 GenY 源代码的一种扩展。在调用 FinStock 服务之前,认知服务会在自定义方法 GetEntityFromLUIS 中被调用。如果您注意 GenZ 源代码中的最后一个方法,它会调用预构建的 Azure 服务,并在成功状态代码下返回适当的响应。

此外,根据 Azure 服务中配置的意图,我们的代码将切换多个业务用例,如随附的代码所示。

   [BotAuthentication]
    public class MessagesController : ApiController
    {
        /// POST: api/Messages
        /// Receive a message from a user and reply to it
        public async Task<message> Post([FromBody]Message message)
        {            
            if (message.Type == "Message")
            {
                string StockRateString;
                Rootobject StLUIS = await GetEntityFromLUIS(message.Text);
                if (StLUIS.intents.Count() > 0)
                {
                    switch (StLUIS.intents[0].intent)
                    {
                        case "StockPrice":
                            StockRateString = await GetStock(StLUIS.entities[0].entity);
                            break;
                        case "StockPrice2":
                            StockRateString = await GetStock(StLUIS.entities[0].entity);
                            break;
                        default:
                            StockRateString = "Sorry, I am not getting you...";
                            break;
                    }
                }
                else
                {
                    StockRateString = "Sorry, I am not getting you...";
                }

                // return our reply to the user  
                return message.CreateReplyMessage(StockRateString);
            }
            else
            {
                return HandleSystemMessage(message);
            }
        }
        private async Task<string> GetStock(string StockSymbol)
        {
            double? dblStockValue = await FinStockBot.GetStockRateAsync(StockSymbol);
            if (dblStockValue == null)
            {
                return string.Format("This \"{0}\" is not an valid stock symbol", 
                                      StockSymbol);
            }
            else
            {
                return string.Format("Stock Price of {0} is {1}", 
                                      StockSymbol, dblStockValue);
            }
        }
        private static async Task<rootobject> GetEntityFromLUIS(string Query)
        {
            Query = Uri.EscapeDataString(Query);
            Rootobject Data = new Rootobject();
            using (HttpClient client = new HttpClient())
            {
                string RequestURI = 
                "https://api.projectoxford.ai/luis/v1/application?id=7f626790-38d6-
                4143-9d46-fe85c56a9016&subscription-key=
                09f80de609fa4698ab4fe5249321d165&q=" + Query;
                HttpResponseMessage msg = await client.GetAsync(RequestURI);

                if (msg.IsSuccessStatusCode)
                {
                    var JsonDataResponse = await msg.Content.ReadAsStringAsync();
                    Data = JsonConvert.DeserializeObject<rootobject>(JsonDataResponse);
                }
            }
            return Data;
        } 

如果您注意到,在 LUIS 服务的实体处理过程中,我们有一个名为“Rootobject”的通信对象。它被创建为 Microsoft LUIS 识别器,指向我们的模型并将其添加为我们 Cortana Bot 的根对话。

除了作为 Microsoft 认知服务接口新添加的 GetEntityFromLUIS 方法外,其他处理逻辑与 GenY 代码库基本相同。

执行 GenZ 代码库后,结果是

GenZ Result

业务中的聊天机器人

在人工智能领域,聊天机器人通过向用户提供购买反馈,并提供客服人员随时提供进一步帮助,发挥着关键作用。

在中国,微信不仅被近三分之二的16-24岁在线消费者使用,该服务还利用其巨大的市场份额,通过提供远远超出简单消息传递的功能,试图尽可能地融入购买旅程中的每一个环节。

作为数字消费者购买旅程和在线生活的主要部分,聊天机器人需要是非侵入性的,对用户明显有益,也许最重要的是,它们要以诚实的助手身份出现,而不是伪装的广告。

FB Chat Bot

作为我分析的总结,聊天机器人使用的两个关键业务优势是:

  1. 手动呼叫中心业务高度自动化;从而降低成本
  2. 在人工智能智能聊天机器人中使用机器学习可以实现持续改进(在使用中)

聊天机器人将购物体验从浏览(网络/零售商店)转变为推荐。机器人会像一位值得信赖的朋友或私人购物员一样了解你。

结论

目前,混合方法可能是深入研究聊天机器人的最佳选择。让技术执行最简单的任务,但有人工支持来处理更复杂的请求和问题。

一旦所有问题解决,您今天研究的内容可能最终会支持您更快地为您的业务部署一个成功的聊天机器人应用程序。

历史

  • 2017年3月27日:初始版本
Microsoft Bot Framework 的演变 - CodeProject - 代码之家
© . All rights reserved.