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;
}
}
Related
Code flow is as follows:
I have a DialogA which calls a qnaMaker Dialog in its StartAsync method.
The qnaMaker Dialog callback calls promptDialog function in a helper class(which is not a Dialog by the way)
Once user selects appropriate choice on the prompt, a callback is called which post a message
Error appears after the message("Helpful"/ "Not helpful") has been posted
My first incination was to use context.Wait() after the message has been posted from OnOptionSelectedForPrompt callback, bit it seems that calling wait here somehow executes the ResumeAfter callback for DialogA.
Code Sample: DialogA.cs
[Serializable]
public class DialogA : IDialog<object>
{
private string tool = "sometool";
private string userInput = String.Empty;
private bool sendToQnaMaker = false;
public DialogA(string userQuery)
{
if (userQuery.Length > 0)
{
sendToQnaMaker = true;
userInput = userQuery;
}
}
public async Task StartAsync(IDialogContext context)
{
if(sendToQnaMaker)
{
await context.Forward(new QnaDialog(), QnaMakerDialogCallback, BotHelper.CreateMessageActivityForQnaMaker(userInput), CancellationToken.None);
} else
{
await BotHelper.ShowWelcomeMessageOnMenuSection(context, tool);
context.Wait(OnMessageReceived);
}
}
private async Task QnaMakerDialogCallback(IDialogContext context, IAwaitable<object> result)
{
var response = await result;
bool isInQnaMaker = Convert.ToBoolean(response);
BotHelper botHelper = new BotHelper(tool);
botHelper.showPrompt(context)
}
BotHelper.cs
public void showPrompt(IDialogContext context)
{
PromptDialog.Choice(context, OnOptionSelectedForPrompt, Constants.promptChoices, MiscellaneousResponses.answerFeedbackMessage, MiscellaneousResponses.invalidOptionMessage, 3);
}
public async Task OnOptionSelectedForPrompt(IDialogContext context, IAwaitable<string> result)
{
string optionSelected = await result;
switch (optionSelected)
{
case MiscellaneousResponses.helpfulMessage:
string botreply = "Helpful";
await context.PostAsync(botreply);
break;
case MiscellaneousResponses.notHelpfulMessage:
string botResponse = "Not Helpful";
await context.PostAsync(botResponse);
break;
}
}
}
Created a separate dialog instead of using the BotHelper class and properly handled dialog stack using Context.Done and Context.Wait.
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:
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 trying to authenticate a user before proceeding to the rest of the bots functions and have this code to ensure a user exists
[Serializable]
public class ModifyLicenceDialog: IDialog<object>
{
private const string CreateLicence = "Create a new licence";
private const string ModifyEndDate = "Modify end date";
private const string CancelLicence = "Cancel a licence";
public async Task StartAsync(IDialogContext context)
{
if (!CommonConversation.User.IsAuthorized)
{
context.Call(new AuthDialog(), ResumeAfterAuth);
}
}
private async Task ResumeAfterAuth(IDialogContext context, IAwaitable<object> result)
{
await result;
PromptDialog.Choice(context, ResumeAfterChoice, new[] { CreateLicence, ModifyEndDate, CancelLicence },
"What licence function would you like?",
"I am sorry i did not understand that, please select one of the below options");
}
private async Task ResumeAfterChoice(IDialogContext context, IAwaitable<string> result)
{
string selected = await result;
switch (selected)
{
case CreateLicence:
context.Call(new CreateLicenseDialog(), AfterLicenseDialog(CreateLicence));
break;
case ModifyEndDate:
break;
case CancelLicence:
break;
}
}
private ResumeAfter<object> AfterLicenseDialog(IDialogContext context, string licenseEvent)
{
switch (licenseEvent)
{
case CancelLicence:
await context.PostAsync("Auth done");
context.Done(licenseEvent);
break;
}
}
However when my code goes into the AuthDialog when it tries to call the api to get if the user with the provided email exists (specifically at res = await client.GetAsync(uri)) once the api returns a result the bot returns to the parent dialog (ModifyLicenceDialog) and executes ResumeAfterAuth
[Serializable]
public class AuthDialog: IDialog<object>
{
private const string InputEmail = "Please input your email address to continue.";
private int _attempts = 3;
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync(InputEmail);
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
IMessageActivity activity = await result;
string email = activity.Text;
await context.PostAsync("Please wait while we verify this...");
HttpResponseMessage res = new HttpResponseMessage();
HttpClient client = AuthFunctions.Client;
string uri = $"api/v1/auth/licenses/{email}";
res = await client.GetAsync(uri);
if (res.IsSuccessStatusCode)
{
CommonConversation.User = JsonConvert.DeserializeObject<CustomerUser>(await res.Content.ReadAsStringAsync());
await context.PostAsync($"Welcome {CommonConversation.User.FirstName}!");
context.Done(email);
}
else if (_attempts > 0)
{
_attempts--;
await context.PostAsync("I am sorry we do not recognize that email address, lets try again. Please input your email address");
context.Wait(MessageReceivedAsync);
}
else
{
context.Fail(new Exception("I am afraid you are not an authorized user"));
}
}
}
}
Update after some guidance from Nicolas R it turns out that at that point an exception is thrown
the exception is as follows
{"Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host."}
" at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()\r\n at SupportHelpdeskBot.Dialogs.AuthDialog.<MessageReceivedAsync>d__3.MoveNext() in C:\\Workspace\\SupportBot\\SupportHelpdeskBot\\SupportHelpdeskBot\\Dialogs\\AuthDialog.cs:line 41"
I have tried System.Net.ServicePointManager.Expect100Continue = false; to stop this from happening but this hasnt work any suggesitons?
There is no error in your implementation: you are completing your AuthDialog here with your context.Done(email):
if (res.IsSuccessStatusCode)
{
CommonConversation.User = JsonConvert.DeserializeObject<CustomerUser>(await res.Content.ReadAsStringAsync());
await context.PostAsync($"Welcome {CommonConversation.User.FirstName}!");
context.Done(email);
}
That's the normal behaviour. What do you want to implement? I think your question is incomplete
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);
}
}