Integrating QnAMaker and Luis - Exception Stack is empty - c#

Using Luis dialog if the intent is none, I want to forward to QnAMakerDialog for matching QnAPair.
If pair is found, return the answer and return to luis stack. If a pair is not send message "Sorry I don't know that" and return to luis stack awaiting another message.
I am successfully getting response from await context.Forward(qnaDialog, AfterQnADialog, messageToForward, CancellationToken.None). After the response is returned the emulator throws Exception Stack is empty [File Type 'text/plain']
I think the error is in AfterQnADialog but not sure how to fix it. Thanks in advance for your help.
Message Controller*
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootLuisDialog());
}
Luis Dialog Class
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, IAwaitable<IMessageActivity> message ,LuisResult result)
{
var qnaDialog = new QnADialog();
var messageToForward = await message;
await context.Forward(qnaDialog, AfterQnADialog, messageToForward, CancellationToken.None);
}
...
private async Task AfterQnADialog(IDialogContext context, IAwaitable<object> result)
{
var messageHandled = await result;
if (messageHandled == null)
{
await context.PostAsync("I’m not sure what you want.");
}
context.Wait(this.MessageReceived);
}
QnAMakerDialog Class
[QnAMaker("<QnAMakerAppID", "QnAMakerApiKey", "I don't understand this right now! Try another query!", 0.10, 1)]
public class QnADialog : QnAMakerDialog
{
}
}

I do a test with the following sample code to forward a message to the QnAMakerDialog from a LUIS dialog if no intents match, which works for me, you can refer to it.
In LUIS dialog:
[LuisIntent("None")]
public async Task NoneIntent(IDialogContext context, LuisResult result)
{
await context.Forward(new QnADialog(), AfterQnADialog, context.Activity, CancellationToken.None);
}
QnAMakerDialog:
using Microsoft.Bot.Builder.Dialogs;
using QnAMakerDialog;
using QnAMakerDialog.Models;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace HalChatBot.Dialogs
{
[Serializable]
[QnAMakerService("{subscriptionKey_here}", "{ knowledgeBaseId_here}")]
public class QnADialog : QnAMakerDialog<object>
{
public override async Task NoMatchHandler(IDialogContext context, string originalQueryText)
{
await context.PostAsync($"Sorry, I couldn't find an answer for '{originalQueryText}'.");
context.Done(false);
}
public override async Task DefaultMatchHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
await context.PostAsync($"I found {result.Answers.Length} answer(s) that might help...{result.Answers.First().Answer}.");
context.Done(true);
}
}
Note: I get same exception message: "Stack is empty" if I do not override the NoMatchHandler and call context.Done to pass control back to the parent dialog.
Update:
It seems that you are using Microsoft.Bot.Builder.CognitiveServices.QnAMaker in your project. If that is the case, you do not need to install QnAMakerDialog. To avoid "Stack is empty" error, you can override RespondFromQnAMakerResultAsync and call context.Done to pass control back to the parent dialog.
[Serializable]
[QnAMaker("{subscriptionKey_here}", "{knowledgeBaseId_here}")]
public class BaiscQnaDialog : Microsoft.Bot.Builder.CognitiveServices.QnAMaker.QnAMakerDialog
{
protected override async Task RespondFromQnAMakerResultAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
await context.PostAsync($"I found {result.Answers.Count} answer(s) that might help...{result.Answers.First().Answer}.");
context.Done(true);
}
}
Test result:

Related

Integrating bot framework Luis with QnA as an Intent, then re-ask user after getting to the QnA

I am trying to make a FAQ intent that act as a QnA dialog, which should re-ask the user after getting into the intent.
Below is my code so in integrating the luis and QnA:
[LuisIntent("FAQ")]
public async Task FAQ(IDialogContext context, LuisResult result)
{
await context.PostAsync("FAQ");
await context.Forward(new QnADialog(), ResumeAfterQnA, context.Activity, CancellationToken.None);
}
private async Task ResumeAfterQnA(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Back to Intent");
context.Wait(MessageReceived);
}
While in the QnA Dialog:
[Serializable]
[QnAMakerService("endpoint", "knowledge base id", "subscription key")]
public class QnADialog : QnAMakerDialog<object>
{
public bool flag = false;
public override async Task DefaultMatchHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
if (result.Answers.Length > 0 && result.Answers.FirstOrDefault().Score > 0.75 && flag)
{
await context.PostAsync(result.Answers.FirstOrDefault().Answer);
await context.PostAsync("To continue using the FAQ please type another question, if not type no");
}
else if (originalQueryText.Contains("no"))
{
context.Done(true);
}
else
{
await base.DefaultMatchHandler(context, originalQueryText,result);
flag = true;
}
}
}
The test result is the following:
i would like for the "No Good match found in KB" to not show after the welcome to the FAQ but struggling to do so, i already look at the documentation samples but there's isn't any similar samples with my problem.
Any help will be appreciated
i would like for the "No Good match found in KB" to not show after the welcome to the FAQ
Based on your code and requirement, I modified the code in DefaultMatchHandler method, which work for me, you can refer to it.
public override async Task DefaultMatchHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
if (result.Answers.Length > 0 && result.Answers.FirstOrDefault().Score > 0.75 && flag)
{
await context.PostAsync(result.Answers.FirstOrDefault().Answer);
await context.PostAsync("To continue using the FAQ please type another question, if not type no");
}
else if (originalQueryText.Contains("no"))
{
context.Done(true);
}
else
{
//detect if originalQueryText contains "faq"
if (!originalQueryText.ToLower().Contains("faq"))
{
await base.DefaultMatchHandler(context, originalQueryText, result);
}
flag = true;
}
}
Test result:

