Bot 框架发送不必要的错误消息

Bot Framework Sending Unnecessary Error Messages

我使用 Microsoft 的 Bot Framework 创建了一个名为 picturesaver 的机器人,我添加了一个 GroupMe 频道,并将其托管在 Azure 中。该机器人运行完美,将图片保存到 Google 驱动器。

但是,机器人报错"Service Error:POST to picturesaver timed out after 15s"是否可以延长超时时间?或者甚至完全阻止机器人发布任何内容。这可能是 Azure 问题还是 GroupMe 问题?

Bot Connector 服务有 15 秒的超时时间,因此您需要确保所有异步 API 调用都在该时间范围内得到处理,或者确保您的机器人在等待其他消息时以某种消息响应操作来完成。目前无法修改15s超时。

如果您的机器人执行的操作处理消息的时间超过 15 秒,您可以在另一个线程上处理该消息,并立即确认调用。类似于:

public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
    if (activity.Type == ActivityTypes.Message)
    {
        if ([determine if this will take > 15s]) 
        {
            // process the message asyncronously
            Task.Factory.StartNew(async () => await Conversation.SendAsync(activity, () => new Dialogs.RootDialog()));
        }
        else
        {
            //process the message normally
            await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
        }
    }

    return Request.CreateResponse(HttpStatusCode.OK); //ack the call
}

这将避免连接器和机器人之间的 15 秒超时。


编辑:以上不会缩放,只是使用 Task.Factory。请参阅 https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-long-operations-guidance 以获取有关处理来自机器人的长时间操作的推荐指南。

在另一个线程上处理消息并立即确认调用的解决方案仅适用于应用服务上的机器人

但是对于 a Functions Bot 如果我立即从这个方法中 return 这样做将完成 Azure Function。

我试过了。 Azure 函数停止 运行,对聊天的真正响应从未到来。所以它根本不是功能机器人的解决方案。

我最终得到了 Functions Bot 的这段代码,它解决了这个问题。

使用 Azure 队列

public static class Functions
{
    [FunctionName("messages")]
    [return: Queue("somequeue")]
    public static async Task<MessagePayload> Messages([HttpTrigger
            (WebHookType = "genericJson")]HttpRequestMessage req) =>
        // return from this Azure Function immediately to avoid timeout warning message 
        // in the chat.
        // just put the request into "somequeue". 
        // We can't pass the whole request via the Queue, so pass only what we need for 
        // the message to be processed by Bot Framework
        new MessagePayload
        {
            RequestUri = req.RequestUri,
            Content = await req.Content.ReadAsStringAsync(),
            AuthScheme = req.Headers.Authorization.Scheme,
            AuthParameter = req.Headers.Authorization.Parameter
        };

    // Do the actual message processing in another Azure Function, which is 
    // triggered by a message enqueued in the Azure Queue "somequeue"
    [FunctionName("processTheMessage")]
    public static async Task ProcessTheMessage([QueueTrigger("somequeue")]
        MessagePayload payload, TraceWriter logger)
    {
        // we don't want the queue to process this message 5 times if it fails, 
        // so we won't throw any exceptions here at all, but we'll handle them properly.
        try
        {
            // recreate the request
            var request = new HttpRequestMessage
            {
                Content = new StringContent(payload.Content),
                RequestUri = payload.RequestUri
            };
            request.Headers.Authorization = new  
                AuthenticationHeaderValue(payload.AuthScheme, payload.AuthParameter);

            // initialize dependency injection container, services, etc.
            var initializer = new SomeInitializer(logger);
            initializer.Initialize();

            // handle the request in a usual way and reply back to the chat
            await initializer.HandleRequestAsync(request);
        }
        catch (Exception ex)
        {
            try
            {
                // TODO: handle the exception
            }
            catch (Exception anotherException)
            {
                // swallow any exceptions in the exceptions handler?
            }
        }
    }

}

[Serializable]
public class MessagePayload
{
    public string Content { get; set; }
    public string AuthParameter { get; set; }
    public string AuthScheme { get; set; }
    public Uri RequestUri { get; set; }
}

(请务必使用不同的 Azure 队列 以使用 Bot Framework 模拟器进行本地开发和 cloud-deployed 函数应用程序。否则,从当您在您的机器上调试时,真实客户可能会在本地处理)

使用 HTTP 请求

当然,不使用 Azure 队列 直接调用另一个 Azure 函数的 public URL - https://<my-bot>.azurewebsites.net/api/processTheMessage?code=<function-secret>.此调用必须在另一个线程上完成,无需等待 messages 函数中的结果。

[FunctionName("messages")]
public static async Task Run([HttpTrigger(WebHookType = "genericJson")]
    HttpRequestMessage req)
{
    // return from this Azure Function immediately to avoid timeout warning message 
    // in the chat.
    using (var client = new HttpClient())
    {
        string secret = ConfigurationManager.AppSettings["processMessageHttp_secret"];
        // change the RequestUri of the request to processMessageHttp Function's 
        // public URL, providing the secret code, stored in app settings 
        // with key 'processMessageHttp_secret'
        req.RequestUri = new Uri(req.RequestUri.AbsoluteUri.Replace(
            req.RequestUri.PathAndQuery, $"/api/processMessageHttp?code={secret}"));

        // don't 'await' here. Simply send.
#pragma warning disable CS4014
        client.SendAsync(req);
#pragma warning restore CS4014

        // wait a little bit to ensure the request is sent. It will not 
        // send the request at all without this line, because it would 
        // terminate this Azure Function immediately
        await Task.Delay(500);
    }
}

[FunctionName("processMessageHttp")]
public static async Task ProcessMessageHttp([HttpTrigger(WebHookType = "genericJson")]
    HttpRequestMessage req,
    Microsoft.Extensions.Logging.ILogger log)
{
    // first and foremost: initialize dependency 
    // injection container, logger, services, set default culture/language, etc.
    var initializer = FunctionAppInitializer.Initialize(log);

    // handle the request in a usual way and reply back to the chat
    await initializer.HandleRequest(req);
}