I have created a chatbot v4 with Microsoft Bot Framework and it was working fine. We have two environments QA and PROD. As time passed the bot has more functions and Dialogs.
We have discovered that in PROD (that has the same code as QA) it's not working fine, sometimes it exits from the current Dialog and returns to the init.
I have already tried to connect QnA QA base to PROD to see if it is a DB problem, but it hasn't solved the problem.
this is part of my code
in Bot.cs
in OnTurnAsync
if (activity.Type == ActivityTypes.Message)
{
// Continue the current dialog
var dialogResult = await dc.ContinueDialogAsync();
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await NewConversationFlow(turnContext, dc, conversationId, cancellationToken);
break;
case DialogTurnStatus.Waiting:
// The active Dialog is waiting for a response from the user, so do nothing.
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
// do things
await NewConversationFlow(turnContext, dc, conversationId, cancellationToken);
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
in Bot.cs
in NewConversationFlow
var response = await _services.QnAServices["QnA"].GetAnswersAsync(turnContext, new QnAMakerOptions() { Top = 5, ScoreThreshold = 0.1F });
QueryResult qnaAnswer = GetQnaAnswer(response, 0.60);
await _flowService.ShortDelayWithTypingActionAsync(turnContext);
await turnContext.SendActivityAsync(response.Answer, cancellationToken: cancellationToken);
var flowValue = response.Metadata?.Where(metadata => metadata.Name == "flow").Select(metadata => metadata.Value).FirstOrDefault();
if (!string.IsNullOrEmpty(flowValue))
{
switch (flowValue)
{
case ONE:
...
default:
await dc.BeginDialogAsync(nameof(OneAnswerDialog));
break;
case TWO:
...
await dc.BeginDialogAsync(nameof(TwoAnswerDialog));
break;
case SEARCH:
await dc.BeginDialogAsync(nameof(SearchDialog));
break;
}
}
in OneAnswerDialog
// Dialog IDs
profileDialog = nameof(OneAnswerDialog);
// Add control flow dialogs
var firstCaseWaterfallSteps = new WaterfallStep[]
{
GetAnswerAsync,
SearchStepAsync,
};
AddDialog(new WaterfallDialog(profileDialog, firstCaseWaterfallSteps));
AddDialog(new TextPrompt(ResponsePrompt, ValidateResponseAsync));
Usually, it fails after that the user insert an answer after the first question (the one that is printed in NewConversationFlow). I don't know if it fails in GetAnswerAsync or if in ValidateResponseAsync because I can't debug the code in PRD.
in GetAnswerAsync
return await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
in ValidateResponseAsync
PositiveResponse = false;
var value = promptContext?.Recognized?.Value?.Trim() ?? string.Empty;
if (value != string.Empty)
{
promptContext.Recognized.Value = value;
bool userSayYes = false;
var response = await _services.QnAServices["QnA"].GetAnswersAsync(promptContext.Context, new QnAMakerOptions() { Top = 5, ScoreThreshold = 0.1F });
if (response != null && response.Length > 0)
{
var responses = response.Where(resp => resp.Metadata?.Any(metadata => metadata.Name == "metadata") ?? false).Select(x => x.Metadata);
PositiveResponse = responses.Any(metadatas => metadatas.Any(metadata => metadata.Value == "more" || metadata.Value == "no"));
if (!PositiveResponse && responses.Any(metadatas => metadatas.Any(metadata => metadata.Value == "yes")))
{
userSayYes = true;
await _flowService.DelayWithTypingActionAsync(promptContext.Context);
await promptContext.Context.SendActivityAsync("Can I help you with anything else?", cancellationToken: cancellationToken);
}
}
if (response == null || response.Length <= 0 || (!PositiveResponse && !userSayYes))
{
await _flowService.AddOrUpdateQuestion(promptContext.Context.Activity.Conversation.Id, value);
}
else if (userSayYes)
{
await _flowService.RemoveFlowTypeAndQuestion(promptContext.Context.Activity.Conversation.Id);
}
}
return true;
(if PositiveResponse is false, in the successive pass the Dialog end and returns in Bot.cs)
For some reason in PROD the bot is "confused" and exit from the Dialog in GetAnswerAsync or ValidateResponseAsync and recall the NewConversationFlow.
I have read in another post that the delay with the dot typing can cause this problem, I have removed them but the problem persists...
What could be the problem?
What can cause a premature exit from a Dialog?
Thanks in advance.
-------------------------- EDIT --------------------------
I've tunnelled PRD with ngrok and debug it with VS, in this case, the bot it's working...
What can be? There is any Azure configuration that can cause it? I have already checked the billing plan and it isn't the free one.
-------------------------- EDIT 2 --------------------------
I have updated GetAnswerAsync to be sure to that the bot is entering in the Dialog:
protected async Task<DialogTurnResult> GetAnswerAsync(
WaterfallStepContext stepContext,
CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("What do you think? Does it answer your question?");
await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
It seems to exit from the dialog at this point, when waiting the user prompt...
("Oh, sorry to hear that!" Is chit chat, it's not the next step of the WaterFall)
Sometimes, the bot works. It seems to be something "random".
-------------------------- EDIT 3 --------------------------
I still have this problem... I have tried to remove all the settings from Azure to force the app to read it from the file. I have used the same settings in PRD and QA but nothing... Any idea?
In your "GetAnswerAsync" file, try replacing your code with the below snippet. I usually work in Node and occasionally I've experienced the dialog will "reset" if I only return the awaited "send" / "prompt" activity. If I explicitly send the DialogTurnStatus, then the flow moves as expected (i.e. on to the following step).
await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
return new DialogTurnStatus(DialogTurnStatus.waiting);
If not that, could the QnA response scores be too close to the .60 threshold in GetQnaAnswer()? Since results are non-deterministic (meaning the score can fluctuate from instance to instance), perhaps the response isn't always meeting the threshold?
Hope of help!
Related
I'm working on a game, where players can get points, and players can earn points that are saved in my database. I have one method that looks like this:
private async Task SendAnswer(Answer answer)
{
clicked = true;
answerText = answer.AnswerText;
team = await gameRepo.GetTeamByNameAndGameSession(Teamname, GameSessionId);
if (answer.isCorrect)
{
team.TeamPoints = team.TeamPoints + answer.Points;
}
team.Answer = answer;
await gameRepo.UpdateTeam(team);
if (hubConnection is not null)
{
await hubConnection.SendAsync("SendTeamAnswer", team, GameSessionId);
}
}
That one works just fine, but then I also have this one in another view:
private async Task ChooseBestAnswer(Team team)
{
var answer = currentQuestion.Answers.FirstOrDefault();
team.TeamPoints = team.TeamPoints + answer.Points;
await gameRepo.UpdateTeam(team);
}
Both of them uses this method
public async Task UpdateTeam(Team teamToUpdate)
{
var oldTeam = await _context.Teams.FirstOrDefaultAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
if (teamToUpdate is not null)
{
oldTeam = teamToUpdate;
}
_context.SaveChangesAsync();
}
In the first method everything works as it should but at the second "oldteam" suddenly returns that points = 0 although I can see in the database that it is not 0, how is this possible, I use the same method put it fetches a 0 where there isn't any. All the other variables that are returned from the db to "oldteam" are correct it is just the points that suddenly are zero.
Does anyone know what is going on?
A couple of problems with this code:
public async Task UpdateTeam(Team teamToUpdate)
{
var oldTeam = await _context.Teams.FirstOrDefaultAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
if (teamToUpdate is not null)
{
oldTeam = teamToUpdate;
}
_context.SaveChangesAsync();
}
As mentioned in the comments, the SaveChangesAsync isn't awaited, but more importantly, this code doesn't update the team in the database. You are loading the existing team, but then simply overwriting the in-memory reference. That doesn't copy values across. Instead:
public async Task UpdateTeam(Team teamToUpdate)
{
if (teamToUpdate == null) throw new NullReferenceException(nameof(teamToUpdate));
var existingTeam = await _context.Teams.SingleAsync(t => t.TeamName == teamToUpdate.TeamName && t.GameSessionGuid == teamToUpdate.GameSessionGuid);
existingTeam.TeamPoints = teamToUpdate.TeamPoints;
// copy any additional fields that are allowed to be updated.
await _context.SaveChangesAsync();
}
Key changes to consider here. Assert the passed in state early and handle if it's invalid. If you expect 1 team to be found, use Single rather than First, and if an entry is expected, don't use the OrDefault variations. Those should be used only if you expect that an item might not be found. Once we have the existing data record, copy the values that can change across and call SaveChanges, awaiting the async operation.
This code will throw exceptions if expected state isn't valid, but it will throw meaningful exceptions to be handled at an appropriate level. (Rather than less descriptive exceptions when assumptions aren't met, or failing silently.)
I am trying to customize the CoreBot example (https://github.com/microsoft/BotBuilder-Samples/tree/master/samples/csharp_dotnetcore/13.core-bot) , so it can also receive images in addition to text.
While there are plenty of good documentation (below) and responses on stackoverflow, i am new to C# and have difficulties to combine several pieces of code with the C# syntax.
https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-add-media-attachments?view=azure-bot-service-4.0&tabs=csharp
https://learn.microsoft.com/en-us/azure/bot-service/dotnet/bot-builder-dotnet-add-media-attachments?view=azure-bot-service-3.0
https://learn.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-receive-attachments?view=azure-bot-service-3.0
how to send images which are in local folder in microsoft botframework sdk v4 using c#
Can a Bot receive image as message or attachment from a user
On the code below, i am inserting this piece of code in the CoreBot :
var activity = stepContext.Context.Activity
var reply = activity.CreateReply();
if (activity.Attachments != null && activity.Attachments.Any())
{
var messageText = stepContext.Options?.ToString() ?? "this seems to be an image an i am not yet able to understand it";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
Below is the block of code in which i have inserted the "if image, then"
private async Task<DialogTurnResult> ActStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (!_luisRecognizer.IsConfigured)
{
// LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance.
return await stepContext.BeginDialogAsync(nameof(BookingDialog), new BookingDetails(), cancellationToken);
}
var activity = stepContext.Context.Activity;
if (activity.Attachments != null && activity.Attachments.Any())
{
var messageText = stepContext.Options?.ToString() ?? "this seems to be an image an i am not yet able to understand it";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
var luisResult = await _luisRecognizer.RecognizeAsync<FlightBooking>(stepContext.Context, cancellationToken);
switch (luisResult.TopIntent().intent)
{
case FlightBooking.Intent.BookFlight:
await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);
// Initialize BookingDetails with any entities we may have found in the response.
var bookingDetails = new BookingDetails()
{
// Get destination and origin from the composite entities arrays.
Destination = luisResult.ToEntities.Airport,
Origin = luisResult.FromEntities.Airport,
TravelDate = luisResult.TravelDate,
};
// Run the BookingDialog giving it whatever details we have from the LUIS call, it will fill out the remainder.
return await stepContext.BeginDialogAsync(nameof(BookingDialog), bookingDetails, cancellationToken);
I have also added AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt))); in the waterfall declaration as below
public MainDialog(FlightBookingRecognizer luisRecognizer, BookingDialog bookingDialog, ILogger<MainDialog> logger)
: base(nameof(MainDialog))
{
_luisRecognizer = luisRecognizer;
Logger = logger;
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(bookingDialog);
AddDialog(new AttachmentPrompt(nameof(AttachmentPrompt)));
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
IntroStepAsync,
ActStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
The issue is that the code the piece of code I added is not doing anything.
As mentionned, i am a noob to C# and any suggestion or observation would be greatly appreciated!
I'm surprised it even lets you compile with Any(). In my testing, Visual Studio threw build errors.
Change:
if (activity.Attachments != null && activity.Attachments.Any())
to
if (activity.Attachments != null && activity.Attachments.Count > 0)
The above answer assumes that the activity contains an attachment, but just isn't being caught. If activity doesn't even contain an attachment, there's something else wrong. In which case, please include your whole dialog or preferably, a link to your code/repo.
I have this code but but i think its over-complicated and can be simplified.
Also is there a way to go back to a spefici waterfall step if ever the user type "back" without restarting the whole dialog? I am new to this and it's hard to find a guide or online course on botframework v4 since it is new. Any help would be appreciated thanks!
public GetNameAndAgeDialog(string dialogId, IEnumerable<WaterfallStep> steps = null) : base(dialogId, steps)
{
var name = "";
var age = "";
AddStep(async (stepContext, cancellationToken) =>
{
return await stepContext.PromptAsync("textPrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("What's your name?")
});
});
AddStep(async (stepContext, cancellationToken) =>
{
name = stepContext.Result.ToString();
return await stepContext.PromptAsync("numberPrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply($"Hi {name}, How old are you ?")
});
});
AddStep(async (stepContext, cancellationToken) =>
{
age= stepContext.Result.ToString();
return await stepContext.PromptAsync("confirmPrompt",
new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply($"Got it you're {name}, age {age}. {Environment.NewLine}Is this correct?"),
Choices = new[] {new Choice {Value = "Yes"},
new Choice {Value = "No"},
}.ToList()
});
});
AddStep(async (stepContext, cancellationToken) =>
{
var result = (stepContext.Result as FoundChoice).Value;
if(result == "Yes" || result == "yes" || result == "Yeah" || result == "Correct" || result == "correct")
{
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.Name = name;
state.Age = int.Parse(age);
return await stepContext.BeginDialogAsync(MainDialog.Id, cancellationToken);
}
else
{
//restart the dialog
return await stepContext.ReplaceDialogAsync(GetNameAndAgeDialog.Id);
}
});
}
public static string Id => "GetNameAndAgeDialog";
public static GetNameAndAgeDialog Instance { get; } = new GetNameAndAgeDialog(Id);
}
And this is my accessors code:
public class FPBotAccessors
{
public FPBotAccessors(ConversationState conversationState)
{
ConversationState = conversationState ?? throw new ArgumentNullException(nameof(conversationState));
}
public static string FPBotAccessorName { get; } = $"{nameof(FPBotAccessors)}.FPBotState";
public IStatePropertyAccessor<FPBotState> FPBotStateAccessor { get; internal set; }
public static string DialogStateAccessorName { get; } = $"{nameof(FPBotAccessors)}.DialogState";
public IStatePropertyAccessor<DialogState> DialogStateAccessor { get; internal set; }
public ConversationState ConversationState { get; }
//
public static string ConversationFlowName { get; } = "ConversationFlow";
public IStatePropertyAccessor<ConversationFlow> ConversationFlowAccessor { get; set; }
}
So, there are a couple issues with your code and things you can do to make it better.
State within the dialog
First, let's start with the fact that you're closing over local variables in your constructor and accessing those from the closures that represent your steps. This "works" right now but is ultimately flawed. The initial flaw is different depending on the approach you've taking with instancing your GetNameAndAgeDialog dialog.
If you're using it as a singleton, that means all active conversations between users and your bot would be going through that one instance and you would have a concurrency issue where two users talking to the bot at the same time would be storing their values into the same memory (those variables) and stepping on each other's data.
It's also possible, depending on which samples you're following, that you're instead instantiating your GetNameAndAgeDialog on every turn. This would mean that those variables are then initialized to an empty string on every turn of the conversation and you'd lose track of the original values.
Ultimately, regardless of the instancing used, the approach ends up being flawed no matter what when it comes to scale out because at best your state would be pegged to a single server instance and if one turn of the conversation took place on ServerA and the next turn of the conversation took place on ServerM then ServerM would not have the values from the previous turn.
Alright, so clearly you need to store them with some kind of proper state management mechanism. You're clearly somewhat familiar with using BotState (be it conversation or user scope) already being that you're already using the state property accessors, but it's probably premature to store values you're collecting throughout a multi-turn prompt into someplace more permanent until you're at the end of the collection process. Luckily, dialogs themselves are stored into state, which you may have figured out when you set up a state property accessor for DialogState, and therefore offer a temporarily persistence mechanism that is tied to each dialog's lifetime on the dialog stack. Using this state is not obvious or documented well (yet), but WaterfallDialog actually goes a step further and exposes a first class Values collection via its WaterfallStepContext companion class which is fed into each step. This means that each step of your waterfall flow can add values into the Values collection and access values that previous steps may have put into there. There is a pretty good sample of this in the documentation page titled Create advanced conversation flow using branches and loops.
Not Making The Best Use of Prompts
You're using a TextPrompt for name which is perfect and you'll get a string from it and be all set. Though you might want to consider throwing a validator on there to make sure you get something that looks like a name instead of just allowing any old value.
You appear to be using a NumberPrompt<T> for the age (judging by the name "numberPrompt" at least), but then you .ToString() the step.Result and ultimately do an int.Parse in the final step. Using a NumberPrompt<int> would guarantee you get an int and you can/should just use that value as is rather than turning it back into a string and then parsing it yourself again later.
You've got a prompt named "confirmPrompt", but it does not appear to be an actual ConfirmPrompt because you're doing all the Choice work and positive value detection (e.g. checking for variations of "Yes") yourself. If you actually use a ConfirmPrompt it will do this all of this for you and its result will be a bool which you can then just easily test in your logic.
Minor stuff
Currently you're using stepContext.Context.Activity.CreateReply to create activities. This is fine, but long winded and unecessary. I would highly recommend just using the MessageFactory APIs.
I would always make sure to pass the CancellationToken through to all the XXXAsync APIs that take it... it's just good practice.
Your final step either restarts the GetNameAndAgeDialog if they don't confirm the details or starts the MainDialog if they do confirm the details. Restarting with ReplaceDialogAsync is awesome, that's the right way to do it! I just wanted to point out that by using BeginDialogAsync to start the MainDialog means that you're effectively leaving the GetNameAndAgeDialog at the bottom of the stack for the remainder of the conversation's lifetime. It's not a huge deal, but considering you'll likely never pop the stack back to there I would instead suggest using ReplaceDialogAsync for starting up the MainDialog as well.
Refactored Code
Here is the code rewritten using all of the advice above:
public GetNameAndAgeDialog(string dialogId, IEnumerable<WaterfallStep> steps = null) : base(dialogId, steps)
{
AddStep(async (stepContext, cancellationToken) =>
{
return await stepContext.PromptAsync("textPrompt",
new PromptOptions
{
Prompt = MessageFactory.Text("What's your name?"),
},
cancellationToken: cancellationToken);
});
AddStep(async (stepContext, cancellationToken) =>
{
var name = (string)stepContext.Result;
stepContext.Values["name"] = name;
return await stepContext.PromptAsync("numberPrompt",
new PromptOptions
{
Prompt = MessageFactory.Text($"Hi {name}, How old are you ?"),
},
cancellationToken: cancellationToken);
});
AddStep(async (stepContext, cancellationToken) =>
{
var age = (int)stepContext.Result;
stepContext.Values["age"] = age;
return await stepContext.PromptAsync("confirmPrompt",
new PromptOptions
{
Prompt = MessageFactory.Text($"Got it you're {name}, age {age}.{Environment.NewLine}Is this correct?"),
},
cancellationToken: cancellationToken);
});
AddStep(async (stepContext, cancellationToken) =>
{
var result = (bool)stepContext.Result;
if(result)
{
var state = await (stepContext.Context.TurnState["FPBotAccessors"] as FPBotAccessors).FPBotStateAccessor.GetAsync(stepContext.Context);
state.Name = stepContext.Values["name"] as string;
state.Age = stepContext.Values["age"] as int;
return await stepContext.ReplaceDialogAsync(MainDialog.Id, cancellationToken: cancellationToken);
}
else
{
//restart the dialog
return await stepContext.ReplaceDialogAsync(GetNameAndAgeDialog.Id, cancellationToken: cancellationToken);
}
});
}
Also is there a way to go back to a spefici waterfall step if ever the user type "back" without restarting the whole dialog?
No, there is not a way to do this today. The topic has come up in internal discussions with the team, but nothing has been decided yet. If you think this is a feature that would be useful, please submit an issue over on GitHub and we can see if it gains enough momentum to get the feature added.
I have a subdialog in a bot built using MS bot framework that starts as follows - the standard way:
public async Task StartAsync(IDialogContext context)
{
var msg = "Let's find your flights! Tell me the flight number, city or airline.";
var reply = context.MakeMessage();
reply.Text = msg;
//add quick replies here
await context.PostAsync(reply);
context.Wait(UserInputReceived);
}
This dialog is called using two different ways, depending on whether in the previous screen the user tapped a button that says "Flights" or immediately entered a flight number. Here is the code from the parent dialog:
else if (response.Text == MainOptions[2]) //user tapped a button
{
context.Call(new FlightsDialog(), ChildDialogComplete);
}
else //user entered some text instead of tapping a button
{
await context.Forward(new FlightsDialog(), ChildDialogComplete,
activity, CancellationToken.None);
}
Question: how can I know (from within the FlightsDialog) whether that dialog was called using context.Call() or context.Forward()? This is because in the case of context.Forward(), StartAsync() shouldn't output the prompt asking the user to enter the flight number - they already did this.
The best idea I have is to save a flag in the ConversationData or user data, as below, and access it from the IDialog, but I thought there could be a better way?
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
Unfortunately Forward actually calls Call (and then does some other stuff afterwards), so your Dialog wouldn't be able to differentiate.
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
{
var callRest = ToRest(child.StartAsync);
if (resume != null)
{
var doneRest = ToRest(resume);
this.wait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
}
else
{
this.wait = this.fiber.Call<DialogTask, object>(callRest, null);
}
}
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
{
IDialogStack stack = this;
stack.Call(child, resume);
await stack.PollAsync(token);
IPostToBot postToBot = this;
await postToBot.PostAsync(item, token);
}
From https://github.com/Microsoft/BotBuilder/blob/10893730134135dd4af4250277de8e1b980f81c9/CSharp/Library/Dialogs/DialogTask.cs#L196
I have created a bot, having a FormFlow in it. Now if you type I want to launch a product, LUIS will tell which dialog it has to go to :
internal static IDialog<AssesmentHelper> CreateProduct()
{
return Chain.From(() => FormDialog.FromForm(AssesmentHelper.BuildForm))
.Do(async (context, profileForm) =>
{
try
{
var completed = await profileForm;
}
catch (FormCanceledException<AssesmentHelper> e)
{
string reply;
if (e.InnerException == null)
{
reply = $"You quit on {e.Last}--maybe you can finish next time!";
}
else
{
reply = Responses.ShortCircuit;
}
await context.PostAsync(reply);
}
});
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
stLuis = await LuisHelper.ParseUserInput(activity.Text);
switch (stLuis.topScoringIntent.intent)
{
case "CreateProduct":
await Conversation.SendAsync(activity, CreateProduct);
break;
case "EditProduct":
await Conversation.SendAsync(activity, EditProduct);
break;
case "None":
break;
default:
break;
}
}
Now, once it enters dialog, it asks user to select numbers:
Please select numbers:
Azure
Windows
Now if I reply 1,2. Luis returns it as None intent, so my message does not go to the corresponding dialog. It always go to None case.
The code for dialog is:
public static IForm<AssesmentHelper> BuildForm()
{
return new FormBuilder<AssesmentHelper>()
.Message(Responses.NumberSelection)
.Field(nameof(Program))
.Field(nameof(Product))
.Build();
}
Enum for program and product:
[Serializable]
public enum Program
{
None = 0,
A = 1,
};
[Serializable]
public enum Product
{
None = 0,
Azure = 1,
Windows = 2
};
As soon as I enter this dialog, It asks me to select number for selecting program. Now if i select 1,2. Luis returns it as None intent. So Case "None", is executed.
What I want is, redirect the control to same dialog. I have similar dialog for Edit Product as well. That's why I cannot train my luis app to understand numbers as Product Intent. Otherwise whenever i select number for Edit Product, it will go to different case.
Earlier it was identifying correct intents somehow, but today i republished my luis app and it stopped identifying.