【问题标题】:Resume Bot Framework dialog when triggered by external service由外部服务触发时恢复 Bot Framework 对话框
【发布时间】:2018-02-02 03:23:26
【问题描述】:

情景

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

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

之前

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

思路一:使用 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:尝试模仿 bot 服务调用我的 bot

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

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

更新: 在 Eric 的下方查看我的回答,看看我做了什么。

【问题讨论】:

  • 根据您的描述,Send a dialog-based proactive message 应该是您的方案的方式,您的想法 2 中的代码似乎用于发送主动消息,您说它不起作用,但我看不到该代码有任何问题。
  • 感谢@GraceFeng-MSFT 的回答,我希望能够编写一个对话框然后恢复它,而不是将另一个对话框推入堆栈。我想我可能有办法解决这个问题,今天尝试一下。

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


【解决方案1】:

想法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 控制器(将 ConversationReference 保存在 cookie 或其他内容中),当用户在页面上添加完地址后,使用 ConversationReference 向机器人发送事件(类似于 BotAuth 在 CallbackController 中恢复对话的方式)。

想法 3:

这将绕过连接器服务,并且不是受支持的方案。您共享的链接详细说明了身份验证在 Bot Framework 中的工作原理,而不是如何绕过连接器服务。

【讨论】:

  • 谢谢 Eric,我从来没有找到过 Auth 机器人,但因为它看起来最有希望,所以我将研究实现它。在此期间,我设法让 Idea 3 运行良好,但由于它不是受支持的场景,我不会依赖它。
  • 感谢 Eric,BotAuth 的风格正是我所需要的。
【解决方案2】:

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 Framework 时遇到的最复杂的问题,我发现文档有点欠缺。希望这对某人有帮助!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-07-27
    • 2022-11-10
    • 1970-01-01
    • 1970-01-01
    • 2017-04-27
    • 2016-10-30
    • 2014-06-25
    • 1970-01-01
    相关资源
    最近更新 更多