【发布时间】: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