Valid KnowledgeBaseId and SubscriptionKey not provided

I need help, after migrating to the new QNAMaker, im now getting an error: Valid KnowledgeBaseId and SubscriptionKey not provided. Use QnAMakerServiceAttribute or set fields on QnAMakerDialog. What am i missing? Subscription Key and KnowledgebaseID was already added. I simply followed the sample http request upong publishing in the qnamaker portal.
using Microsoft.Bot.Builder.Dialogs;
using QnAMakerDialog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Threading;
using Microsoft.Bot.Connector;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.CognitiveServices.QnAMaker;
namespace TEST.Dialog
{
[Serializable]
[QnAMaker(authKey:"013ffd97-XXXX-XXXX-981f-ea298085591c", knowledgebaseId:"f81ce668-XXXX-XXXX-XXXX-ad20c5f4d3fa", endpointHostName:"https://XXXX.azurewebsites.net/qnamaker")]
public class QnADialog : QnAMakerDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.PrivateConversationData.TryGetValue("Name", out name);
await context.PostAsync($"Hello {name}. The QnA Dialog was started. Ask a question.");
context.Wait(MessageReceivedAsync);
}
public async Task DefaultMatchHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
try
{
// ProcessResultAndCreateMessageActivity will remove any attachment markup from the results answer
// and add any attachments to a new message activity with the message activity text set by default
// to the answer property from the result
// var messageActivity = ProcessResultAndCreateMessageActivity(context, ref result);
if (result.Score > 30 && result.Answer != NOMATCH)
{
await context.PostAsync(result.Answer);
context.Wait(this.MessageReceived);
return;
}
else
{
await context.Forward(new RootLuisDialog(), DialogsCompleted, context.Activity, CancellationToken.None);
}
}
catch (Exception ex)
{
throw;
}
}
public override async Task NoMatchHandler(IDialogContext context, string originalQueryText)
{
try
{
await context.Forward(new RootLuisDialog(), DialogsCompleted, context.Activity, CancellationToken.None);
}
catch (Exception ex)
{
throw;
}
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
// calculate something for us to return
int length = (activity.Text ?? string.Empty).Length;
// return our reply to the user
//await context.PostAsync($"You sent {activity.Text} which was {length} characters");
context.Wait(this.MessageReceived);
return;
}
private async Task DialogsCompleted(IDialogContext context, IAwaitable<object> result)
{
var success = await result;
//if (!(bool)success)
// await context.PostAsync("I'm sorry. I didn't understand you.");
//context.UserData.Clear();
context.Wait(MessageReceived);
}
[QnAMakerResponseHandler(30)]
public async Task LowScoreHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
//await context.PostAsync($"I found an answer that might help: {result.Answer}");
await context.Forward(new RootLuisDialog(), DialogsCompleted, context.Activity, CancellationToken.None);
//context.Wait(MessageReceived);
}
}
}
RootDialog that calls QNAMaker:
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.FormFlow;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
namespace Test.Dialog
{
[Serializable]
public class RootDialog : IDialog<object>
{
public string name = string.Empty;
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
try
{
//Some User Code that retreives user via PrivateConversationData
//Calls QNADialog
context.Call(new QnADialog(), MessageReceivedAsync);
}
}
}
My Version of CognitiveServices:
Microsoft.Bot.Builder.CognitiveServices.1.1.7
Bot Builder,Bot Connector :3.15.2.2
QNADialog: 1.2.0
As explained on the Generally Available announcement, you can still use Microsoft.Bot.Builder.CognitiveServices package in your code: the code has been changed to handle bot v1 to v3 calls and new v4 (GA version).
You just have to add the necessary information, that is to say the endpoint hostname as previously it was hosted on Microsoft side.
So your dialog code will look like the following (see Github official sample here):
[Serializable]
[QnAMaker("set yout authorization key here", "set your kbid here", <score threshold>, <number of results>, "endpoint hostname")]
public class SimpleQnADialog : QnAMakerDialog
{
}
It seems that you are using QnAMakerDialog with QnA Maker service v4.0, but in description of NuGet package QnAMakerDialog and GitHub readme of QnAMakerDialog (updated to work with QnA Maker v3 API), we can find that this QnAMakerDialog nugget package can work with v3 of the QnA Maker API, not QnA Maker service v4.0.
To work with QnA Maker service v4.0, as Nicolas R said, you can use this NuGet package: Microsoft.Bot.Builder.CognitiveServices to create your QnAMakerDialog.
[Serializable]
public class QnADialog : QnAMakerDialog
{
public QnADialog() : base(new QnAMakerService(new QnAMakerAttribute("{qnaAuthKey_here}", " {knowledgebaseId_here}", "No good match in FAQ.", 0.5, 1, "https://xxxx.azurewebsites.net/qnamaker")))
{ }
//other code logic
}

