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);
}
Related
I was trying to connect LUIS and QnA, but from the message controller on the first instance it is going to luis and if required accordingly going to QnA, but once in QnA next messages are not being sent to LUIS only executed by QnA. Someone could Help?
messageControler.cs
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
//await Conversation.SendAsync(activity, () => new BasicLuisDialog());
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
//await Conversation.SendAsync(activity, () => new BasicQnAMakerDialog());
await Conversation.SendAsync(activity, () => new BasicLuisDialog());
}
else
{
//await Conversation.SendAsync(activity, () => new BasicQnAMakerDialog());
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
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)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
}
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;
}
}
}
BasicLuisDialog.cs
This is the code for basic luisdialog, from here if the intent matches then it is supposed to provide the required reply else if none it will redirected the search to the basic qna. this is performing only for the first instance. from second instance onwards if it is in qna it does not starting from luis.
public class BasicLuisDialog : LuisDialog<object>
{
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var mForward = await message as Activity;
var username = context.Activity.From.Name;
string reply = $"Hello {username}! Your query we are taking forward, as we are not aware about what exactly you want to know.";
//await context.PostAsync(reply);
await context.Forward(new IDialog(), this.ResumeAfterQnA, mForward, CancellationToken.None);
}
private async Task ResumeAfterQnA(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceived);
}
[LuisIntent("leave.apply")]
public async Task ApplyLeave(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var username = context.Activity.From.Name;
string reply = $"Hello {username}! we are processing it";
await context.PostAsync(reply);
}
[LuisIntent("it")]
public async Task IT(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var username = context.Activity.From.Name;
string reply = $"Hello {username}! we would look into your IT problems shortly";
await context.PostAsync(reply);
}
}
BasicQnAMakerDialog
Basic QnA code is given below. please help me to find where exactly is the problem.
public class IDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
/* Wait until the first message is received from the conversation and call MessageReceviedAsync
* to process that message. */
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
/* When MessageReceivedAsync is called, it's passed an IAwaitable<IMessageActivity>. To get the message,
* await the result. */
var message = await result;
var activity = await result as Activity;
var qnaAuthKey = ConfigurationManager.AppSettings["QnAAuthKey"];
var qnaKBId = ConfigurationManager.AppSettings["QnAKnowledgebaseId"];
var endpointHostName = ConfigurationManager.AppSettings["QnAEndpointHostName"];
// QnA Subscription Key and KnowledgeBase Id null verification
if (!string.IsNullOrEmpty(qnaAuthKey) && !string.IsNullOrEmpty(qnaKBId))
{
// Forward to the appropriate Dialog based on whether the endpoint hostname is present
if (string.IsNullOrEmpty(endpointHostName)) {
await context.Forward(new BasicQnAMakerPreviewDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
else
{
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
}
else
{
await context.PostAsync("Please set QnAKnowledgebaseId, QnAAuthKey and QnAEndpointHostName (if applicable) in App Settings. Learn how to get them at https://aka.ms/qnaabssetup.");
}
//var activity = await result as Activity;
//await context.Forward(new BasicLuisDialog(), ResumeAfterLuisDialog, activity, CancellationToken.None);
}
You need to call context.Done(new MyDialogResult()) when the dialog has finished doing what it was supposed to do. The bot framework keeps a stack of dialogs per conversation and whenever you perform a context.Forward it pushes a new dialog to the stack and every message to the bot will always go the dialog who is at the top of the stack and skip the others below, so when you perform context.Done it pops the current dialog from the stack and the conversation returns to the previous dialog.
I'm working with Microsoft's BotFramework in C#. I have a LUIS Intent, and from there I want to fire a FormFlow, but I honestly don't know how to do it.
[LuisModel("35554cdf-92ae-4413-*", "*")]
[Serializable]
public class LuisIntegration : LuisDialog<Object>
{
public BuildFormDelegate<Cotizacion> _configurecotizacion;
public BuildFormDelegate<Cotizacion> LuisDialog(BuildFormDelegate<Cotizacion> configureCotizacion) => _configurecotizacion = configureCotizacion;
[LuisIntent("Cotizar")]
public async Task CotizarAsync(IDialogContext context, LuisResult result)
{
var entitiesArray = result.Entities;
var form = new FormDialog<Cotizacion>(new Cotizacion(), Cotizacion.BuildForm, FormOptions.PromptInStart, result.Entities);
context.Call(new FormDialog<Cotizacion>(new Cotizacion(), this._configurecotizacion, FormOptions.PromptInStart), ResumeAfterCallback);
}
private async Task ResumeAfterCallback(IDialogContext context, IAwaitable<Cotizacion> result)
{
Cotizacion coti = null;
try
{
coti = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("Cancelaste la cotización.");
return;
}
//context.Wait(MessageReceived);
}
}
public static IForm<Cotizacion> BuildForm()
{
return new FormBuilder<Cotizacion>()
.Message("¡Listo! Necesito algunos datos para cotizar, empecemos.")
.Field(nameof(Nombre))
.Field(nameof(Apellido))
.Field(nameof(Provincia))
.Field(nameof(Localidad))
.Field(nameof(Edad))
.Field(new FieldReflector<Cotizacion>(nameof(Marca))
.SetType(null)
.SetDefine(async (state, field) =>
{
foreach (var prod in await Gets.ObtenerMarcasAsync())
field
.AddDescription(prod, prod)
.AddTerms(prod, prod);
return await Task.FromResult(true);
}))
.Field(new FieldReflector<Cotizacion>(nameof(Modelo))
.SetType(null)
.SetDefine(async (state, field) =>
{
foreach (var prod in await Gets.ObtenerModelosAsync(state.Marca))
field
.AddDescription(prod, prod)
.AddTerms(prod, prod);
return await Task.FromResult(true);
}))
.Field(nameof(Anio))
.Field(nameof(Version))
.Field(nameof(Email))
.Field(nameof(Telefono))
.Field(nameof(Sexo))
.Confirm("Estamos a punto de cotizar tu auto, ¿está correcta la información que ingresaste?")
.OnCompletion(async (context, state) => { ... }
The FormFlow alone works perfectly, the problem is when I added the LuisIntent.
I fixed the problem calling the FormFlow, the problem now is that is not prompting the confirm, therefore is not finishing. It hangs in the last property of the form. I updated the code.
Edit 2:
private async Task ResumeAfterCallback(IDialogContext context, IAwaitable<Cotizacion> result)
{
Cotizacion coti = null;
try
{
coti = await result;
}
catch (OperationCanceledException)
{
await context.PostAsync("Cancelaste la cotización.");
return;
}
context.Wait(MessageReceived);
}
Edit 3:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new LuisIntegration());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Update: the (final) issue is in the definition of the DefineAsyncDelegate parameter to FieldReflector's SetDefine method:
public delegate Task<bool> DefineAsyncDelegate<T>(T state, Field<T> field) where T : class;
It is not explicitly required to be an async method, but since the implementation above uses async calls within it ('Gets.ObtenerXXXAsync()'), the method should return a bool and not a wrapped Task.FromResult<bool>.
If the async was omitted, and the Gets.ObtenerXXXAsync() was called using a Task.Run(...).Result, for example, then it would be correct to return a Task.FromResult(true).
....
Maybe your bot is obsessed with 'Sexo'... :)
Why do you need your BuildFormDelegate<Cotizacion> member variables? Your CotizarAsync method could just look like this:
[LuisIntent("Cotizar")]
public async Task CotizarAsync(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
context.Call(new FormDialog<Cotizacion>(new Cotizacion(), Cotizacion.BuildForm, FormOptions.PromptInStart, result.Entities),
ResumeAfterCallback);
}
which just passes Cotizacion.BuildForm to the FormDialog constructor.
I tried a cut-down version in my LUIS bot (with just Nombre and Apellido in Cotizacion) and it worked OK - the ResumeAfterCallback was called successfully.
Confirmation Prompt with ParseMessage sending text over to Luis, and then I return true or false. I've set debugger in this file and verified response returns bool.
Code in question. When I get result back it says Cannot implicitly convert IAwaitable<bool> to bool, which is where Convert.ToBoolean() came in; still no luck however.
How can I check the returned bool so that I can verify results in this if statement.
In this current code sample it just sends back a message in bot emulator saying:
Exception: Unable to cast object of type ‘Microsoft.Bot.Builder.Internals.Fibers.Wait`2[Microsoft.Bot.Builder.Dialogs.Internals.DialogTask,System.Boolean]’ to type ‘System.IConvertible’.
Edit: Updated for more code
RootDialog.cs
private async Task SendWelcomeMessageAsync(IDialogContext context)
{
await context.PostAsync("Hi, I'm the Basic Multi Dialog bot. Let's get started.");
context.Call(new ConfirmLuisPrompt(), this.ConfirmLuisPromptAfter);
}
private async Task ConfirmLuisPromptAfter(IDialogContext context, IAwaitable<bool> result)
{
//var res = Convert.ToBoolean(result);
var confirm = await result;
if (confirm)
{
//yes
context.Call(FormDialog.FromForm(PersonInfo.BuildForm, FormOptions.PromptInStart), this.PersonInfoAfter);
}else
{
//no
await context.PostAsync($"Ok, let's get you started");
context.Call(FormDialog.FromForm(PatientInfo.BuildForm, FormOptions.PromptInStart), InHospital);
}
}
ConfirmLuisPrompt.cs
[Serializable]
public class ConfirmLuisPrompt : IDialog<bool>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Do you have insurance?");
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
dynamic m = await result;
var message = m.Text.ToString();
//context.Wait( ParseMessage(context, message));
bool response = await ParseMessage(message);
context.Done(response);
//context.PostAsync(response.toString());
}
public bool ParseMessage(string input)
{
LuisClient luisClient = new LuisClient("<key1>", "<key2>");
Task<LuisResult> resultTask = luisClient.Predict(input);
resultTask.Wait();
LuisResult result = resultTask.Result;
if (result.TopScoringIntent.Name == "Yes")
{
return true;
}
else if (result.TopScoringIntent.Name == "No")
{
return false;
}
else
{
return false;
}
}
}
You just need to do the following in your ConfirmLuisPromptAfter method:
var confirm = await result;
if (confirm)
{
...
}
Do not convert just do: result.GetAwaiter().GetResult() which should return a boolean
Hy, for my Bot I need to call a second LUIS dialog instance. But with the Forward function in the first LUIS dialog only normal dialogs work. The intent recognition doesn't work.
So how can I call a new second LUIS dialog in the first LUIS dialog?
MessagesController:
await Conversation.SendAsync(activity, () => new FirstDialogClass());
FirstDialogClass:
[LuisModel("luis", "key")]
[Serializable]
public class FirstDialogClass: LuisDialog<object>
{
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("intension first dialog: none");
// call second luis instance
await context.Forward(new SecondDialogClass(), CallbackFirstDialog, "message", CancellationToken.None);
}
[LuisIntent("Greeting")]
public async Task Hallo(IDialogContext context, LuisResult result)
{
await context.PostAsync("intension first dialog: greeting");
context.Wait(MessageReceived);
}
private async Task CallbackFirstDialog(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("callback first dialog");
context.Wait(MessageReceived);
}
}
SecondDialogClass:
[LuisModel("luis", "key")]
[Serializable]
public class SecondDialogClass : LuisDialog<object>
{
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("intension second dialog: none");
context.Done(new Object());
}
[LuisIntent("Alphabet")]
public async Task Alphabet(IDialogContext context, LuisResult result)
{
await context.PostAsync("intension second dialog: alphabet");
context.Done(new Object());
}
}
I found a solution myself.
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
await context.PostAsync("intension first dialog: none");
// call second luis instance
var message = context.MakeMessage(); // create a message
message.Text = "abc"; // alphabet intension is called
await context.Forward(new SecondDialogClass(), CallbackFirstDialog, message, CancellationToken.None);
}
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.