Bot (V4) shows inconsistent results in diff channel for basic choiceprompt - c#

I am getting inconsistent result for a basic choice prompt in Bot using V4 in different channel. Also when i enter invalid options, it sometime appends the option list in some channle. not sure, what i am doing wrong.
e.g in Emulator
it shows like this which is perfect most of the time
e.g when i publish to azure web app bot, in test in web chat. it does not show choice in boxes but show as suggestion list and also appends the list of options after i enter incorrect option.
see in portal Web chat
e.g in Microsoft Teams, it does not show choice in boxes but shows as suggestion list and also appends the list of options after i enter incorrect option.
see here in Microsoft Teams
My code is like below
public MytestBotRootDialog()
: base(nameof(PowerBICSSRootDialog))
{
this.rootChoices = new List<Choice>();
this.rootChoices.Add(new Choice { Value = "Service"});
this.rootChoices.Add(new Choice { Value = "Desktop"});
this.rootChoices.Add(new Choice { Value = "Other"});
var waterfallSteps = new WaterfallStep[]
{
PromptForRootChoicesAsync,
RouteToSpecificDialogAsync,
RepeatRootChoiceAsync
};
AddDialog(new WaterfallDialog(rootdialog, waterfallSteps));
AddDialog(new ChoicePrompt(rootchoicesprompt));
AddDialog(new ContentPackRootDialog());
}
private async Task<DialogTurnResult> PromptForRootChoicesAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var opts = new PromptOptions
{
Prompt = MessageFactory.Text("Select the Area"),
Choices = rootChoices,
RetryPrompt = MessageFactory.Text("Please enter valid Area"),
};
return await stepContext.PromptAsync(rootchoicesprompt, opts, cancellationToken);
}
private async Task<DialogTurnResult> RouteToSpecificDialogAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var selectedchoice = stepContext.Result as FoundChoice;
var result = selectedchoice.Value.ToLowerInvariant();
if (selectedchoice.Index == 0 || result.Contains("service"))
{
var context = stepContext.Context;
await context.SendActivityAsync($"You Selected Service");
return await stepContext.NextAsync();
}
else if (selectedchoice.Index == 1 || result.Contains("desktop"))
{
var context = stepContext.Context;
await context.SendActivityAsync($"You Selected Dekstop");
return await stepContext.NextAsync();
}
else if (selectedchoice.Index == 2 || result.Contains("other"))
{
return await stepContext.BeginDialogAsync(nameof(ContentPackRootDialog));
}
else
return await stepContext.NextAsync();
}
private async Task<DialogTurnResult> RepeatRootChoiceAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.ReplaceDialogAsync(rootdialog, null, cancellationToken);
}

Related

How can I get directly response initial dialog in chat bot developed by Microsoft bot frame work

I’m now developing chat bot that reply the question the customer asked. I want to delete the first communication line “Type some words for reply the question”. However, I can’t find the way.
Initialize the communication.
if (member.Id != turnContext.Activity.Recipient.Id)
{
var reply = MessageFactory.Text("質問を続けるには何か入力してください。");
await turnContext.SendActivityAsync(reply, cancellationToken);
}
Ask a customer for a question.
private async Task<DialogTurnResult> InputQuestionAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("MainDialog.InputQuestionAsync");
var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("ご質問を話し言葉で入力して下さい。") };
return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);
}
Reply the question.
private async Task<DialogTurnResult> ShowCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("MainDialog.ShowCardStepAsync");
var receivedMessage = (string)stepContext.Result;
var userProfile = await _userProfileAccessor.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken);
userProfile.Question = receivedMessage;
*
*
*
*
msg = msg + sec.ToString() + ".知りたい質問がありません\n\n" + third.ToString() + ".質問を変える\n\n";
var reply = MessageFactory.Text(msg);
list1.Add(sec.ToString());
list1.Add(third.ToString());
reply.Attachments.Add(Cards.GetHeroCard(list1).ToAttachment());
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("番号を押してください") };
return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);
}
}
[![image of the dialog flow][1]][1]
Add
var accessor = ConversationState.CreateProperty<DialogState>(nameof(DialogState));
await Dialog.RunAsync(turnContext, accessor, cancellationToken);
In
OnMembersAddedAsync and OnMessageActivityAsync solved this issue.

How call a step of waterfall on a particular condition?

