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

如何使用 Microsoft Bot Framework 创建主动式 Bot

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.71/5 (7投票s)

2017 年 3 月 17 日

CPOL

7分钟阅读

viewsIcon

23183

本文将指导您一步一步地创建Microsoft Bot Framework的主动型机器人。

引言

本文解释了如何使用Microsoft Bot Framework创建主动型机器人。在Microsoft Bot Framework文档中提供了一个很好的示例:https://docs.botframework.com/en-us/azure-bot-service/templates/proactive/

什么是主动型机器人和主动消息?

主动型机器人是指主动向用户发送消息的机器人。也就是说,在没有用户交互的情况下发送信息。例如,闹钟机器人。

为了实现此功能,Bot Framework使用了主动消息。主动消息的示例包括折扣通知、最新商品通知等,这些信息会在特定时间自动发送到您的聊天机器人。

主动型机器人的先决条件

要开始开发,您需要具备以下条件:

  1. 已部署到Bot Framework并在任何频道(例如Skype)上配置的机器人。
  2. 在Bot Framework上配置的Directline频道。
  3. 具有创建Azure Functions和Azure Database权限的Azure账户。

背景

本文分为两部分。在第一部分,我们将展示如何创建一个基本的主动型机器人消息;在第二部分,我们将展示如何使用Azure数据库进行良好的用户界面管理。

第一部分

基本主动型机器人

基本上,要向机器人发送主动消息,我们需要触发机器人来发送消息。机器人可以通过多种方式触发,但最简单的触发方式是使用Azure Function。通过使用Azure Functions,我们可以将输出发送到机器人(这将在后面的博客中解释)。

因此,首先在Visual Studio中创建机器人解决方案,并将其命名为ProactiveBot。

然后创建一个名为ProactiveBusiness.cs的类文件,然后将以下代码片段添加到您的ProactiveBusiness.cs类中。

[Serializable]
public class ProactiveBusiness : IDialog<object>
{
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);
            
        }
        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
        {

        }
}

您的消息控制器将如下所示。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity != null)
            {
                try
                {
                    switch (activity.GetActivityType())
                    {
                        case ActivityTypes.Message:
                            activity.Text = activity.RemoveRecipientMention();
                            await Conversation.SendAsync(activity, () => new ProactiveBusiness());                            
                            break;                       
                    }
                }
                catch (Exception ex)
                {

                }
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

现在,我们需要一个存储数据并将其传递给机器人的中介。我们可以将数据存储到Azure Queue Storage中以开始主动消息传递。

在此,为了接收用户的Proactive消息,我们需要用户的消息以及用户的ResumptionCookie。通过使用这些,我们可以识别用户并发送Proactive消息。因此,我们将消息与ResumptionCookie和文本以json格式保存在队列中。

为此,首先在ProactiveBusiness.cs类中创建一个名为QueueMessage的类,如下所示:

[Serializable]
public class QueueMessage
{
        public ResumptionCookie ResumptionCookie;
        public string MessageText;
}

然后,在QueueMessage中添加消息和resumption cookie,如下所示:

QueueMessage queueMessage;

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
    var msg = await argument;

    ResumptionCookie resCookie = new ResumptionCookie(msg);
    queueMessage = new QueueMessage
    {
        ResumptionCookie = resCookie,
        MessageText = msg.Text
    };
}

现在,我们将此消息添加到AzureQueue。以下是有关如何将消息添加到AzureQueue的步骤。在此处提供了很好的示例:here

首先,您需要添加nuget包以使用AzureStorage。以下是其链接:

.NET的Microsoft Azure Storage客户端库

创建Azure存储账户

登录Azure并创建存储账户,如下所示。

创建存储后,导航到“密钥”部分,并复制存储账户名称和密钥(我们将需要此密钥来创建连接字符串)。

根据上述信息,我们的连接字符串将采用以下格式。

DefaultEndpointsProtocol=https;AccountName=<storage-account-name>;AccountKey=<account-key>

现在,我们需要将此存储账户连接字符串放在机器人解决方案的web.config文件中appconfig部分,如下所示。

<add key="StorageConnectionString" value="DefaultEndpointsProtocol=https;AccountName=<stroge-account-name>;AccountKey=<account-key>" />

编写以下代码以将消息存储到队列中。

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));

// Create the queue client.
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();

// Retrieve a reference to a container.
CloudQueue queue = queueClient.GetQueueReference("myqueue");

// Create the queue if it doesn't already exist
queue.CreateIfNotExists();
            
CloudQueueMessage messageNew = new CloudQueueMessage(JsonConvert.SerializeObject(queueMessage));
queue.AddMessage(messageNew);

将当前消息存储到Azure Queue Storage的最终代码将如下所示。

(有时会出现存储在队列中的数据无法检索的情况,这是由于队列的超时机制)。

参考文献

using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Web;

using Microsoft.Azure; // Namespace for CloudConfigurationManager
using Microsoft.WindowsAzure.Storage; // Namespace for CloudStorageAccount

using Microsoft.WindowsAzure.Storage.Table; // Namespace for Table storage types
using Microsoft.WindowsAzure.Storage.Queue;
using Newtonsoft.Json;