There was an error sending this message to your bot: HTTP status code InternalServerError

my bot suddenly stopped wirking ...
I have create a bot using botframework, luis.ai c# . I have deploy in azure .
The bot was working very good , but now i get this code when i send a message to my bot : There was an error sending this message to your bot: HTTP status code InternalServerError .
I try to generate another key in my luis.ai and i added in my code ...but still i have the same problem .
P.S my botframework is updated with the last version.
MessagesController.cs :
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web.Http;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace FirstBotApplication
{
//[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new AllTheBot());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
else if (message.Type == ActivityTypes.ConversationUpdate)
{
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}
AllTheBot.cs
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Builder.Luis;
using Microsoft.Bot.Builder.Luis.Models;
using Microsoft.Bot.Connector;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Web;
namespace FirstBotApplication
{
// [LuisModel("Please Enter Your LUIS Model ID", "Please Enter Your LUIS
Subscription Key")]
[Serializable]
[LuisModel("xxxxxxxx",
"yyyyyyy")]
public class AllTheBot : LuisDialog<object>
{
// internal static string results;
[LuisIntent("None")]
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = $"Sorry, I did not understand '{result.Query}'. Please reformulate your question";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
// private const string HeroCard = "Hero card";
[LuisIntent("grettings")]
[LuisIntent("intentfr")]
public async Task Greeting(IDialogContext context,
IAwaitable<IMessageActivity> activity, LuisResult result)
{
await context.PostAsync("Welcome ");
context.Wait(MessageReceived);
}`
}}
Does the code you posted here match your code in exactly, if so you may want to look at formatting errors.
I think the main problem is your declaration of
[LuisIntent("grettings")]
[LuisIntent("intentfr")]
public async Task Greeting(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
await context.PostAsync("Welcome");
context.Wait(MessageReceived);
}
In my experience I'd get errors when I had more than just
IDialogContext and LuisResult as the parameters for a LUIS Task. Try removing IAwaitable<IMessageActivity> activity from your parameters:
[LuisIntent("grettings")]
[LuisIntent("intentfr")]
public async Task Greeting(IDialogContext context, LuisResult result)
{
await context.PostAsync("Welcome");
context.Wait(MessageReceived);
}
I had the same issue. i ended up recreating the bot and restarting my machine. worked like a charm

Chaining multiple dialogs using context.call/done

I have a LuisDialog which calls a particular dialog if an intent is found. There,on the basis of entity extracted, I am calling another dialog, which seems to be throwing some unhandled exception.
Here is the intent in my root LuiDialog calling BuyDialog(I'm saving the LuisResult for later use)-
[LuisIntent("Buy")]
public async Task Buy(IDialogContext context, LuisResult result)
{
var cts = new CancellationTokenSource();
await context.PostAsync("you want to buy");
context.PrivateConversationData.SetValue<LuisResult>("luis", result);
context.Call(new BuyDialog(), ResumeAfterBuyDialog);
}
private async Task ResumeAfterBuyDialog(IDialogContext context, IAwaitable<bool> result)
{
var success = await result;
if (success)
{
await context.PostAsync("How else can I help you?");
context.Wait(MessageReceived);
}
}
Here is my BuyDialog calling another dialog(BookDialog) if entity contains "book" string-
else if(is_book)
{
await context.PostAsync("You selected book category");
context.Call(new BookDialog(),BookFormComplete);
context.Done(true);
}
private async Task BookFormComplete(IDialogContext context, IAwaitable<bool> result)
{
var BookResult = await result;
//do something if result is true
}
And here is my BookDialog-
[Serializable]
public class BookDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
if (message.Text == "mtl100")
{
await context.PostAsync("Correct");
context.Done(true);
}
else
{
await context.PostAsync("Please enter valid course code");
context.Wait(MessageReceivedAsync);
}
I know the problem is in that second context.call(the call to bookDialog), because if I remove it the code works perfectly fine. Right now, I'm getting "sorry, my bot code is having an issue" when it reaches there. Any idea what's wrong?
The context.Done(true) that you are doing in the second dialog must be in the BookDialog ResumeAfter<T> method (BookFormComplete)
The 'Is Book' path should looks like:
else if(is_book)
{
await context.PostAsync("You selected book category");
context.Call(new BookDialog(),BookFormComplete);
}

Is it possible to grab the user's message from the IDialogContext parameter?

In one of the examples on the Microsoft Bot Framework page, they have the following code:
[Serializable]
public class EchoDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
await context.PostAsync("You said: " + message.Text);
context.Wait(MessageReceivedAsync);
}
}
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new EchoDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
Is it possible to grab the user's message that is passed from Post to the StartAsync method via the context parameter? Is it also possible to store things in it? The documentation is going way over my head and I'd just like to know if this thing is modifiable at all.
You can add a constructor to EchoDialog that stores whatever data you like e.g. the Activity instance.

Categories

Resources