LUIS Bot 框架不会从外部调用中调用 Intent

LUIS Bot framework won't call Intent from external call

我为我的 BOT 实施了外部登录。当外部站点调用 Bot CallBack 方法时,我需要在 PrivateConversationData 中设置令牌和用户名,然后使用 "Welcome back [username]!".

之类的消息恢复聊天

为了显示这条消息,我发送了一个 MessageActivity,但是这个 activity 永远不会连接到我的聊天,也不会触发适当的 [LuisIntent("UserIsAuthenticated")]

登录流程之外的其他意图按预期工作。

这是回调方法:

public class OAuthCallbackController : ApiController
{
    [HttpGet]
    [Route("api/OAuthCallback")]
    public async Task OAuthCallback([FromUri] string userId, [FromUri] string botId, [FromUri] string conversationId,
        [FromUri] string channelId, [FromUri] string serviceUrl, [FromUri] string locale,
        [FromUri] CancellationToken cancellationToken, [FromUri] string accessToken, [FromUri] string username)
    {
        var resumptionCookie = new ResumptionCookie(TokenDecoder(userId), TokenDecoder(botId),
            TokenDecoder(conversationId), channelId, TokenDecoder(serviceUrl), locale);

            var container = WebApiApplication.FindContainer();

            var message = resumptionCookie.GetMessage();
            message.Text = "UserIsAuthenticated";

            using (var scope = DialogModule.BeginLifetimeScope(container, message))
            {
                var botData = scope.Resolve<IBotData>();
                await botData.LoadAsync(cancellationToken);

                botData.PrivateConversationData.SetValue("accessToken", accessToken);
                botData.PrivateConversationData.SetValue("username", username);

                ResumptionCookie pending;
                if (botData.PrivateConversationData.TryGetValue("persistedCookie", out pending))
                {
                    botData.PrivateConversationData.RemoveValue("persistedCookie");
                    await botData.FlushAsync(cancellationToken);
                }

                var stack = scope.Resolve<IDialogStack>();
                var child = scope.Resolve<MainDialog>(TypedParameter.From(message));
                var interruption = child.Void<object, IMessageActivity>();

                try
                {
                    stack.Call(interruption, null);

                    await stack.PollAsync(cancellationToken);
                }
                finally
                {
                    await botData.FlushAsync(cancellationToken);
                }
            }
        }
    }   

    public static string TokenDecoder(string token)
    {
        return Encoding.UTF8.GetString(HttpServerUtility.UrlTokenDecode(token));
    }
}

这是控制器:

public class MessagesController : ApiController
{
    private readonly ILifetimeScope scope;

    public MessagesController(ILifetimeScope scope)
    {
        SetField.NotNull(out this.scope, nameof(scope), scope);
    }

    public async Task<HttpResponseMessage> Post([FromBody] Activity activity, CancellationToken token)
    {
        if (activity != null)
        {
            switch (activity.GetActivityType())
            {
                case ActivityTypes.Message:
                    using (var scope = DialogModule.BeginLifetimeScope(this.scope, activity))
                    {
                        var postToBot = scope.Resolve<IPostToBot>();
                        await postToBot.PostAsync(activity, token);
                    }
                    break;
            }
        }

        return new HttpResponseMessage(HttpStatusCode.Accepted);
    }
}

这是我注册组件的方式:

protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        builder.Register(
            c => new LuisModelAttribute("myId", "SubscriptionKey"))
            .AsSelf()
            .AsImplementedInterfaces()
            .SingleInstance();

        builder.RegisterType<MainDialog>().AsSelf().As<IDialog<object>>().InstancePerDependency();

        builder.RegisterType<LuisService>()
            .Keyed<ILuisService>(FiberModule.Key_DoNotSerialize)
            .AsImplementedInterfaces()
            .SingleInstance();
    }

这是对话框:

[Serializable]
public sealed class MainDialog : LuisDialog<object>
{
    public static readonly string AuthTokenKey = "TestToken";
    public readonly ResumptionCookie ResumptionCookie;
    public static readonly Uri CloudocOauthCallback = new Uri("http://localhost:3980/api/OAuthCallback");

    public MainDialog(IMessageActivity activity, ILuisService luis)
        : base(luis)
    {
        ResumptionCookie = new ResumptionCookie(activity);
    }

    [LuisIntent("")]
    public async Task None(IDialogContext context, LuisResult result)
    {
        await context.PostAsync("Sorry cannot understand!");
        context.Wait(MessageReceived);
    }

    [LuisIntent("UserAuthenticated")]
    public async Task UserAuthenticated(IDialogContext context, LuisResult result)
    {
        string username;
        context.PrivateConversationData.TryGetValue("username", out username);

        await context.PostAsync($"Welcome back {username}!");
        context.Wait(MessageReceived);
    }

    [LuisIntent("Login")]
    private async Task LogIn(IDialogContext context, LuisResult result)
    {
        string token;
        if (!context.PrivateConversationData.TryGetValue(AuthTokenKey, out token))
        {
            context.PrivateConversationData.SetValue("persistedCookie", ResumptionCookie);

            var loginUrl = CloudocHelpers.GetLoginURL(ResumptionCookie, OauthCallback.ToString());

            var reply = context.MakeMessage();

            var cardButtons = new List<CardAction>();
            var plButton = new CardAction
            {
                Value = loginUrl,
                Type = ActionTypes.Signin,
                Title = "Connetti a Cloudoc"
            };
            cardButtons.Add(plButton);
            var plCard = new SigninCard("Connect", cardButtons);

            reply.Attachments = new List<Attachment>
            {
                plCard.ToAttachment()
            };

            await context.PostAsync(reply);
            context.Wait(MessageReceived);
        }
        else
        {
            context.Done(token);
        }
    }
}

