问题
I have a bot that is working fine in Bot emulator with all its LUIS,QnAMaker and Dispatch. I have many intents including Help which all works perfectly in Bot Emulator but when published on azure and messenger, Anything i type it only returns two intents, Cancel and sometimes None Intent.
Bot emulator
In Messenger after publishing in Azure
My LUIS intents
MainDialog: (Dialog A and Dialog B is same as maindialog but inherits ComponentDialog instead of InterruptDialog)
public class MainDialog : InterruptDialog
{
private const string InitialId = nameof(MainDialog);
public MainDialog()
: base(nameof(MainDialog))
{
InitialDialogId = InitialId;
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
FirstStepAsync,
SecondStepAsync,
};
AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new DialogA());
AddDialog(new DialogB());
}
private async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("<Start of MainDialog>");
return await stepContext.PromptAsync(
nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"What do you want to do next?"),
Choices = new List<Choice>
{
new Choice
{
Value = "Nothing",
Synonyms = new List<string>
{
"nothing",
},
},
},
RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
});
}
private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
await stepContext.Context.SendActivityAsync("End of Main");
return await stepContext.EndDialogAsync();
}
Interrupt Dialog:
public class InterruptDialog : ComponentDialog
{
public InterruptDialog(string id)
: base(id)
{
}
protected override async Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
private async Task<DialogTurnResult> IsTurnInterruptedAsyncHelpAndCancel(DialogContext innerDc, CancellationToken cancellationToken)
{
var recognizerResult = innerDc.Context.TurnState.Get<RecognizerResult>("recognizerResult");
string topIntent = string.Empty;
var luisResult = recognizerResult.Properties["luisResult"] as LuisResult;
var result = luisResult.ConnectedServiceResult;
if(result != null)
{
topIntent = result.TopScoringIntent.Intent;
}
if (topIntent.Equals("MainDialog"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
else
{
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
if (topIntent.Equals("DialogB"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogB));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogB));
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
if (topIntent.Equals("DialogA"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogA));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogA));
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
if (topIntent.Equals("Cancel"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.Context.SendActivityAsync("👍 Ok. I've cancelled our last activity.");
}
else
{
await innerDc.Context.SendActivityAsync("I don't have anything to cancel.");
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
if (topIntent.Equals("Help"))
{
await innerDc.Context.SendActivityAsync("Let me help you. 😉");
if (innerDc.ActiveDialog != null)
{
await innerDc.RepromptDialogAsync();
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
return null;
}
}
}
Botservice
public class BotServices : IBotServices
{
public BotServices(IConfiguration configuration)
{
DispatchService = new LuisRecognizer(new LuisApplication(
configuration["DispatchLuisAppId"],
configuration["DispatchLuisAPIKey"],
$"https://{configuration["DispatchLuisAPIHostName"]}"),
new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
true);
LuisService = new LuisRecognizer(new LuisApplication(
configuration["LuisAppId"],
configuration["LuisAPIKey"],
$"https://{configuration["LuisAPIHostName"]}"),
new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
true);
QnaService = new QnAMaker(new QnAMakerEndpoint
{
KnowledgeBaseId = configuration["QnAKnowledgebaseId"],
EndpointKey = configuration["QnAEndpointKey"],
Host = configuration["QnAEndpointHostName"]
});
}
public LuisRecognizer DispatchService { get; private set; }
public LuisRecognizer LuisService { get; private set; }
public QnAMaker QnaService { get; private set; }
}
DialogBot
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
public readonly IStatePropertyAccessor<DialogState> _dialogAccessor;
protected readonly Dialog Dialog;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
private readonly IBotServices BotServices;
private DialogSet Dialogs { get; set; }
public DialogBot(IBotServices botServices, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
BotServices = botServices;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogBot<T>)));
RegisterDialogs(Dialogs);
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
var recognizerResult = turnContext.TurnState.Get<RecognizerResult>("recognizerResult");
var topIntent = turnContext.TurnState.Get<string>("topIntent");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await DispatchToTopIntentAsync(turnContext, topIntent, recognizerResult, cancellationToken);
break;
case DialogTurnStatus.Waiting:
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
string text = string.IsNullOrEmpty(turnContext.Activity.Text) ? string.Empty : turnContext.Activity.Text.ToLower();
if (!string.IsNullOrEmpty(text))
{
var recognizerResult = await BotServices.DispatchService.RecognizeAsync(turnContext, cancellationToken);
var topIntent = recognizerResult.GetTopScoringIntent();
var luisResult = recognizerResult.Properties["luisResult"] as LuisResult;
var result = luisResult.ConnectedServiceResult;
var topLuisIntent = result.TopScoringIntent.Intent;
turnContext.TurnState.Add("topIntent", topIntent.intent);
turnContext.TurnState.Add("recognizerResult", recognizerResult);
}
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
private void RegisterDialogs(DialogSet dialogs)
{
dialogs.Add(new MainDialog());
dialogs.Add(new DialogA());
dialogs.Add(new DialogB());
}
private async Task DispatchToTopIntentAsync(ITurnContext turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (dispatch)
{
case "q_thisAzureBotQna":
await DispatchToQnAMakerAsync(turnContext, cancellationToken);
break;
case "l_thisAzureBot-953e":
await DispatchToLuisModelAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
}
}
private async Task DispatchToQnAMakerAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(turnContext.Activity.Text))
{
var results = await BotServices.QnaService.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
private async Task DispatchToLuisModelAsync(ITurnContext turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
switch (topIntent)
{
case "Greeting":
Random r = new Random();
var x = r.Next(1, 3);
switch (x)
{
case 1:
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi!"));
break;
case 2:
await turnContext.SendActivityAsync(MessageFactory.Text($"Hello!"));
break;
case 3:
await turnContext.SendActivityAsync(MessageFactory.Text($"Hey!"));
break;
default:
break;
}
break;
default:
break;
}
}
}
}
Startup:
public class Startup
{
public Startup()
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddSingleton<IStorage, MemoryStorage>();
services.AddSingleton<UserState>();
services.AddSingleton<ConversationState>();
services.AddSingleton<IBotServices, BotServices>();
services.AddTransient<MainDialog>();
services.AddTransient<IBot, DialogBot<MainDialog>>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}
}
}
回答1:
@user10860492, you're one of our most active support users and we're all happy to help you. However, I feel that we've been doing you a disservice by providing answers to your questions that are too easy to implement and don't actually provide you with a better understanding of the Bot Framework--or coding, for that matter.
As such, this answer is going to be a little bit different than most. I'll provide you guidance instead of just an answer. I'd like it to be more of a back-and-forth than just an answer that you can take and run with.
I'll get you started and we can continue in the comments, eventually opening StackOverflow chat to resolve this fully.
So to start with, I'll answer
So i managed to fixed this by not using the injected BotService and just by manually doing the luisRecognizer code in the DialogBot's
OnMessageActivityAsync
directly
Take a look at how your code differs. There is a very key difference between the code you have posted for BotService
and the code you posted in your answer. I'm not going to give away the answer directly, but I'll hint that it has nothing to do with Dependency Injection or any kind of high-level BotFramework or dotnet stuff.
I'll also hint that this is directly related to your "No such host is known" error.
Another issue you have is that you're thinking about Dispatch wrong. I can't see your code, but this likely relates to your "Cancel is the only intent" issue, although may also be part of "No such host is known".
Read here about how Dispatch works
I also recommend looking deep into how this sample works. Here's a quick primer:
- Look how it only uses a single LuisRecognizer in BotServices
- Now, look at how it queries the Dispatch service and then how it routes the top scoring intent to the appropriate service.
- You should be able to see that if this results in calling another LUIS service, it doesn't need to actually call it because it already has the LUIS Child's intent. But if it results in a QnA intent, it queries QnA Maker.
If you've done your reading, you should see that dispatch works like this:
- Your bot calls ONLY the Parent Dispatch LUIS app
- The Parent Dispatch LUIS app looks at its intents to see if any match any of its connected Child apps (LUIS or QnAMaker)
- If it matches LUIS, it returns the top intent from that child app. If it matches QnAMaker, it returns the QnAMaker intent and your code should call QnAMaker
I have a sneaking suspicion that your LUIS apps aren't laid out correctly. Your Parent Dispatch app should really only hold intents that direct to the Child apps. Your Child apps should hold everything else. You should also only need to call the Parent Dispatch app for LUIS and only need to call QnA if the Parent Dispatch app returns the QnA intent.
I know we're in different time zones, so you may not see this until tomorrow morning for you--and I'm not working this weekend, but will be back online to help by 8am PST Monday.
Response 1:
Thank you i would love that. Sometimes i make things work by just doing trial and error and not really learning how and why. mainly because i am not that good and the project needs to finish fast.
Excellent! We've all been there! I think the whole team is on board with this approach. I believe it will help you more but will also be a good avenue for us to start providing more conceptual answers for others when they search for help, as well.
Also i think i understand now (though i have not tested it yet) i think the key difference is that i hard coded the keys and not call them by the configuration. and that botservice has nothing to do with it?
Close! You're right that botservice/DI has nothing to do with it. And it's not because of hardcoded vs. non-hardcoded. If your appsettings.json
has:
"DispatchLuisAPIHostName": "https://southeastasia.api.cognitive.microsoft.com"
...why would that not work for Botservice.cs
? Look at Botservice.cs
closely. Note: I have no idea why that would work in Emulator and not live...unless Emulator does some sort of URL parsing to prevent this error. That's an odd one. Sometimes, though, Emulator caches old values so you need to restart it and/or restart IIS service to ensure it's using the right value.
I have updated the codes in my question. I now only call dispatch, save the the recognizerResult in turnState for me to use it in interruptDialog in a different class. Is it now the correct way sir? Also does this mean it is now only 1 endpoint hit per reply of user? because i read somewhere with dispatch it is 2 endpoint hits per call of luis.
I believe your code looks as it should. Tough to say without testing it. Does it perform how you'd expect?
And you are correct: Using dispatch means that you only get charged one endpoint call per dispatch call (not an additional one for dispatch calling each child app -- but if it has to call QnA after getting the QnA intent, you get charged for the additional QnA call). Edit: Confirmed w/ LUIS team
回答2:
Okay so i think the issue/bug is within the configuration/appsettings and not botservice.
The intents are being detected normally now in messenger
The fix is hardcoding the keys and not using configuration:
namespace SabikoBotV2.Services
{
public class BotServices : IBotServices
{
public BotServices(IConfiguration configuration)
{
DispatchService = new LuisRecognizer(new LuisApplication(
"f2833d51-b9d2-4420xxxxxx",
"4e17f6cf0574497c85cxxxxxxx",
$"https://southeastasia.api.cognitive.microsoft.com"),
new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
true);
LuisService = new LuisRecognizer(new LuisApplication(
"a17b5589-80a4-4245-bfdf-xxxxxx",
"4e17f6cf0574497c85c260bxxxxxxx",
$"https://southeastasia.api.cognitive.microsoft.com"),
new LuisPredictionOptions { IncludeAllIntents = true, IncludeInstanceData = true },
true);
QnaService = new QnAMaker(new QnAMakerEndpoint
{
KnowledgeBaseId = configuration["QnAKnowledgebaseId"],
EndpointKey = configuration["QnAEndpointKey"],
Host = configuration["QnAEndpointHostName"]
});
}
public LuisRecognizer DispatchService { get; private set; }
public LuisRecognizer LuisService { get; private set; }
public QnAMaker QnaService { get; private set; }
}
}
来源:https://stackoverflow.com/questions/57625246/luis-only-picking-up-2-intent-when-published-in-azure-but-is-working-perfectly-i