I have a bot made using c# and v4 framework. It always asks user do they want to ask more questions from bot with a yes/no prompt:
i want that on "clicking on a particular condition of waterfall should be called" otherwise on all other conditions that step of waterfall should skipped and bot should function normally.I am also adding code just for reference.
The code of main dialog
public MainDialog(ILogger<MainDialog> logger, IBotServices botServices, UserState userState) :
base(nameof(MainDialog))
{ _botServices = botServices;// ?? throw new System.ArgumentNullException(nameof(botServices));
_logger = logger;
_userState = userState;
AddDialog(new ProductIssue($"{nameof(MainDialog)}.fromMain", _botServices, _userState));
AddDialog(new ProductIssue($"{nameof(Confirm)}.fromConfirm", _botServices, _userState));
AddDialog(new ProductIssue($"{ nameof(Resolution)}.resolution", _botServices, _userState));
AddDialog(new PurchaseFlow(_userState));
AddDialog(new Resolution( _userState));
AddDialog(new Confirm(_botServices,_userState));
AddDialog(new ChoicePrompt($"{nameof(MainDialog)}.issue"));
AddDialog(new TextPrompt($"{nameof(MainDialog)}.callDialog"));
AddDialog(new TextPrompt($"{nameof(MainDialog)}.number", ValidationAsync));
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.mainFlow", new WaterfallStep[]
{
MoblieNumberAsync,
ChoiceCardStepAsync,
ShowCardStepAsync,
CallingDialogsAsync
}));
AddDialog(new TextPrompt(nameof(TextPrompt)));
InitialDialogId = $"{nameof(MainDialog)}.mainFlow";
}
private async Task<DialogTurnResult> MoblieNumberAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values[UserInfo] = new UserInput();
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Kindly enter your 10 digit mobile number without any spaces, dashes and country code. We will be sending an OTP later to this number "),
RetryPrompt = MessageFactory.Text("Incorrect mobile number entered. Please only enter the 10 digits of your mobile without any spaces, dashes and country code.")
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.number", options, cancellationToken);
}
private async Task<DialogTurnResult> ChoiceCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userStateAccessors = _userState.CreateProperty<UserInput>(nameof(UserInput));
var userinfo = await userStateAccessors.GetAsync(stepContext.Context, () => new UserInput());
userinfo.phone_no = (string)stepContext.Result;
CustomerDetails customerDetails = new CustomerDetails();
//API-Get Customer Details from CRM
try
{
BotAPIBLL botApiBLL = new BotAPIBLL();
var response = botApiBLL.GetCustomerDetail(stepContext.Context.Activity.Text);
customerDetails = JsonConvert.DeserializeObject<CustomerDetails>(response);
}
catch(Exception ex)
{
}
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Fetching your details from our systems. This may take a moment"), cancellationToken);
if (customerDetails.D != null && !string.IsNullOrEmpty(customerDetails.D.TelNumber) && !string.IsNullOrEmpty(customerDetails.D.NameFirst))
{
DbConnection dbConnection = new DbConnection();
dbConnection.SaveCutomerInfo(customerDetails);
userinfo.Name = customerDetails.D.NameFirst;
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Welcome " + customerDetails.D.NameFirst + ", How can we serve you ? "),
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a option between 1 to 5."),
Choices = GetChoices(),
Style = ListStyle.HeroCard
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.issue", options, cancellationToken);
}
else
{
var options = new PromptOptions()
{
Prompt = MessageFactory.Text("Welcome Guest_" + userinfo.phone_no + ", How can we serve you ? "),
RetryPrompt = MessageFactory.Text("That was not a valid choice, please select a option between 1 to 5."),
Choices = GetChoices(),
Style = ListStyle.HeroCard
};
return await stepContext.PromptAsync($"{nameof(MainDialog)}.issue", options, cancellationToken);
}
}
private async Task<DialogTurnResult> ShowCardStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var userinfo = (UserInput)stepContext.Values[UserInfo];
userinfo.choiceselected = ((FoundChoice)stepContext.Result).Value;
var attachments = new List<Attachment>();
var reply = MessageFactory.Text("");
var user_choice = ((FoundChoice)stepContext.Result).Value;
switch (user_choice)
{
case "Product issue":
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
break;
case "Register Product":
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
case "Online Purchase":
return await stepContext.BeginDialogAsync(nameof(PurchaseFlow), null, cancellationToken);
case "Customer Grivance":
reply.Attachments.Add(Cards.GetHeroCard3().ToAttachment());
break;
case "Order Status":
reply.Attachments.Add(Cards.GetHeroCard6().ToAttachment());
break;
default:
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.CreateAdaptiveCardAttachment3());
reply.AttachmentLayout = AttachmentLayoutTypes.Carousel;
reply.Attachments.Add(Cards.GetHeroCard1().ToAttachment());
break;
}
if (user_choice == "Register Product" || user_choice == "Online Purchase" || user_choice == "Customer Grivance")
{
await stepContext.Context.SendActivityAsync(reply, cancellationToken);
Random r = new Random();
var validationcard = Cards.CreateAdaptiveCardAttachment2(_cards[r.Next(_cards.Length)]);
//var feedbackcard = Cards.CustomerFeedback();
await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(validationcard), cancellationToken);
//await stepContext.Context.SendActivityAsync(MessageFactory.Attachment(feedbackcard), cancellationToken);
var accessor = _userState.CreateProperty<UserInput>(nameof(UserInput));
await accessor.SetAsync(stepContext.Context, userinfo, cancellationToken);
return await stepContext.EndDialogAsync(null, cancellationToken);
}
else
{
var options2 = new PromptOptions() { Prompt = reply, RetryPrompt = MessageFactory.Text("Retry") };
return await stepContext.PromptAsync($"{nameof(MainDialog)}.callDialog", options2, cancellationToken);
}
}
private async Task<DialogTurnResult> CallingDialogsAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(stepContext.Context, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
switch (topIntent.intent)
{
case "Mainissue":
return await stepContext.BeginDialogAsync($"{nameof(MainDialog)}.fromMain", stepContext.Values[UserInfo], cancellationToken);
case "ConfirmIntent":
return await stepContext.BeginDialogAsync(nameof(Confirm), null, cancellationToken);
case "EndBotIntent":
return await stepContext.CancelAllDialogsAsync(true, null, null, cancellationToken);
case "InverterData":
await ProcessSampleQnAAsync(stepContext, cancellationToken);
break;
default:
await stepContext.Context.SendActivityAsync(MessageFactory.Text($"I'm sorry I don't know what you mean."), cancellationToken);
break;
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
private IList<Choice> GetChoices()
{
var cardOptions = new List<Choice>()
{
new Choice() { Value = "Product issue", Synonyms = new List<string>() { "adaptive" } },
new Choice() { Value = "Register Product", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Online Purchase", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Customer Grivance", Synonyms = new List<string>() { "hero" } },
new Choice() { Value = "Order Status", Synonyms = new List<string>() { "hero" } }
};
return cardOptions;
}
private Task<bool> ValidationAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
string value = (string)promptContext.Context.Activity.Text;
if (Regex.IsMatch(value, "^[0-9]{10}$"))
{
return Task.FromResult(true);
}
else
{
return Task.FromResult(false);
}
}
private async Task ProcessSampleQnAAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(stepContext.Context);
if (results.Any())
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await stepContext.Context.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
You can skip the step by evaluating the condition and calling NextAsync, like this:
if (condition)
{
await stepContext.NextAsync(stepContext.Result, cancellationToken);
}
// otherwise process this step
The NextAsync immediately passes control to the next step with whatever you used as the 1st parameter. In this case, I used stepContext.Result, which is the result of the previous step that will be passed to the next step.

QnA call fails in Bot Framework, both locally and deployed

I've been trying to build a bot that uses QnA to anser the user based on categories.
I've managed to connect to QnA correctly and get the first level prompted to the user, however, the problem appears when I try to send to QnA another response from the user.
Here's the dialog that runs smoothly on it's first request.
private async Task<DialogTurnResult> InicioRRHHDialog(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
string intentForQna = IntentEnum.DISPATCH_RRHH;
var castContextForQna = (WaterfallStepContext)stepContext.Parent;
var contextForNextStep = stepContext.Context;
await GetQnaConnection(castContextForQna, intentForQna);
return await stepContext.NextAsync(contextForNextStep, cancellationToken);
}
This is the GetQnaConnection being called.
private async Task GetQnaConnection(WaterfallStepContext stepContext, string intent)
{
servicioQNA = new QnAMakerService(this._qnaService.Url, _qnaRRHHkbId, this._qnaService.QnaEndPointKey);
var mensaje = await servicioQNA.QueryQnAServiceAsync(intent, null);
await ShowResults(stepContext, mensaje);
}
And lastly the ShowResults method that I use to send the activity back to the user.
private static async Task ShowResults(WaterfallStepContext stepContext, QnAResult[] mensaje)
{
Activity outputActivity = null;
QnABotState newState = null;
var qnaAnswer = mensaje[0].Answer;
var prompts = mensaje[0].Context?.Prompts;
if (prompts == null || prompts.Length < 1)
outputActivity = MessageFactory.Text(qnaAnswer);
else
{
newState = new QnABotState
{
PreviousQnaId = mensaje[0].Id,
PreviousUserQuery = stepContext.Result.ToString()
};
outputActivity = CardHelper.GetHeroCard(qnaAnswer, prompts);
}
var outputs = new Activity[] { outputActivity };
foreach (var activity in outputs)
await stepContext.Context.SendActivityAsync(activity).ConfigureAwait(false);
}
Am I handling the Qna connection correctly?

Issue with more than one LUIS Dialog and its entities

I'm try to create a bot with multiple Luis intents, it means multiple dialogs naturally. I've installed the default CoreBot template (Downloaded from Azure). After setting intents and entities;
created second dialog which name is 'WeatherDialog',
created "WeatherDetails" which consists of getter and setter for my LUIS weather entities,
implemented some of code for accessing to entities' results to partial class which is already in the project for BookingDialog.
Then in the MainDialog, I tried to AddDialog.
MainDialog:
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Extensions.Logging;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;
using CoreBot;
namespace Microsoft.BotBuilderSamples.Dialogs
{
public class MainDialog : ComponentDialog
{
private readonly FlightBookingRecognizer _luisRecognizer;
protected readonly ILogger Logger;
// Dependency injection uses this constructor to instantiate MainDialog
public MainDialog(FlightBookingRecognizer luisRecognizer, BookingDialog bookingDialog, CoreBot.Dialogs.WeatherDialog weatherDialog, ILogger<MainDialog> logger)
: base(nameof(MainDialog))
{
_luisRecognizer = luisRecognizer;
Logger = logger;
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(bookingDialog);
AddDialog(weatherDialog);
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
IntroStepAsync,
ActStepAsync,
FinalStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> IntroStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if (!_luisRecognizer.IsConfigured)
{
await stepContext.Context.SendActivityAsync(
MessageFactory.Text("NOTE: LUIS is not configured. To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' to the appsettings.json file.", inputHint: InputHints.IgnoringInput), cancellationToken);
return await stepContext.NextAsync(null, cancellationToken);
}
// Use the text provided in FinalStepAsync or the default if it is the first time.
var messageText = stepContext.Options?.ToString() ?? "How can I help you?";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
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);
}
// Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.)
var luisResult = await _luisRecognizer.RecognizeAsync<CoreBot.WeatherModel>(stepContext.Context, cancellationToken);
switch (luisResult.TopIntent().intent)
{
case CoreBot.WeatherModel.Intent.BookFlight:
//await ShowWarningForUnsupportedCities(stepContext.Context, luisResult, cancellationToken);
Console.WriteLine("This is bookflight");
// 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);
case CoreBot.WeatherModel.Intent.GetWeather:
Console.WriteLine("This is getweather");
var weatherDetails = new CoreBot.WeatherDetails()
{
Location = luisResult.Location,
TravelDate = luisResult.TravelDate,
};
return await stepContext.BeginDialogAsync(nameof(CoreBot.Dialogs.WeatherDialog), weatherDetails, cancellationToken);
default:
// Catch all for unhandled intents
var didntUnderstandMessageText = $"Sorry, I didn't get that. Please try asking in a different way (intent was {luisResult.TopIntent().intent})";
var didntUnderstandMessage = MessageFactory.Text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(didntUnderstandMessage, cancellationToken);
break;
}
return await stepContext.NextAsync(null, cancellationToken);
}
// Shows a warning if the requested From or To cities are recognized as entities but they are not in the Airport entity list.
// In some cases LUIS will recognize the From and To composite entities as a valid cities but the From and To Airport values
// will be empty if those entity values can't be mapped to a canonical item in the Airport.
private static async Task ShowWarningForUnsupportedCities(ITurnContext context, FlightBooking luisResult, CancellationToken cancellationToken)
{
var unsupportedCities = new List<string>();
var fromEntities = luisResult.FromEntities;
if (!string.IsNullOrEmpty(fromEntities.From) && string.IsNullOrEmpty(fromEntities.Airport))
{
unsupportedCities.Add(fromEntities.From);
}
var toEntities = luisResult.ToEntities;
if (!string.IsNullOrEmpty(toEntities.To) && string.IsNullOrEmpty(toEntities.Airport))
{
unsupportedCities.Add(toEntities.To);
}
if (unsupportedCities.Any())
{
var messageText = $"Sorry but the following airports are not supported: {string.Join(',', unsupportedCities)}";
var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput);
await context.SendActivityAsync(message, cancellationToken);
}
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// If the child dialog ("BookingDialog") was cancelled, the user failed to confirm or if the intent wasn't BookFlight
// the Result here will be null.
if (stepContext.Result is BookingDetails result)
{
// Now we have all the booking details call the booking service.
// If the call to the booking service was successful tell the user.
var timeProperty = new TimexProperty(result.TravelDate);
var travelDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now);
var messageText = $"I have you booked to {result.Destination} from {result.Origin} on {travelDateMsg}";
var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(message, cancellationToken);
}
else if (stepContext.Result is CoreBot.WeatherDetails sonuc)
{
var timeProperty = new TimexProperty(sonuc.TravelDate);
var weatherDateMsg = timeProperty.ToNaturalLanguage(DateTime.Now);
var messageText = $"Thats your weather result for {weatherDateMsg} at {sonuc.Location}: 00 F";
var message = MessageFactory.Text(messageText, messageText, InputHints.IgnoringInput);
await stepContext.Context.SendActivityAsync(message, cancellationToken);
}
// Restart the main dialog with a different message the second time around
var promptMessage = "What else can I do for you?";
return await stepContext.ReplaceDialogAsync(InitialDialogId, promptMessage, cancellationToken);
}
}
}
And also my WeatherDialog:
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.BotBuilderSamples.Dialogs;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;
namespace CoreBot.Dialogs
{
public class WeatherDialog : CancelAndHelpDialog
{
private const string DestinationStepMsgText = "Type the location.";
public WeatherDialog()
: base(nameof(WeatherDialog))
{
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));
AddDialog(new DateResolverDialog());
AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
{
LocationStepAsync,
TravelDateStepAsync,
ConfirmStepAsync,
FinalStepAsync,
}));
// The initial child Dialog to run.
InitialDialogId = nameof(WaterfallDialog);
}
private async Task<DialogTurnResult> LocationStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var weatherDetails = (WeatherDetails)stepContext.Options;
if (weatherDetails.Location == null)
{
var promptMessage = MessageFactory.Text(DestinationStepMsgText, DestinationStepMsgText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(TextPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
return await stepContext.NextAsync(weatherDetails.Location, cancellationToken);
}
private async Task<DialogTurnResult> TravelDateStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var weatherDetails = (WeatherDetails)stepContext.Options;
weatherDetails.Location = (string)stepContext.Result;
if (weatherDetails.TravelDate == null || IsAmbiguous(weatherDetails.TravelDate))
{
return await stepContext.BeginDialogAsync(nameof(DateResolverDialog), weatherDetails.TravelDate, cancellationToken);
}
return await stepContext.NextAsync(weatherDetails.TravelDate, cancellationToken);
}
private async Task<DialogTurnResult> ConfirmStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var weatherDetails = (WeatherDetails)stepContext.Options;
weatherDetails.TravelDate = (string)stepContext.Result;
var messageText = $"You want to know weather status at {weatherDetails.Location} for {weatherDetails.TravelDate}. Is this correct?";
var promptMessage = MessageFactory.Text(messageText, messageText, InputHints.ExpectingInput);
return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions { Prompt = promptMessage }, cancellationToken);
}
private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
if ((bool)stepContext.Result)
{
var weatherDetails = (WeatherDetails)stepContext.Options;
return await stepContext.EndDialogAsync(weatherDetails, cancellationToken);
}
return await stepContext.EndDialogAsync(null, cancellationToken);
}
private static bool IsAmbiguous(string timex)
{
var timexProperty = new TimexProperty(timex);
return !timexProperty.Types.Contains(Constants.TimexTypes.Definite);
}
}
}
After I build the code from Visual Studio then start conversation from Bot Framework Emulator.
And the following dialog goes like this:
How is the weather today?
+The bot encounted an error or bug. -> Failed
+To continue to run this bot, please fix the bot source code.
How is the weather today at Los Angeles?
-> Successful
How is the weather at Los Angeles?
+The bot encounted an error or bug. -> Failed
+To continue to run this bot, please fix the bot source code.
book me flight
-> Successful
How is the weather tomorrow at Tokyo?
-> Successful ............
Briefly, when running the bookingDialog with missing parameters (entities), bot asks the missing parameter. However, when I run the weatherDialog with missing parameters conversation overs with error.
When I look the LUIS trace in Bot Framework Emulator, intent and entity recognized successfully.
As you can see, bookingDialog works like a charm but my dialog doesn't work. Can you please help me? What am I missing?

