If the luis highest intent score for a conversation is 0.15, and the second is 0.14, would it be possible for the bot to ask the user if they meant the first intent or the second intent? If yes how to do so? I've been searching in the documentation samples and there doesn't seem to be any solution except just making more and more utterances so this does not happen; is that correct?
If the luis highest intent score for a conversation is 0.15, and the second is 0.14, would it be possible for the bot to ask the user if they meant the first intent or the second intent? If yes how to do so?
Yes, we can achieve this requirement. The following sample code work for me, you can refer to it.
[Serializable]
public class MyLuisDialog : LuisDialog<object>
{
public MyLuisDialog() : base(new LuisService(new LuisModelAttribute("xxxxxxx",
"xxxxxxx",
domain: "westus.api.cognitive.microsoft.com")))
{
}
//modify Luis request to make it return all intents instead of just the topscoring intent
protected override LuisRequest ModifyLuisRequest(LuisRequest request)
{
request.Verbose = true;
return request;
}
protected override async Task DispatchToIntentHandler(IDialogContext context, IAwaitable<IMessageActivity> item, IntentRecommendation bestIntent, LuisResult result)
{
if (bestIntent.Intent == "FindFood" || bestIntent.Intent == "BuyFood")
{
if (result.Intents[0].Score - result.Intents[1].Score < 0.1)
{
bestIntent.Intent = "FindOrBuyFood";
bestIntent.Score = 1;
}
}
await base.DispatchToIntentHandler(context, item, bestIntent, result);
}
[LuisIntent("Greeting")]
public async Task GreetingIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
//...
//other intent handlers
//...
[LuisIntent("FindFood")]
[LuisIntent("BuyFood")]
public async Task FoodIntent(IDialogContext context, LuisResult result)
{
await this.ShowLuisResult(context, result);
}
[LuisIntent("FindOrBuyFood")]
public async Task FindOrBuyFoodIntent(IDialogContext context, LuisResult result)
{
var food = "food";
if (result.Entities.Count() > 0)
{
food = result.Entities[0].Entity;
}
List<string> options = new List<string>() { $"Find {food}", $"Buy {food}" };
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: options,
prompt: "Hi. Please Select one option :",
retry: "Please try again.",
promptStyle: PromptStyle.Auto
);
}
private async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var option = await result;
//your code logic here
await context.PostAsync($"You selected the '{option}'");
context.Wait(MessageReceived);
}
private async Task ShowLuisResult(IDialogContext context, LuisResult result)
{
await context.PostAsync($"You have reached {result.Intents[0].Intent} intent.");
context.Wait(MessageReceived);
}
}
Test Result:
Related
Requirement
FormStateModel already contains FIRST input that users types.
Code
Simply I want to put the string that is in activity.Text inside FormStateModel:
private IDialog<FormStateModel> MakeRootDialog(string input)
{
return Chain.From(() => new FormDialog<FormStateModel>(
new FormStateModel() { Question = input },
ContactDetailsForm.BuildForm,
FormOptions.None));
}
=
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(
toBot: activity,
MakeRoot: () => this.MakeRootDialog(activity.Text));
}
else
{
await HandleSystemMessageAsync(activity);
}
var response = this.Request.CreateResponse(HttpStatusCode.OK);
return response;
}
On ConversationUpdate I start conversation simply by asking "Please type your Question:"
private static async Task<Activity> HandleSystemMessageAsync(Activity message)
{
switch (message.Type)
{
case ActivityTypes.DeleteUserData:
break;
case ActivityTypes.ConversationUpdate:
await Welcome(message);
break;
(...)
In that way:
private static async Task Welcome(Activity activity)
{
(...)
reply.Text = string.Format("Hello, how can we help you today? Please type your Question:");
await client.Conversations.ReplyToActivityAsync(reply);
(...)
}
But I can not find a way how to pass it. In this case this exception occurs:
anonymous method closures that capture the environment are not serializable, consider removing environment capture or using a reflection serialization surrogate:
Is there any way around that to populate state model at this step?
Solved by calling RootDialog inside MessagesController, then Calling new FormDialog by context.Call(form, (...));
public async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
await Conversation.SendAsync(activity, () => new LayerDialog());
}
LayerDialog:
[Serializable]
public class LayerDialog: IDialog<IMessageActivity>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.OnMessageReceivedAsync);
}
private async Task OnMessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var awaited = await result;
FormStateModel model = new FormStateModel();
model.Value = awaited.Text;
var form = new FormDialog<FormStateModel >(model ,
BuildForm , FormOptions.PromptInStart);
context.Call(form , this.AfterResume);
}
I am starting to develop a simple Bot, evolving from the Echo bot in the documentation. And I've hit an issue quickly.
I have these three methods on my RootDialog:
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Olá! Eu sou um bot!");
await context.PostAsync("Qual é o teu nome?");
context.Wait(NameReceivedAsync);
}
private async Task NameReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
userName = activity.Text;
await context.PostAsync($"Olá {userName}. Podes dizer alguma coisa e eu vou repetir.");
context.Wait(MessageReceivedAsync);
}
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($"Tu disseste { activity.Text}, que tem {length} caracteres");
context.Wait(MessageReceivedAsync);
}
And my MessageController Post method is like this:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog());
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
The idea is for the bot to send two messages right away, wait for input with userName from the user, send another message, and then go to MessageReceivedAsync where he'll start his echo loop. The problem is the bot is not waiting for the inputs, only stopping at the end of the MessageReceivedAsync, where he'll start the echo.
I can't seem to understand why this happens, since from what I've seen the context.Wait(...) should make the Bot wait for input, which is not happening. I'm testing it with the Bot Framework Channel Emulator on Chrome, if that makes any difference.
context.Wait(method) is a little confusing because it actually sets up a “continuation delegate to specify the method that should be called when a new message is received” from: https://learn.microsoft.com/en-us/bot-framework/dotnet/bot-builder-dotnet-dialogs#implementation-details However, the context.Wait(method) in .StartAsync will execute the "method" immediately, since the dialog is being run for the first time.
If you change your code to something like the following, it should work as you expect:
[Serializable]
public class RootDialogTest : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(NameReceivedAsync);
}
private async Task NameReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (!context.UserData.ContainsKey("askedname"))
{
await context.PostAsync("Olá! Eu sou um bot!");
await context.PostAsync("Qual é o teu nome?");
context.UserData.SetValue("askedname", true);
context.Wait(NameReceivedAsync);
}
else
{
var userName = activity.Text;
await context.PostAsync($"Olá {userName}. Podes dizer alguma coisa e eu vou repetir.");
context.Wait(MessageReceivedAsync);
}
}
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($"Tu disseste { activity.Text}, que tem {length} caracteres");
context.Wait(MessageReceivedAsync);
}
}
Edit: another option, that involves fewer changes to your current dialog:
[Serializable]
public class RootDialogTest : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Olá! Eu sou um bot!");
await context.PostAsync("Qual é o teu nome?");
context.Wait(SetupMethodWait);
}
private async Task SetupMethodWait(IDialogContext context, IAwaitable<object> result)
{
context.Wait(NameReceivedAsync);
}
private async Task NameReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var userName = activity.Text;
await context.PostAsync($"Olá {userName}. Podes dizer alguma coisa e eu vou repetir.");
context.Wait(MessageReceivedAsync);
}
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($"Tu disseste { activity.Text}, que tem {length} caracteres");
context.Wait(MessageReceivedAsync);
}
}
From what I can tell from developing with the Bot Framework, the Conversation.SendAsync() works like a context.Forward() forwarding the message onto the RootDialog().
I suggest directing the user to a separate dialog using such as AskNameDialog() using context.Call() and process your code there.
Have a look at the samples the BotBuilder provides
https://github.com/Microsoft/BotBuilder-Samples they should help you in understanding.
i have created a bot using luis and qnamaker dialog. in my questions is a part of the code of LuisDialog.cs . During the conversations if user make a questions that is part of qna intent ( the bot jumb to QnADialog) , but i want to pass to other intent when user make another questions to the bot .
LuisDialog.cs here is my code updated with other intent . I want to quit from qnadialog when user type a questions that correspond to test intent for example
using Microsoft.Bot.Builder.Dialogs;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Microsoft.Bot.Builder.Luis;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Luis.Models;
using Microsoft.Bot.Connector;
using MultiDialogsBot.Dialogs;
using System.Threading;
namespace MultiDialogsBot
{
[LuisModel("xxxxxxx", "yyyyyyyyyyy")]
[Serializable]
public class LuisDialog : LuisDialog<object>
{
private object activity;
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceived);
}
[LuisIntent("None")]
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = $"Désolé je n'ai pas compris '{result.Query}'. Veuillez formuler votre question";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
[LuisIntent("test")]
public async Task test(IDialogContext context, LuisResult result)
{
await context.PostAsync("nous testons");
context.Wait(MessageReceived);
}
[LuisIntent("qna")]
public async Task qna(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
var msg = await activity;
// await context.Forward(new QnADialog(), ResumeAfterOptionDialog, msg, CancellationToken.None);
context.Call(new QnADialog(), this.ResumeAfterOptionDialog);
}
public async Task ResumeAfterOptionDialog(IDialogContext context, IAwaitable<object> result)
{
var messageHandled = await result;
if (messageHandled != null)
{
await context.PostAsync("Désolé je n'ai pas compris");
context.Wait(MessageReceived);
}
}
If you want to treat the user input inside your QnA database first, your should change your logic described in your question and set an overridden QnADialog that will get your user input first, and when there is no reply, call a LuisDialog to try to handle the case with 1 or several interesting intents.
You can check here how the QnAMakerDialog is made. You will see that you probably need to rewrite the class to change the MessageReceivedAsync method to avoid the reply from the QnAMakerDialog here:
if (sendDefaultMessageAndWait)
{
// The following line should be removed if you don't want that the QnADialog replies if no answer found
await context.PostAsync(qnaMakerResults.ServiceCfg.DefaultMessage);
await this.DefaultWaitNextMessageAsync(context, message, qnaMakerResults);
}
Your QnAOverriddenDialog must be called from where your LuisDialog was called previously (from your MessageController I guess, as I don't have the details of your implementation).
And your LuisDialog will look like the following:
[LuisModel("xxxxxxx", "yyyyyyyyyyy")]
[Serializable]
public class LuisDialog : LuisDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceived);
}
[LuisIntent("None")]
[LuisIntent("")]
public async Task None(IDialogContext context, LuisResult result)
{
string message = $"Désolé je n'ai pas compris '{result.Query}'. Veuillez formuler votre question";
await context.PostAsync(message);
context.Wait(this.MessageReceived);
}
[LuisIntent("test")]
public async Task test(IDialogContext context, LuisResult result)
{
await context.PostAsync("nous testons");
context.Wait(MessageReceived);
}
[LuisIntent("yourOtherIntent1")]
public async Task OtherIntent1(IDialogContext context, LuisResult result)
{
await context.PostAsync("fallback 1");
context.Wait(MessageReceived);
}
[LuisIntent("yourOtherIntent2")]
public async Task OtherIntent1(IDialogContext context, LuisResult result)
{
await context.PostAsync("fallback 2");
context.Wait(MessageReceived);
}
public async Task ResumeAfterOptionDialog(IDialogContext context, IAwaitable<object> result)
{
var messageHandled = await result;
if (messageHandled != null)
{
await context.PostAsync("Désolé je n'ai pas compris");
context.Wait(MessageReceived);
}
}
}
Try:
[LuisIntent("qna")]
public async Task qna(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
var msg = await activity;
await context.Forward(new QnADialog(), ResumeAfterQnA, context.Activity, CancellationToken.None);
await this.ShowLuisResult(context, result);
}
private async Task ResumeAfterQnA(IDialogContext context, IAwaitable<object> result)
{
context.Done<object>(null);
}
private async Task ShowLuisResult(IDialogContext context, LuisResult result)
{
await context.PostAsync($"You have reached {result.Intents[0].Intent}. You said: {result.Query}");
context.Wait(MessageReceived);
}
I am getting this following error
when trying to use the MS Bot Framework Example to call a different dialog. This is my code:
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Connector;
namespace ReadMeBot.Dialogs
{
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity != null && activity.Text.ToLower().Contains("what is"))
{
await
context.Forward(new InternetSearchDialog(), this.ResumeAfterInternetSearchDialog, activity, CancellationToken.None);
}
// 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. Thank you!");
context.Wait(MessageReceivedAsync);
}
private async Task ResumeAfterInternetSearchDialog(IDialogContext context, IAwaitable<string> result)
{
}
}
}
How can I solve this? I googled around and nobody seems to have this issue. What am I doing wrong?
Since you are forwarding to another dialog, you don't need to wait in this dialog. You'll want to call context.Wait in the resume though.
Things should work as expected if you change your code to something like this:
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if (activity != null && activity.Text.ToLower().Contains("what is"))
{
await
context.Forward(new InternetSearchDialog(), this.ResumeAfterInternetSearchDialog, activity, CancellationToken.None);
}
else
{
// 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. Thank you!");
context.Wait(MessageReceivedAsync);
}
}
private async Task ResumeAfterInternetSearchDialog(IDialogContext context, IAwaitable<string> result)
{
context.Wait(MessageReceivedAsync);
}
}
In Bot Framework, while using C#, I have a Dialog and use PromptDialog.Choice to let users select a question they are interested in.
But when it runs, I got duplicate replies, as seen in the following picture:
public async Task StartAsync(IDialogContext context)
{
this.ShowOptions(context);
}
private void ShowOptions(IDialogContext context)
{
PromptDialog.Choice(
context,
this.OnOptionSelected,
new List() { ImageOption, ToolOption },
"Please select one of the following category.",
"Not a valid option",
3);
}
private async Task OnOptionSelected(IDialogContext context, IAwaitable result)
{
string optionSelected = await result;
switch (optionSelected)
{
case ImageOption:
context.Call(new ImgRelated(), this.ResumeAfterOptionDialog);
break;
case ToolOption:
context.Call(new ToolPBmDailog(), this.ResumeAfterOptionDialog);
break;
}
}
Anyone have an idea?
ShowOptions needs to be an async method, and it needs to be called with context.Wait(this.ShowOptions) in StartAsync instead of just straight up calling it.
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.ShowOptions);
}
public async virtual Task ShowOptions(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
PromptDialog.Choice<string>(
context,
this.OnOptionSelected,
new List() { ImageOption, ToolOption },
"Please select one of the following category.",
"Not a valid option",
3);
}
You have to make the OnOptionSelected method so as it waits for an input from user before executing it. Also, make the ShowOptions method as async, otherwise you might get an exception, because right now, your ShowOptions method returns void to the StartAsync method. The IAwaitable<IMessageActivity> waits for a response from user. Try the following and see if it works for you.
public async Task StartAsync(IDialogContext context)
{
await this.ShowOptions(context);
}
private async Task ShowOptions(IDialogContext context)
{
PromptDialog.Choice(context, this.OnOptionSelected,
new List() { ImageOption, ToolOption },
"Please select one of the following category.",
"Not a valid option", 3);
}
private async Task OnOptionSelected(IDialogContext context, IAwaitable<IMessageActivity> result)
{
string optionSelected = await result;
switch (optionSelected)
{
case ImageOption:
context.Call(new ImgRelated(), this.ResumeAfterOptionDialog);
break;
case ToolOption:
context.Call(new ToolPBmDailog(), this.ResumeAfterOptionDialog);
break;
}
}