is there a way I can chain dialog with luis - c#

I am trying to call a dialog from my luis intent.
But I am getting error. please check my code.It reaches the UserInteraction dialog but the control does not go to Getuserpurpose.It shows an error "my bot code is having an issue"
I tried debugging and I found out that the control transfers to Webapi config class after StartAsync Task.
[LuisIntent("Getfile")]
public async Task GetfileIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Sure we can help you on that... ");
//PromptDialog.Text(context, UserEnteredInfo, "can you please provide us with below info. " +
// "Purpose" + "Client name" + "Team Name");
await context.PostAsync("But first i need to know the purpose ");
// Conversation.SendAsync(Activity, () => new UserInteraction());
context.Call(new UserInteraction(),ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<object> result)
{
await context.PostAsync("Prepared an email for you");
context.Wait(MessageReceived);
}
[Serializable]
public class UserInteraction : IDialog<object>
{
protected string Purpose { get; set; }
protected string ClientName { get; set; }
protected string Teamname { get; set; }
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Please enter the purpose");
// context.Wait(this.MessageReceivedAsync);
context.Wait(Getuserpurpose);
}
public async Task Getuserpurpose(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
this.Purpose = message.Text;
await context.PostAsync("Now please tell me the client name");
context.Wait(Getuserclient);
}

I just tested this out, and it does work as expected.
[LuisIntent("Getfile")]
public async Task GetfileIntent(IDialogContext context, LuisResult result)
{
await context.PostAsync("Sure we can help you on that... ");
await context.PostAsync("But first i need to know the purpose ");
context.Call(new UserInteraction(),ResumeAfterFeedback);
}
private async Task ResumeAfterFeedback(IDialogContext context, IAwaitable<object> result)
{
var interactionResult = await result as UserInteraction;
await context.PostAsync("Prepared an email for you");
context.Wait(MessageReceived);
}
[Serializable]
public class UserInteraction : IDialog<object>
{
protected string Purpose { get; set; }
protected string ClientName { get; set; }
protected string Teamname { get; set; }
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync("Please enter the purpose");
context.Wait(Getuserpurpose);
}
public async Task Getuserpurpose(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
this.Purpose = message.Text;
await context.PostAsync("Now please tell me the client name");
context.Wait(Getuserclient);
}
private async Task Getuserclient(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
ClientName = message.Text;
await context.PostAsync("Now please tell me Team Name");
context.Wait(GetUserTeamName);
}
private async Task GetUserTeamName(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
Teamname = message.Text;
await context.PostAsync("Got it");
context.Done(this);
}
}

Related

"Sorry my bot code is having issues" message on calling PromptDialog.Choice()

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.

Luis choosing between two intent

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:

Error 412 - Manage Bot state Data

I've been following Bot Framework documentation to store Bot-Data but even after using the code snippet provided here for handling concurrency I still get error 412 in the emulator. Can you please tell me if I'm saving & retrieving the Bot state correctly.
I've tried setting breakpoints when I'm saving the bot state but it doesn't hit either of the catch blocks.
Emulator's screenshot
namespace HealthBot
{
[Serializable]
[LuisModel("id", "password")]
public class RootDialog : LuisDialog<object>
{
private const string EntityDateTime = "builtin.datetimeV2.date";
protected override async Task MessageReceived(IDialogContext context, IAwaitable<IMessageActivity> item)
{
var message = await item;
if (message.Text == null)
{
await Help(context, item, null);
}
else
{
await base.MessageReceived(context, item);
}
}
[LuisIntent("Claims")]
private async Task Claims(IDialogContext context, IAwaitable<IMessageActivity> activity, LuisResult result)
{
StateClient stateClient = context.Activity.GetStateClient();
BotData userData = stateClient.BotState.GetPrivateConversationData(context.Activity.ChannelId, context.Activity.Conversation.Id ,context.Activity.From.Id);
bool isVerified = userData.GetProperty<bool>("isVerified");
if (isVerified)
{
string message = "Yes, but please tell me your claim ID";
var response = context.MakeMessage();
response.Text = message;
response.Speak = message;
response.InputHint = InputHints.ExpectingInput;
await context.PostAsync(response);
context.Wait(MessageReceived);
}
else
{
await StartVerification(context, activity);
}
}
private async Task StartVerification(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
string message = "What is your customer ID";
var response = context.MakeMessage();
response.Text = message;
response.Speak = message;
response.InputHint = InputHints.ExpectingInput;
await context.PostAsync(response);
context.Wait(VerifyCustomerId);
}
private async Task VerifyCustomerId(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
var message = await activity;
string customerID = message.Text.Replace(" ", "");
string responseMessage = $"You provided, {customerID}";
try
{
StateClient stateClient = context.Activity.GetStateClient();
BotData userData = await stateClient.BotState.GetPrivateConversationDataAsync(context.Activity.ChannelId, context.Activity.Conversation.Id, context.Activity.From.Id, CancellationToken.None);
userData.SetProperty<bool>("isVerified", true);
await stateClient.BotState.SetPrivateConversationDataAsync(context.Activity.ChannelId, context.Activity.Conversation.Id, context.Activity.From.Id, userData);
}
catch (HttpOperationException err)
{
responseMessage = "Oops! something went wrong";
}
catch (Exception ex)
{
responseMessage = ex.Message;
}
finally
{
await context.PostAsync(CreateMessageWith(context, responseMessage));
context.Wait(MessageReceived);
}
}
private IMessageActivity CreateMessageWith(IDialogContext context, string response)
{
var reply = context.MakeMessage();
reply.Text = response;
reply.Speak = response;
reply.InputHint = InputHints.ExpectingInput;
return reply;
}
}
}
In the context of a dialog, I would try using context.PrivateConversationData instead of the StateClient.
You can check the State C# sample to fully understand how this works.

dialog returns to parent dialog after api get call instead of continuing to current dialog

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

Bot Framework Forward Type Arguments Error

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);
}
}

Categories