Microsoft Bot Framework 的演变






4.89/5 (36投票s)
一篇关于人工智能聊天机器人应用程序开发的有趣文章。
世代
聊天机器人是一种可以模拟与人类对话的软件程序。最早也是最著名的聊天机器人之一(在网络出现之前)是 Eliza,一个假装是心理治疗师并用其他问题回答问题的程序。
Red 和 Andrette 是两个早期程序的名称,它们可以自定义来回答寻求产品服务的用户的问题。这种程序有时被称为虚拟代表或虚拟服务代理。
概述
基于 Microsoft Bot Framework 的发展演变,我个人将其分为三代:
- Gen X - 核心(代表机器人编码的基础)
- Gen Y - 协作(与外部商业服务接口)
- Gen Z - 认知(在业务逻辑中涉及人工智能)
它以图表形式表示为
用例
为了说明 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。
Bot Framework Emulator 是此软件包的一部分,用于模拟客户端组件。
实现
代码级别实现按以下序列图草拟。
实现从 Microsoft Service 的基础类开始,即 System.Web.HttpApplication
。一旦所有核心 ASP.NET 对象创建完成,就会创建 HttpApplication
对象来处理请求。如果您的系统中有一个 global.asax(继承自 HttpApplication
),那么将创建 global.asax 文件的对象。
当 ASP.NET 页面第一次附加到应用程序时,会创建一个新的 HttpApplication
实例。为了最大化性能,HttpApplication
实例可能会被重用于多个请求。这在生命周期图中有所描述。
HttpConfiguration
类位于 System.Web.Http
库中,这是一个用于构建 Web API 的框架。
代码示例
作为起始点,当请求基于 Web 应用程序中的第一个资源时,会调用 Application_Start
。Application_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
部分,MicrosoftAppId
和 MicrosoftAppPassword
是在您的 Bot 注册到 Microsoft Bot Framework Connector 时生成的。MicrosoftAppId
和 MicrosoftAppPassword
用于验证对话,并允许开发人员配置他们的 Bot 以便在他们希望显示的渠道上可见。您指定的 BotId
用于目录和开发人员门户中的 URL。
Bot Framework 注册页面中的 AppId
和 AppPassword
必须记录在项目的 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
代码库后,结果是
GenY
GenY
涵盖了连接另一个服务器组件、提交请求以获取相关信息、将响应检索回 Bot 组件的额外步骤。它是 GenX
的一个扩展,带有外部通信功能。
概念
作为 Bot 应用程序开发的下一级,其功能扩展到连接其他服务以执行核心逻辑。在之前的 GenX
示例的基础上,我们将展示 GenY
的概念。
在自定义的 MyStockBot MyController
对象中,Post
方法接收来自最终用户的消息并回复。因此,它是构建核心业务逻辑的正确容器。
在此方法中,实例化了股票交易所价格查找器以接口股票价值查询过程。此应用服务器连接执行核心业务逻辑并将匹配结果返回到 Post
方法。反过来,结果将返回到最终用户层。
实现
Microsoft Bot Framework 有三个主要的 SDK 组件可供构建
- Bot Builder - GitHub 上托管的开源 SDK,提供构建基于 Node.js、.NET 或 REST API 的机器人所需的一切,以创建出色的对话
- 开发者门户 - 让我们将机器人无缝连接到 Skype、Slack、Facebook Messenger、Kik、Office 365 邮件和其他流行服务,进行文本/短信交流
- Bot 目录 - 用户将能够发现、试用并将其机器人添加到他们最喜欢的对话体验中
编写机器人的开发者都面临着同样的问题:机器人需要基本的 I/O;它们必须具备语言和对话能力;它们必须高效、响应迅速且可扩展;它们必须连接到用户——最好是用户选择的任何对话体验和语言。
作为 Bot App 执行的核心逻辑,组件图从 Http.ApiController
基类开始。应用程序的对象派生自 ApiController
并命名为 MessageController
。
MessageController
类的 Post
方法接收 Message
对象,并根据 Message
的 Type
成员切换操作。如果 Type
是预定义 System
数据的一部分,如 ping
、BotAdded
、UserAdded
等,那么它们将在名为 HandleSystemMessage
的单独方法中处理。自定义消息将单独处理/加工。
在我们的示例中,非系统消息被处理以计算字符数并返回给调用者。
代码示例
作为 GenX
Bot 编码的扩展,GenY
通过外部服务的协作努力而添加。它主要通过扩展 ApiController
类,即 MessageController
来实现。
如前所述,MessageController
中有两个主要的异步方法,即
Post
HandleSystemMessage
Post
是 ApiController
对象的主要入口。当从最终用户收到消息/请求时,它就会被触发。消息上下文在方法参数中传递。此方法应该处理传入请求并将输出响应发送给最终用户。
HandleSystemMessage
是传入最终用户请求的非“Message
”类型参数的处理方法。如上面的序列图所示,Handler
节点处理任何类型的系统消息并采取适当的操作,如 BotAdded
、UserAdded
等。
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 YahooBot
。GetStock
方法将 StockSymbol
作为输入参数,并返回合并后的价格输出作为返回值。
执行 GenY
代码库后,结果是
GenZ
GenZ
是 Microsoft Bot App 开发的高级阶段,集成了认知服务。它通过利用预构建的 Azure 服务,具备高智能能力。
概念
在编程概念方面,GenZ
在 GenY
AppServer FinStock 集成之前,能够理解最终用户的自由文本输入。它让最终用户能够以自然提问的方式查询 Bot App,而不是通过预定义的股票输入参数。
逻辑上,MyStockBot
类中有两个步骤的过程,如上图所示。
实现
GenZ
认知 BotApp
开发的逐步实现,已仔细编写在下面的流程图中。编码执行过程大致分为两个部分
- Microsoft LUIS Azure 应用设置
- 我的自定义 .NET 应用设置
基本上,自定义的 .NET 代码应该在 Microsoft Azure LUIS 门户中完成初始设置后编写。
根据 luis.ai 的说法,LUIS 是一种语言理解智能服务,它提供了一种快速有效的方式为应用程序添加语言理解能力。借助 LUIS,您可以使用 Bing 和 Cortana 预先存在的、世界级的、预构建的模型,只要它们符合您的目的,以及当您需要专门的模型时。
LUIS 会指导您快速构建它们。LUIS 是 Microsoft 认知服务的一部分,在此处可找到。详细信息已在 MSDN 此处发布。
代码示例
在 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
代码库后,结果是
业务中的聊天机器人
在人工智能领域,聊天机器人通过向用户提供购买反馈,并提供客服人员随时提供进一步帮助,发挥着关键作用。
在中国,微信不仅被近三分之二的16-24岁在线消费者使用,该服务还利用其巨大的市场份额,通过提供远远超出简单消息传递的功能,试图尽可能地融入购买旅程中的每一个环节。
作为数字消费者购买旅程和在线生活的主要部分,聊天机器人需要是非侵入性的,对用户明显有益,也许最重要的是,它们要以诚实的助手身份出现,而不是伪装的广告。
作为我分析的总结,聊天机器人使用的两个关键业务优势是:
- 手动呼叫中心业务高度自动化;从而降低成本
- 在人工智能智能聊天机器人中使用机器学习可以实现持续改进(在使用中)
聊天机器人将购物体验从浏览(网络/零售商店)转变为推荐。机器人会像一位值得信赖的朋友或私人购物员一样了解你。
结论
目前,混合方法可能是深入研究聊天机器人的最佳选择。让技术执行最简单的任务,但有人工支持来处理更复杂的请求和问题。
一旦所有问题解决,您今天研究的内容可能最终会支持您更快地为您的业务部署一个成功的聊天机器人应用程序。
历史
- 2017年3月27日:初始版本