代码

[Serializable]
public class QueueMessage
{
        public ResumptionCookie ResumptionCookie;
        public string MessageText;
}
   

[Serializable]
public class ProactiveBusiness : IDialog<object>
{
        public async Task StartAsync(IDialogContext context)
        {
            context.Wait(MessageReceivedAsync);

        }

        QueueMessage queueMessage;
        private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
        {
            var msg = await argument;

            ResumptionCookie resCookie = new ResumptionCookie(msg);
            queueMessage = new QueueMessage
            {
                ResumptionCookie = resCookie,
                MessageText = msg.Text
            };

            CloudStorageAccount storageAccount = CloudStorageAccount.Parse(CloudConfigurationManager.GetSetting("StorageConnectionString"));

            // Create the queue client.
            CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();

            // Retrieve a reference to a container.
            CloudQueue queue = queueClient.GetQueueReference("myqueue");

            // Create the queue if it doesn't already exist
            queue.CreateIfNotExists();

            CloudQueueMessage messageNew = new CloudQueueMessage(JsonConvert.SerializeObject(queueMessage));
            queue.AddMessage(messageNew);
        }

}

创建Azure Functions

我们将数据存储到Azure队列的任务已完成。现在,我们将创建一个队列触发的Azure函数。

当向队列中添加任何项目时,此函数将自动触发。我们将把函数输出设置为我们的Azure函数中的机器人。

为此,请创建一个函数应用,并为其命名。在此,在创建函数应用时,请确保选择您已创建的用于存储队列数据的存储账户。

创建函数应用后,导航到它,并从“创建自定义函数”部分创建QueueTrigger函数。这里将提供函数列表,我们需要选择QueueTriggered-CSharp

选择它后,向下滚动页面,并在“为函数命名”部分提供QueueTriggeredFunction。此外,在Queue Name中,请输入我们创建的队列名称,即myqueue。在存储账户连接部分,我们需要提供到我们存储账户的连接字符串。

如果您已经创建了连接字符串,则选择它,或者您也可以稍后添加(暂时从任何可用的连接字符串中保存)。

将连接字符串添加到Azure Function

要添加连接字符串,请转到您刚刚创建的函数应用的函数应用设置。

在此,“开发”部分下方将有“应用程序设置”。点击“应用程序设置”旁边的“配置应用程序设置”按钮。

这将打开设置部分。那里将有不同的部分,例如“常规设置”、“调试”、“应用程序设置”等。在这里,我们需要转到“应用程序设置”,并添加我们的存储账户的密钥。在key文本框中提供key的名称,即StorageConnectionString,以及连接字符串的值,正如我们在上面的代码中所使用的。即DefaultEndpointsProtocol=https;AccountName=<stroge-account-name>;AccountKey=<account-key>,然后保存页面。存储连接字符串将在一段时间后填充到应用程序的连接字符串部分。

一旦连接字符串填充完毕,点击我们的QueueTriggered函数,然后点击“集成”部分。这里有一个下拉列表用于选择连接字符串,选择StorageConnectionString并保存。

将函数输出设置为Bot Framework

现在,我们需要将输出设置为Bot Framework。为此,请转到我们的QueueTriggeredFunction并点击其下方的“集成”部分。在这里,我们将在右侧找到“输出”部分。我们需要点击“+新建输出”,这将打开下方的输出设置面板。在这里,我们需要提供三项内容:

1. Bot参数名称 2. 发送者ID 3. Direct Line密钥

1. Bot参数名称:此变量将用于存储机器人数据,并将值返回给BotFramework。我们将名称命名为message

2. 发送者ID:这是我们的机器人的ID,将用于在Bot Framework上识别机器人。

3. Directline密钥:我们的机器人将从Direct Line接收数据,所以在这里我们需要配置我们机器人的Direct Line客户端的密钥。要设置Directline密钥,首先需要激活Bot Framework上的Directline频道并检索密钥。然后,我们需要在Directline Key部分提供该密钥。

代码

设置好输入和输出后,从左侧导航栏转到“开发”部分。这里提供了我们可以编写代码和编写自己逻辑的区域。以下代码用于读取队列中的数据并将其作为输出提供给Bot Framework。

以下代码应粘贴到QueueTriggered函数中。

using System;
using System.Net;
using System.Net.Http;
using Microsoft.Azure.WebJobs.Host;

public class BotMessage
{    
    public string Message { get; set; }
}

public static BotMessage Run(string myQueueItem, out BotMessage message, TraceWriter log)
{
    log.Info($"C# Queue trigger function process started: {myQueueItem}");
    message = new BotMessage
    { 
        Message = myQueueItem
    };
    log.Info($"C# Queue trigger function process Complete: {myQueueItem}");
    return message;    
}

在这里,myQueueItem变量包含来自队列的数据,并将保存到message变量中。然后它会将message变量返回给Bot Framework。

这将创建我们机器人中的一个触发器。现在,我们需要编写代码来处理触发器并显示适当的消息。

接收触发的消息

