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);
}
我为我的 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);
}