QnA call fails in Bot Framework, both locally and deployed - c#

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?

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.

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

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

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

Start conversation with a PromptDialog in MS BotFramework

Is there a proper way for bot to start a conversation with PromptDialog.Choice in the Direct Line channel?
I am trying an ugly hack with catching the fist ConversationUpdate activity and creating a fake message from user to initialize the dialog like this:
IMessageActivity greetingMessage = Activity.CreateMessageActivity();
greetingMessage.From = message.Recipient;//from bot
greetingMessage.Recipient = userAccount;//to user
greetingMessage.Conversation = message.Conversation;
greetingMessage.Text = "Hello, I am a bot";
greetingMessage.Locale = "en-us";
greetingMessage.Id = Guid.NewGuid().ToString();
await connector.Conversations.SendToConversationAsync((Activity)greetingMessage);
IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
dialogEntryMessage.Recipient = message.Recipient;//to bot
dialogEntryMessage.From = message.From;//from user
dialogEntryMessage.Conversation = message.Conversation;
dialogEntryMessage.Text = "any text";
dialogEntryMessage.Locale = "en-us";
dialogEntryMessage.ChannelId = message.ChannelId;
dialogEntryMessage.ServiceUrl = message.ServiceUrl;
dialogEntryMessage.Id = Guid.NewGuid().ToString();
dialogEntryMessage.ReplyToId = greetingMessage.Id;
await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());
Where message is a ConversationUpdate message from. In the RootDialog I start with a PromptDialog.Choice.
It works in the emulator, but in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.
Update
I found a relevant blogpost from Microsoft: https://blog.botframework.com/2018/07/12/how-to-properly-send-a-greeting-message-and-common-issues-from-customers/
in Direct Line channel bot doesn't remember the dialog state and when user choose one of dialog options and send his first real message, root dialog starts again from the PromptDialog.Choice, so it appears twice.
I can reproduce same issue on my side, and I find that ConversationUpdate handler will be executed when both bot and user is added to the conversation.
To solve the issue, you can refer to the following code sample.
In MessagesController:
else if (message.Type == ActivityTypes.ConversationUpdate)
{
// Handle conversation state changes, like members being added and removed
// Use Activity.MembersAdded and Activity.MembersRemoved and Activity.Action for info
// Not available in all channels
if (update.MembersAdded != null && update.MembersAdded.Any())
{
foreach (var newMember in update.MembersAdded)
{
if (newMember.Name == "{your_botid_here}")
{
IMessageActivity greetingMessage = Activity.CreateMessageActivity();
//...
//your code logic
//...
IMessageActivity dialogEntryMessage = Activity.CreateMessageActivity();
dialogEntryMessage.Recipient = message.Recipient;//to bot
dialogEntryMessage.From = message.From;//from user
dialogEntryMessage.Conversation = message.Conversation;
dialogEntryMessage.Text = "show choices";
dialogEntryMessage.Locale = "en-us";
dialogEntryMessage.ChannelId = message.ChannelId;
dialogEntryMessage.ServiceUrl = message.ServiceUrl;
dialogEntryMessage.Id = System.Guid.NewGuid().ToString();
dialogEntryMessage.ReplyToId = greetingMessage.Id;
await Conversation.SendAsync(dialogEntryMessage, () => new Dialogs.RootDialog());
}
}
}
}
In RootDialog:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var mes = activity.Text.ToLower();
string[] choices = new string[] { "choice 1", "choice 2" };
if (Array.IndexOf(choices, mes) > -1)
{
await context.PostAsync($"You selected {mes}");
}
else if(mes == "show choices")
{
PromptDialog.Choice(context, resumeAfterPrompt, choices, "please choose an option.");
}
else
{
await context.PostAsync($"You sent {activity.Text} which was {length} characters.");
context.Wait(MessageReceivedAsync);
}
}
private async Task resumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
{
string choice = await result;
await context.PostAsync($"You selected {choice}");
}
Test result:

Categories