我想念什么?

更新

还尝试在回调方法中使用 ResumeAsync

var container = WebApiApplication.FindContainer();

var message = resumptionCookie.GetMessage();
message.Text = "UserIsAuthenticated";

using (var scope = DialogModule.BeginLifetimeScope(container, message))
{
     var botData = scope.Resolve<IBotData>();
     await botData.LoadAsync(cancellationToken);

     botData.PrivateConversationData.SetValue("accessToken", accessToken);
     botData.PrivateConversationData.SetValue("username", username);

     ResumptionCookie pending;
     if (botData.PrivateConversationData.TryGetValue("persistedCookie", out pending))
     {
         botData.PrivateConversationData.RemoveValue("persistedCookie");
         await botData.FlushAsync(cancellationToken);
     }

     await Conversation.ResumeAsync(resumptionCookie, message, cancellationToken);
 }

但它给我错误 Operation is not valid due to the current state of the object.

更新 2

按照 Ezequiel 的想法,我以这种方式更改了我的代码:

    [HttpGet]
    [Route("api/OAuthCallback")]
    public async Task OAuthCallback(string state, [FromUri] string accessToken, [FromUri] string username)
    {
        var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
        var message = resumptionCookie.GetMessage();
        message.Text = "UserIsAuthenticated";

        await Conversation.ResumeAsync(resumptionCookie, message);
    }

resumptionCookie 好像没问题:

但是await Conversation.ResumeAsync(resumptionCookie, message);继续报错Operation is not valid due to the current state of the object.

您需要恢复与机器人的对话,这就是消息可能未到达的原因。

尝试使用

而不是使用对话框堆栈
await Conversation.ResumeAsync(resumptionCookie, message);

根据您的身份验证需求,您可能需要考虑 AuthBot. You can also take a look to the logic 在库的 OAuthCallback 控制器上,以了解他们如何在身份验证后恢复与机器人的对话。

ContosoFlowers example, is also using the resume conversation mechanism。不是为了验证目的,而是为了展示如何处理虚拟信用卡付款。

我找到了如何让它发挥作用。

控制器:

public class MessagesController : ApiController
{
    public async Task<HttpResponseMessage> Post([FromBody] Activity activity, CancellationToken token)
    {
        if (activity != null)
        {
            switch (activity.GetActivityType())
            {
                case ActivityTypes.Message:

                    var container = WebApiApplication.FindContainer();

                    using (var scope = DialogModule.BeginLifetimeScope(container, activity))
                    {
                        await Conversation.SendAsync(activity, () => scope.Resolve<IDialog<object>>(), token);
                    }
                    break;
            }
        }
        return new HttpResponseMessage(HttpStatusCode.Accepted);
    }
}

Global.asax

public class WebApiApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        GlobalConfiguration.Configure(WebApiConfig.Register);

        var builder = new ContainerBuilder();

        builder.RegisterModule(new DialogModule());

        builder.RegisterModule(new MyModule());

        var config = GlobalConfiguration.Configuration;

        builder.RegisterApiControllers(Assembly.GetExecutingAssembly());

        builder.RegisterWebApiFilterProvider(config);

        var container = builder.Build();
        config.DependencyResolver = new AutofacWebApiDependencyResolver(container);
    }

    public static ILifetimeScope FindContainer()
    {
        var config = GlobalConfiguration.Configuration;
        var resolver = (AutofacWebApiDependencyResolver)config.DependencyResolver;
        return resolver.Container;
    }
}

我的模块:

public sealed class MyModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        base.Load(builder);

        builder.Register(
            c => new LuisModelAttribute("MyId", "SubId"))
            .AsSelf()
            .AsImplementedInterfaces()
            .SingleInstance();

        builder.RegisterType<MainDialog>().AsSelf().As<IDialog<object>>().InstancePerDependency();

        builder.RegisterType<LuisService>()
            .Keyed<ILuisService>(FiberModule.Key_DoNotSerialize)
            .AsImplementedInterfaces()
            .SingleInstance();
    }
}

回调方法:

public class OAuthCallbackController : ApiController
{

    [HttpGet]
    [Route("api/OAuthCallback")]
    public async Task OAuthCallback(string state, [FromUri] CancellationToken cancellationToken, [FromUri] string accessToken, [FromUri] string username)
    {
        var resumptionCookie = ResumptionCookie.GZipDeserialize(state);
        var message = resumptionCookie.GetMessage();
        message.Text = "UserIsAuthenticated";

        using (var scope = DialogModule.BeginLifetimeScope(Conversation.Container, message))
        {
            var dataBag = scope.Resolve<IBotData>();
            await dataBag.LoadAsync(cancellationToken);

            dataBag.PrivateConversationData.SetValue("accessToken", accessToken);
            dataBag.PrivateConversationData.SetValue("username", username);

            ResumptionCookie pending;
            if (dataBag.PrivateConversationData.TryGetValue("persistedCookie", out pending))
            {
                dataBag.PrivateConversationData.RemoveValue("persistedCookie");
                await dataBag.FlushAsync(cancellationToken);
            }
        }

        await Conversation.ResumeAsync(resumptionCookie, message, cancellationToken);
    }