由外部服务触发时恢复Bot Framework对话框

时间:2018-02-02 03:23:27

标签: c# botframework chatbot azure-bot-service

情景

我使用Bot Framework构建了一个带有一系列对话框的机器人。其中一个对话框为用户提供了通过向其显示按钮来通过网页输入一些复杂数据的选项。单击按钮,然后将它们带到站点,填写数据,保存,然后返回到机器人。

我希望我的机器人暂停对话,直到它从我的网页收到一个事件,告诉我用户已保存数据,然后继续询问用户问题。

之前

我有一个版本实现,我会在用户点击按钮之前存储一个ConversationReference,然后当外部事件发生时,我会从webhook发送卡片和我想要显示的下一条消息(不是在对话框中),很好,但它变得非常复杂/凌乱 - 我宁愿将整个应用程序保持在一个连续的对话框中。

创意1:使用DirectLine API

我做了一些研究,很多人建议使用DirectLine API。所以我实现了这个:

public async Task SendEventAsync(InternalEventMessage message, ConversationReference reference) {
        var client = new DirectLineClient(!String.IsNullOrEmpty(_settings.DirectLineSecret) ? _settings.DirectLineSecret : null);
        if (_settings.SiteUrl.Contains("localhost")) {
            client.BaseUri = new Uri(_settings.DirectLineServiceUrl);
        }

        var eventMessage = Activity.CreateEventActivity();
        //Wrong way round?!?
        eventMessage.From = reference.Bot;
        eventMessage.Type = ActivityTypes.Event;
        eventMessage.Value = message;
        var conversation = await client.Conversations.PostActivityAsync(reference.Conversation.Id, eventMessage as Activity);
    }

这使用DirectLine客户端使用存储的ConversationReference向serviceUrl发送事件消息,基本上模仿用户(僵尸程序和用户在SDK中似乎是错误的方式)。检查localhost是因为DirectLine库指向模拟器服务器而不是https://directline.botframework.com

在我的对话框中,我打电话给:

//method above shows input button and links to web page
context.Wait(WaitForAddressInput);
}

private async Task WaitForAddressInput(IDialogContext context, IAwaitable<IActivity> result) {
    var message = await result;
    switch (message.Type) {
        case ActivityTypes.Message:
            //TODO: Add response
            break;

        case ActivityTypes.Event:
            var eventMessage = message as IEventActivity;
            if (((JObject)eventMessage.Value).ToObject<InternalEventMessage>().Type == EventType.AddressInputComplete) {
                _addressResult = (await _tableService.ReadOrderById(Order.OrderId)).Address;
                await context.PostAsync($"Great}");
                context.Done(_addressResult);
            }
            break;
    }
}

在显示按钮后,等待用户发出的任何消息,如果我们的事件匹配,那么我们继续对话。

这可以在本地使用模拟器,但令人沮丧的是,它并不存在。它无法识别通过网络聊天或Messenger创建的频道。这在此解释:Microsoft Bot Framework DirectLine Can't Access Conversations

  

出于安全原因,您无法使用DirectLine来侦听来自的邮件   另一次谈话。

因此,我无法访问使用DirectLine创建的频道。

创意2:BotConnector

所以我想我会尝试使用类似代码的BotConnector:

public async Task SendEventAsync(InternalEventMessage message, Microsoft.Bot.Connector.DirectLine.ConversationReference reference) {
    var botAccount = new ChannelAccount(reference.User.Id, reference.User.Name);
    var userAccount = new ChannelAccount(reference.Bot.Id, reference.Bot.Name);

    MicrosoftAppCredentials.TrustServiceUrl(reference.ServiceUrl);
    var connector = new ConnectorClient(new Uri(reference.ServiceUrl), new MicrosoftAppCredentials("xxxxxxxxxxxxxxxxxxxxxxxx", "xxxxxxxxxxxxxxxxxxxxxxxx"));

    connector.Credentials.InitializeServiceClient();

    var eventMessage = Activity.CreateMessageActivity();
    eventMessage.Recipient = botAccount;
    eventMessage.From = userAccount;
    eventMessage.Type = ActivityTypes.Event;
    eventMessage.Conversation = new ConversationAccount(id: reference.Conversation.Id);
    eventMessage.ServiceUrl = reference.ServiceUrl;
    eventMessage.Timestamp = DateTimeOffset.UtcNow;
    eventMessage.LocalTimestamp = DateTime.Now;
    eventMessage.ChannelId = reference.ChannelId;

    var result = await connector.Conversations.SendToConversationAsync(eventMessage as Microsoft.Bot.Connector.Activity);
}

这不会崩溃,我可以看到事件出现在模拟器请求控制台中,但没有任何反应,它似乎被忽略了!

想法3:尝试模仿调用我的机器人的机器人服务

我还没有尝试过这个,因为我认为这可能是最耗时的,但我正在阅读有关服务身份验证的here,并想知道是否有可能模仿托管的bot服务发送一个消息并以所需数据发送我的事件?

这似乎是一个相当常见的情况,所以我很惊讶我还没有遇到过这样做的方法。如果有人对如何从外部服务向我的机器人发送事件消息有任何其他想法,那么我很乐意听到它。

更新 请参阅Eric下面的回答,看看我做了什么。

2 个答案:

答案 0 :(得分:2)

创意1:

DirectLine 是一个频道,而不是用于连接频道的库。 (例如:您不会使用Facebook Messenger连接到Skype) DirectLineClient 对于创建通过Direct Line连接器服务连接到 DirectLine 频道的客户端应用程序非常有用。

创意2:

此方法应该有效。事实上, BotAuth 库将此方法用于 CallbackController 中的MagicNumber登录流程:https://github.com/MicrosoftDX/botauth/blob/9a0a9f1b665f4aa95b6d60d09346dda90d8b314e/CSharp/BotAuth/Controllers/CallbackController.cs

对于您的方案,您应该能够构建 ActionTypes.OpenUrl 类型的 CardAction ,其中包含编码为 ConversationReference 的值网址。单击该按钮将调用显示页面的mvc控制器(在cookie或其他内容中保存 ConversationReference ),当用户完成在页面上添加地址时,使用 ConversationReference 向bot发送事件(类似于BotAuth如何在CallbackController中恢复对话)。

创意3:

这会绕过连接器服务,并且不是受支持的方案。您共享的链接解释了身份验证在Bot Framework中的工作方式,而不是如何绕过连接器服务。

答案 1 :(得分:1)

Eric的回答让我用BotAuth示例解决了这个问题,但为了完整起见,这就是我使用Idea 2所做的。

我在Bot Framework端点上创建了一个CallbackController,然后使用以下代码将一个事件发送回等待对话框:

MicrosoftAppCredentials.TrustServiceUrl(reference.ServiceUrl);
            var message = reference.GetPostToBotMessage();
            message.Value = new InternalEventMessage(type);
            message.Type = ActivityTypes.Event;

            await Conversation.ResumeAsync(reference, message);

对话框等待此代码并继续:

context.Wait(WaitForAddressInput);
        }

        private async Task WaitForAddressInput(IDialogContext context,
                                      IAwaitable<IActivity> result)
        {
            var message = await result;
            switch (message.Type)
            {
                case ActivityTypes.Message:
                    //TODO: Add response
                    break;

                case ActivityTypes.Event:
                    //Process event and continue!
                    break;
            }
        }

这是我在Bot框架中遇到的最复杂的问题,我发现文档有点缺乏。希望这有助于某人!