以下是显示来自QueueTriggeredFunction的触发文本的触发代码。我们需要将此代码写入MessageController.cs

case ActivityTypes.Trigger:
    ITriggerActivity trigger = activity;
    var message = JsonConvert.DeserializeObject<QueueMessage>(((JObject)trigger.Value).GetValue("Message").ToString());
    if (!string.IsNullOrEmpty(message.MessageText))
    {
        var messageactivity = (Activity)message.ResumptionCookie.GetMessage();

        var client = new ConnectorClient(new Uri(messageactivity.ServiceUrl));
        activity.Text = activity.RemoveRecipientMention();

        Activity replyToConversation = messageactivity.CreateReply();
        replyToConversation.Recipient = activity.From;
        replyToConversation.Type = "message";
        
        replyToConversation.Attachments = new List<Attachment>();
        List<CardImage> cardImages = new List<CardImage>();
        cardImages.Add(new CardImage(url: "http://cdn.wonderfulengineering.com/wp-content/uploads/2016/02/iron-man-wallpaper-22.jpg"));
        List<CardAction> cardButtons = new List<CardAction>();
        CardAction plButton = new CardAction()
        {
            Value = "https://en.wikipedia.org/wiki/Iron_Man",
            Type = "openUrl",
            Title = "Ironman Wiki"
        };
        cardButtons.Add(plButton);
        HeroCard plCard = new HeroCard()
        {
            Title = message.MessageText,
            Subtitle = "Triggered Iron Man Card",
            Images = cardImages,
            Buttons = cardButtons
        };
        Attachment plAttachment = plCard.ToAttachment();
        replyToConversation.Attachments.Add(plAttachment);
        await client.Conversations.ReplyToActivityAsync(replyToConversation);
    }
    break;

上面的代码将获取从Azure Queue触发的消息。然后它会将数据解析为JSON对象,并获取resumption cookie和消息。然后,我们基于resumption cookie创建活动,并将Hero Card发送给用户。

最终的消息控制器将如下所示。

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
        {
            if (activity != null)
            {
                try
                {
                    switch (activity.GetActivityType())
                    {
                        case ActivityTypes.Message:
                            activity.Text = activity.RemoveRecipientMention();
                            await Conversation.SendAsync(activity, () => new ProactiveBusiness());                            
                            break;
                        case ActivityTypes.Trigger:
                            ITriggerActivity trigger = activity;
                            var message = JsonConvert.DeserializeObject<QueueMessage>(((JObject)trigger.Value).GetValue("Message").ToString());
                            if (!string.IsNullOrEmpty(message.MessageText))
                            {
                                var messageactivity = (Activity)message.ResumptionCookie.GetMessage();

                                var client = new ConnectorClient(new Uri(messageactivity.ServiceUrl));
                                activity.Text = activity.RemoveRecipientMention();

                                Activity replyToConversation = messageactivity.CreateReply();
                                replyToConversation.Recipient = activity.From;
                                replyToConversation.Type = "message";
                                
                                replyToConversation.Attachments = new List<Attachment>();
                                List<CardImage> cardImages = new List<CardImage>();
                                cardImages.Add(new CardImage(url: "http://cdn.wonderfulengineering.com/wp-content/uploads/2016/02/iron-man-wallpaper-22.jpg"));
                                List<CardAction> cardButtons = new List<CardAction>();
                                CardAction plButton = new CardAction()
                                {
                                    Value = "https://en.wikipedia.org/wiki/Iron_Man",
                                    Type = "openUrl",
                                    Title = "Ironman Wiki"
                                };
                                cardButtons.Add(plButton);
                                HeroCard plCard = new HeroCard()
                                {
                                    Title = message.MessageText,
                                    Subtitle = "Triggered Iron Man Card",
                                    Images = cardImages,
                                    Buttons = cardButtons
                                };
                                Attachment plAttachment = plCard.ToAttachment();
                                replyToConversation.Attachments.Add(plAttachment);
                                await client.Conversations.ReplyToActivityAsync(replyToConversation);
                            }
                            break;
                        
                    }
                }
                catch (Exception ex)
                {

                }
            }
            var response = Request.CreateResponse(HttpStatusCode.OK);
            return response;
        }

测试我的代码

要测试我们上面完成的代码,我们需要将代码发布到Azure,并为机器人配置Directline API(我们已在文章前面的步骤中完成)。或者,如果您知道如何使用ngrok,也可以直接在不发布到Azure的情况下进行测试。使用ngrok的链接在此:https://robinosborne.co.uk/2016/09/19/debugging-botframework-locally-using-ngrok/

要测试主动场景,请向机器人发送任何消息(我目前使用的是Skype频道)。它将返回您输入的消息以及通知卡。

(首次触发活动可能需要一些时间,因为它使用Direct Line API,请耐心等待,并喝杯咖啡。很多时候,我们可能需要发送多条消息来触发活动)。

太棒了!我们已经完成了主动型机器人的配置。现在,对于任何通知,您都可以将消息添加到队列中,并带有resumption cookie,它将被传递给您的用户。

#第二部分将很快发布。

© . All rights reserved.