C# bot V4 Text Prompt inside a waterfall dialog

I need to add a question 'Did this help?' after getting the response from QnA and take the feedback from user. If there is no response for this and if the next input is a completely new query, the flow should restart from bot.cs
I tried using a textprompt, but when tested in emulator, bot doesn't wait for user input after the prompt.
Bot.cs
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = turnContext.Activity;
var dc = await _dialogs.CreateContextAsync(turnContext);
if (turnContext == null)
{
throw new ArgumentNullException(nameof(turnContext));
}
if (turnContext.Activity.Type == ActivityTypes.Message)
{
if (turnContext.Activity.Text != null)
{
var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(dc.Context, cancellationToken);
var luisProperties = LuisEntities.FromLuisResults(luisResults);
await _luisEntitiesAccessor.SetAsync(turnContext, luisProperties);
var topScoringIntent = luisResults?.GetTopScoringIntent();
var topIntent = topScoringIntent.Value.intent;
switch (topIntent)
{
case NoneIntent:
await dc.BeginDialogAsync(QnADialog.Name);
break;
case GreetingsIntent:
await dc.BeginDialogAsync(QnAGreetingsDialog.Name);
break;
case CredentialsIntent:
await dc.BeginDialogAsync(CredentialsDialog.Name);
break;
case ContactusIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
case FeedbackIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
default:
await dc.Context.SendActivityAsync("I didn't understand what you just said to me.");
break;
}
}
else if (string.IsNullOrEmpty(turnContext.Activity.Text))
{
await HandleSubmitActionAsync(turnContext, userProfile);
}
}
else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
{
if (turnContext.Activity.MembersAdded != null)
{
await SendWelcomeMessageAsync(turnContext);
}
}
else if (turnContext.Activity.Type == ActivityTypes.Event)
{
await SendWelcomeMessageAsync(turnContext);
}
else
{
await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
}
// Save the dialog state into the conversation state.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
QnADialog.cs - dialog in which I want the prompt to work
public class QnADialog : ComponentDialog
{
public const int QnaNumResults = 1;
public const double QnaConfidenceThreshold = 0.5;
public const string QnaConfiguration = "QnAFaqSubscriptionKey";
private const string QnAFeedbackDialog = "qnaDialog";
public const string Name = "QnA";
public const string TextPrompt = "textPrompt";
private readonly BotServices _services;
private readonly IStatePropertyAccessor<UserProfile> _userProfileAccessor;
Action<string, string, bool, int, int> updateQna;
private int InvalidMessageCount = 0;
string Query = string.Empty;
List<int> qnaIdStorage;
UserProfile userProfile = new UserProfile();
public QnADialog(Action<string, string, bool, int, int> updateQna, bool isCollection, List<int> rotationTemStorage, BotServices services, UserProfile _userProfile, IStatePropertyAccessor<UserProfile> userProfileAccessor, int invalidMessageCount = 0, string dialogId = null)
: base(Name)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_userProfileAccessor = userProfileAccessor ?? throw new ArgumentNullException(nameof(userProfileAccessor));
userProfile = _userProfile;
this.updateQna = updateQna;
this.InvalidMessageCount = invalidMessageCount;
qnaIdStorage = rotationTemStorage;
var waterfallSteps = new WaterfallStep[]
{
BeginStepAsync,
FetchFAQResultStepAsync,
FeedbackStepAsync,
FeedbackResponseStepAsync,
};
AddDialog(new WaterfallDialog(QnAFeedbackDialog, waterfallSteps));
AddDialog(new TextPrompt("userFeed"));
}
public async Task<DialogTurnResult> BeginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
var messageToForward = stepContext.Context.Activity;
UserProfile.previousQuestion = messageToForward.Text;
string[] supportList = { "HELP", "FEEDBACK", "SUPPORT", "ESCALATE", "AGENT" };
if (messageToForward.Text == null || messageToForward.Text.ToLower() == "no")
{
await stepContext.Context.SendActivityAsync("Sorry, I was not able to help you.");
return await stepContext.EndDialogAsync();
}
else if (messageToForward.Text == null || supportList.Any(x => x == messageToForward.Text.ToUpper()))
{
await stepContext.Context.SendActivityAsync("Please reach out to... ");
return await stepContext.EndDialogAsync();
}
else
{
return await stepContext.NextAsync();
}
}
private async Task<DialogTurnResult> FetchFAQResultStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var qnaResult = await FaqQnaMakerService.GetQnaResult(_services, stepContext, this.Query);
var qnaIdColl = GetQnaIdColl(this.Query, qnaResult);
int qnaPreviousId = 0;
int qnaNewId = 0;
if (qnaIdColl != null && qnaIdColl.Count > 1)
{
qnaIdColl = qnaIdColl.Distinct().OrderBy(x => x).ToList();
//Compare the previous Qnaid collection and existing collection , if it is matching produce the result.
var matchItem = qnaIdColl.Intersect(qnaIdStorage);
if (matchItem.Count() == 0)
{
//If there is no previous collection Qna id then take the first item from the existing Qna collection
qnaNewId = qnaIdColl.FirstOrDefault();
}
else
{
//If there any previous Qnaid that contain in the existing collection then pick the next value and generate a new qna result.
qnaPreviousId = matchItem.FirstOrDefault();
qnaNewId = GetNextRotationKey(qnaIdColl, qnaPreviousId);
}
//Create a new response based on selected new qna id.
qnaResult = new[] { qnaResult.Where(x => x.Id == qnaNewId).Single() };
}
if (qnaResult.First().Answer.Length > 0)
{
if (qnaResult.First().Score > 0)
{
updateQna(this.Query, qnaResult.First().Answer, false, qnaPreviousId, qnaNewId);
InvalidMessageCount = 0;
var QuestionCollection = TextFormatter.FormattedQuestionColl(qnaResult.First().Answer);
if (QuestionCollection != null)
{
userProfile.IsAswerCollection = true;
updateQna(this.Query, qnaResult.First().Answer, true, qnaPreviousId, qnaNewId);
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.QnaAttachment(new Tuple<string, string[]>(QuestionCollection.Item2, QuestionCollection.Item3)) };
if (!string.IsNullOrEmpty(QuestionCollection.Item1))
{
await stepContext.Context.SendActivityAsync(QuestionCollection.Item1);
}
await stepContext.Context.SendActivityAsync(replyMessage);
return await stepContext.EndDialogAsync();
}
else
{
await stepContext.Context.SendActivityAsync(qnaResult.First().Answer);
}
}
else
{
InvalidMessageCount++;
return await stepContext.ContinueDialogAsync();
}
}
return await stepContext.NextAsync();
}
private async Task<DialogTurnResult> FeedbackStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync("userFeed", new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Did this help?")
});
}
private async Task<DialogTurnResult> FeedbackResponseStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var mesgActivity = message as Activity;
string var = userProfile.qnaData;
var qnaResultModel = new { InvalidMessageCount = 0, originalQueryText = string.Empty };
NeedMoreInformation needmoreInfo = NeedMoreInformation.NotSelected;
if (message != null && message.Text == null && message.Value != null)
{
dynamic value = mesgActivity.Value.ToString();
UserReply response = JsonConvert.DeserializeObject<UserReply>(value);
if (!string.IsNullOrEmpty(response.Reply))
{
mesgActivity.Text = response.Reply;
}
}
//This if condition work only the user reply back to the question "Did this help?"
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() != "yes" && message?.Text?.ToLower() != "no")
{
//The response message pass to LUIS service to understand the intention of the conversation is “yes” or “no”
bool? moreInformationYes = await LUISService.GetResultAESChatBotYesNo(message?.Text);
if (moreInformationYes != null && moreInformationYes == true)
{
//Once the LUIS understand the conversation change the original message to yes.
message.Text = "yes";
//needmoreInfo = NeedMoreInformation.Yes;
}
else if (moreInformationYes != null && moreInformationYes == false)
{
////Once the LUIS understand the conversation change the original message to no.
message.Text = "no";
needmoreInfo = NeedMoreInformation.No;
}
else
{
needmoreInfo = NeedMoreInformation.None;
}
}
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "yes")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
var output = JsonConvert.DeserializeObject<UserReply>(stepContext.Context.Activity.Value.ToString());
if (userProfile.feedbackCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.FeedbackAdapativecard() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
if (output.Reply != "yes")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
}
else if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "no")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
if (value.Type == "GetMoreContent")
{
await AdaptiveCardGetMoreContent(_services, stepContext, userProfile);
}
else if (value.Type == "GetHelpSubmit")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
else if (userProfile.getMoreContentCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.GetMoreContent() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
// context.Wait(AdaptiveCardGetMoreContent);
}
else
{
await stepContext.BeginDialogAsync(nameof(Bot.cs));
}
return await stepContext.EndDialogAsync();
}
}
After this prompt it should go to the next step as added in the waterfall steps but it does not. Any possible suggestions/help would be greatly appreciated. Thanks in advance!
Without seeing the code for your other steps inside the Waterfall such as BeginStepAsync and FetchFAQResultStepAsync it is difficult to give you an exact answer for your scenario.
How I would suggest you accomplish this is through the use of a message with suggested actions underneath this message, once either of this actions is clicked both options will disappear, thus removing the potential for multiple submissions by the same user for the same answer reply.
You have a couple of options here:
1) Use this dated sample that uses v3.9.0 of the Microsoft.Bot.Builder NuGet package, the meat of which is in the QnADialog and FeedbackDialog classes.
The important part is that the QnADialog implements QnAMakerDialog.
2) Right after where you send the reply to the user with the answer (inside FetchFAQResultsStepAsync I assume) you could add the following code:
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "Yes", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "No", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
EDIT
Thank you for providing the full code for your QnADialog class, unfortunately I cannot run it locally because the implementations for methods such as GetQnaIdColl, GetNextRotationKey, TextFormatter.FormattedQuestionColl among other methods and classes that you call but haven't provided. Your code for prompting a user for a response looks right but it sounds like you're not even getting the feedback prompt to show, or you're getting the feedback prompt to show but you get stuck on there - can you confirm which it is? Have you tried stepping through the code to see which path it takes?
Another suggestion would be to separate out your QnA step and Feedback steps into separate Dialogs, I have proved and example feedback dialog below.
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using System.Threading;
using System.Threading.Tasks;
namespace ChatBot.VirtualAssistant.Dialogs
{
public class RateAnswerDialog : ComponentDialog
{
public RateAnswerDialog()
: base(nameof(RateAnswerDialog))
{
InitialDialogId = nameof(RateAnswerDialog);
var askToRate = new WaterfallStep[]
{
AskRating,
FinishDialog
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, askToRate));
}
private async Task<DialogTurnResult> AskRating(WaterfallStepContext sc, CancellationToken cancellationToken)
{
PromptOptions promptOptions = new PromptOptions
{
Prompt = MessageFactory.Text("Was this helpful?")
};
return await sc.PromptAsync(nameof(TextPrompt), promptOptions);
}
private async Task<DialogTurnResult> FinishDialog(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.EndDialogAsync(sc);
}
protected override async Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object context, CancellationToken cancellationToken)
{
var waterfallContext = (WaterfallStepContext)context;
var userResponse = ((string)waterfallContext.Result).ToLowerInvariant();
if (userResponse == "yes")
{
await waterfallContext.Context.SendActivityAsync("Thank you for your feedback");
}
else if (userResponse == "no")
{
await waterfallContext.Context.SendActivityAsync("Sorry I couldn't help you");
}
else
{
await waterfallContext.Context.SendActivityAsync("The valid answers are 'yes' or 'no'");
// TODO reprompt if required
}
return await outerDc.EndDialogAsync();
}
}
}

Categories