I have created a DispatchBot using Luis and QnA and I want to additionally use multiple Dialogs (one for each intent.
Everything is working except if the dialog needs to prompt for a question. If you get the all the entities in the first utterance you can respond and all is good, however if you need to request further info that is when it fails. When the user responds it goes back to OnMessageActivityAsync and then forgets about the dialog.
I understand that I need to run RunAsync(..) to reopen the dialog however I can't get the right context. Everything I have tried either opens the dialog with null Accessors/DialogState or fails to open the dialog.
I am very new to Azure Bot Framework and I have spent days googling but each example doesn't do everything I need to do.
My bot is as follows:
public class DispatchBot : ActivityHandler
{
private readonly ILogger<DispatchBot> _logger;
private readonly IBotServices _botServices;
private readonly DialogSet _dialogSet;
private readonly MilaAccessors _milaAccessors;
private readonly BotState _userState;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger, MilaAccessors accessors)
{
_logger = logger;
_botServices = botServices;
_dialogSet = new DialogSet(accessors.ConversationDialogState);
_milaAccessors = accessors;
_userState = accessors.UserState;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
await base.OnMessageActivityAsync(turnContext, cancellationToken);
await _milaAccessors.ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
List<Dialog> dialogs = _dialogSet.GetDialogs().ToList();
if (dialogs.Any()) //This is always false
{
//If count is greater than zero, then you can continue dialog conversation.
await dialogs.First().RunAsync(turnContext, _milaAccessors.ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
else
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
if (topIntent.score > 0.5)
{
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
else
{
await ProcessQnAAsync(turnContext, cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case "Form15":
await Form15IntentAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "Layouts":
await ProcessLayoutsAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "Weather":
await ProcessWeatherAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
default:
await ProcessQnAAsync(turnContext, cancellationToken);
break;
}
}
}
And the Dialogs are of the form:
public class Form15Dialog : ComponentDialog
{
private const string UserInfo = "form15-userInfo";
private readonly MilaAccessors _milaAccessors;
private readonly string DlgAddressId = "AddressDlg";
private readonly string Form15Id = "Form15DialogName";
private readonly BotState _userState;
private readonly BotState _conversationState;
public Form15Dialog(MilaAccessors milaAccessors) : base(nameof(Form15Dialog))
{
_milaAccessors = milaAccessors;
_userState = milaAccessors.UserState;
_conversationState = milaAccessors.ConversationState;
AddDialog(new TextPrompt(DlgAddressId, AddressValidation));
AddDialog(new WaterfallDialog(nameof(Form15Id), new WaterfallStep[]
{
InitialiseStepAsync,
GetAddressStepAsync,
DisplayForm15StepAsync
}));
InitialDialogId = nameof(Form15Id);
}
private async Task<DialogTurnResult> InitialiseStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
stepContext.Values["MilaAccessors"] = _milaAccessors;
UserProfile userProfile = _milaAccessors.UserProfile.GetAsync(stepContext.Context, () => new UserProfile(), cancellationToken).Result;
Form15DialogValues userInfo = new Form15DialogValues {Name = userProfile.Name, MilaSessionId = userProfile.SessionId};
IList<EntityModel> options = (IList<EntityModel>)stepContext.Options;
foreach (EntityModel model in options)
{
switch (model.Type)
{
case "JobNumber":
userInfo.JobNumber = model.Entity;
break;
case "Sections":
userInfo.Sections = model.Entity;
break;
case "StreetAddress":
userInfo.StreetAddress = model.Entity;
break;
case "Suburb":
userInfo.Suburb = model.Entity;
break;
case "PostCode":
userInfo.PostCode = model.Entity;
break;
}
}
// Create an object in which to collect the user's information within the dialog.
stepContext.Values[UserInfo] = userInfo;
if (UpdateUserInfoFromWebService(userInfo))
{
stepContext.Values[UserInfo] = userInfo;
return await stepContext.NextAsync(new List<string>(), cancellationToken);
}
await _userState.SaveChangesAsync(stepContext.Context, false, cancellationToken);
await _conversationState.SaveChangesAsync(stepContext.Context, false, cancellationToken);
PromptOptions promptOptions = new PromptOptions { Prompt = MessageFactory.Text("Could you give me the full Job Address?") };
return await stepContext.PromptAsync(DlgAddressId, promptOptions, cancellationToken);
}
private async Task<DialogTurnResult> GetAddressStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
Form15DialogValues userInfo = (Form15DialogValues)stepContext.Values[UserInfo];
if (!(stepContext.Result is string[] results) || results.Length == 0)
{
return await stepContext.NextAsync(new List<string>(), cancellationToken);
}
userInfo.JobNumber = null;
userInfo.StreetAddress = results[0];
userInfo.Suburb = null;
if (UpdateUserInfoFromWebService(userInfo))
{
stepContext.Values[UserInfo] = userInfo;
return await stepContext.NextAsync(new List<string>(), cancellationToken);
}
await _userState.SaveChangesAsync(stepContext.Context, false, cancellationToken);
await _conversationState.SaveChangesAsync(stepContext.Context, false, cancellationToken);
var promptOptions = new PromptOptions { Prompt = MessageFactory.Text("I'm unable to find that address. Could you please enter the job number?") };
return await stepContext.PromptAsync(nameof(TextPrompt), promptOptions, cancellationToken);
}
I have been following the info in https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-conversation-flow?view=azure-bot-service-4.0&tabs=csharp however I can not get "Dialog.RunAsync".
Any help/links/pointers you could give me would be most appreciated.
You can inject an instance of your dialog into the bot constructor like below
private readonly BotState _userState;
private readonly Dialog Dialog;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger, MilaAccessors accessors,Form15Dialog dialog)
{
_logger = logger;
_botServices = botServices;
_dialogSet = new DialogSet(accessors.ConversationDialogState);
_milaAccessors = accessors;
_userState = accessors.UserState;
Dialog = dialog;
}
Now you can run the below code in OnMessageActivityAsync to continue with your previous dialog
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
If you never call stepContext.EndDialogAsync WaterfallStep always finishes with the end of the Waterfall. In your code, it's DisplayForm15StepAsync and your dialog will be finished and goes back to OnMessageActivityAsync
So no need to reopen the active dialog, you should re-prompt at your WaterfallStep when it fails and doesn't let it finish until you get the right context
Related
I'm building a bot connected to LUIS to get intents and with some dialogs that make other things.
I have the following dialogs:
MainDialog: that calls LUIS to get intents
DialogOne: with some steps
DialogTwo: with some steps
ScoreDialog: with a survey to get opinnion from user after other dialogs. this dialog can be called from others.
MainDialog is the entry point and this is the code:
public class MainDialog : ComponentDialog
{
public MainDialog() : base($"{nameof(MainDialog)}.main")
{
var waterfallSteps = new WaterfallStep[]
{
RedirectStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(MainDialog)}.main", waterfallSteps));
AddDialog(new DialogOne($"{nameof(MainDialog)}.dialogOne"));
AddDialog(new DialogTwo($"{nameof(MainDialog)}.dialogTwo"));
InitialDialogId = $"{nameof(MainDialog)}.main";
}
private async Task<DialogTurnResult> RedirectStepAsync(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
await waterfallStepContext.Context.SendActivityAsync("What you want?");
var result = await _myLuisService.RecognizeAsync(waterfallStepContext.Context, cancellationToken);
var topIntent = result.GetTopScoringIntent();
switch (topIntent.intent)
{
case "None": break;
case "IntentOne":
await waterfallStepContext.BeginDialogAsync($"{nameof(MainDialog)}.dialogOne", null, cancellationToken);
break;
case "IntentTwo":
await waterfallStepContext.BeginDialogAsync($"{nameof(MainDialog)}.dialogTwo", null, cancellationToken);
break;
}
return await waterfallStepContext.EndDialogAsync(null, cancellationToken);
}
}
Note that MainDialog only have one step that calls to LUIS in order to get user intent.
When user intent is received I switch to another specific dialog with 'BeginDialogAsync' method
For example, we are switching to DialogOne.
This is the code:
public class DialogOne : ComponentDialog
{
public DialogOne(string dialogId) : base(dialogId)
{
var waterfallSteps = new WaterfallStep[]
{
StepOne,
StepTwo,
StepThree
};
AddDialog(new WaterfallDialog($"{nameof(DialogOne)}.main", waterfallSteps));
AddDialog(new TextPrompt($"{nameof(ScoreDialog)}.score"));
InitialDialogId = $"{nameof(DialogOne)}.main";
}
private async Task<DialogTurnResult> StepOne(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
// do something...
}
private async Task<DialogTurnResult> StepTwo(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
// do something...
}
private async Task<DialogTurnResult> StepThree(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
await waterfallStepContext.BeginDialogAsync($"{nameof(DialogOne)}.score", null, cancellationToken);
}
}
This DialogOne contains some steps, and when I finish, I want to show a new one dialog to ask their opinnion about the process. It's a votting process.
Note that I'm using await waterfallStepContext.BeginDialogAsync($"{nameof(DialogOne)}.score", null, cancellationToken); to call ScoreDialog
Finally, the ScoreDialog prompt two options to vote and then, in the next steps we capture and process the response, we thank the user for their vote...
public class ScoreDialog : ComponentDialog
{
public ScoreDialog(string dialogId) : base(dialogId)
{
var waterfallSteps = new WaterfallStep[]
{
AskForScoreStepAsync,
GratitudeForScoreStepAsync
};
AddDialog(new WaterfallDialog($"{nameof(ScoreDialog)}.main", waterfallSteps));
AddDialog(new TextPrompt($"{nameof(ScoreDialog)}.ask"));
InitialDialogId = $"{nameof(ScoreDialog)}.main";
}
private async Task<DialogTurnResult> AskForScoreStepAsync(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
await SaveConversationAsync(waterfallStepContext, data);
return await waterfallStepContext.PromptAsync(
$"{nameof(ScoreDialog)}.ask",
new PromptOptions()
{
Prompt = CreateScoreButtons()
},
cancellationToken);
}
private Activity CreateScoreButtons()
{
var reply = MessageFactory.Text(string.Empty);
reply.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new() { Title = "I like", Value = "I like", Type = ActionTypes.ImBack },
new() { Title = "I dislike", Value = "I like", Type = ActionTypes.ImBack },
}
};
return reply;
}
private async Task<DialogTurnResult> GratitudeForScoreStepAsync(WaterfallStepContext waterfallStepContext, CancellationToken cancellationToken)
{
await waterfallStepContext.Context.SendActivityAsync("Thanks for voting!");
//TODO: do something with the vote
return await waterfallStepContext.EndDialogAsync(null, cancellationToken);
}
}
But, the issue is when I redirects to ScoreDialog. This dialog only shows the first step (AskForScoreStepAsync), but no continues to the last (AskForScoreStepAsync).
What is does is return to MainDialog with the value "I like" or "I dislike" from previous step... this "I like" or "I dislike" text is passed again to LUIS with wrong result (because isn't a valid utterance)
So, what's wrong?
How can I redirect the control from dialog to another dialog, and on this new one dialog, complete all steps?
I'm developing a chatbot using Microsoft Azure Web App Service. Among them, I'd like to insert a hero card in the greeting, and then click Info Dynamics365 among the corresponding hero buttons to start calling the card list source I've created, and if I press FAQ, I'd like to connect with QnAMaker for questioning. If you use ActionType now, both will call up a list of cards, one of which type should be used to connect to QnAMaker, or how to specify a path.
Bpts/QnABot.cs
namespace Microsoft.BotBuilderSamples
{
public class QnABot<T> : ActivityHandler
{
private BotState _conversationState;
private BotState _userState;
//QnAMaker
//protected readonly BotState ConversationState;
//protected readonly Microsoft.Bot.Builder.Dialogs.Dialog Dialog;
//protected readonly BotState UserState;
private string KBID = "fcd905a3-7269-4ea5-9a58-7b02c888ddb6";
private string ENDPOINT_KEY = "7b9a938a-e7ac-46f6-ab69-97d4e2e04f66";
private string HOST = "myfirstqa.azurewebsites.net";
//Azure 첫 세팅 소스
public QnABot(ConversationState conversationState, UserState userState)
{
_conversationState = conversationState;
_userState = userState;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
var card = new HeroCard();
card.Title = "";
card.Text = #"Welcome to Welcome Users bot sample! This Introduction card";
card.Images = new List<CardImage>() { new CardImage("https://www.google.com/url?sa=i&source=images&cd=&ved=2ahUKEwjQjeeS4obmAhUIfnAKHQGgCB0QjRx6BAgBEAQ&url=https%3A%2F%2Fdougame.tistory.com%2F98&psig=AOvVaw11Y-BZJtsxh1pTp0Qxzedb&ust=1574819546545481") };
card.Buttons = new List<CardAction>()
{
new CardAction(ActionTypes.PostBack, "테스트 FAQ연결", null,"Connect QnA-Makeshfrjflrk todrur","Connect QnA-Maker", "Connection QnAMaker"),
new CardAction(ActionTypes.PostBack, "YOUTUBE LINK", null,"Connect YouTube","Connect YouTube", "Connection YouTube"),
new CardAction(ActionTypes.PostBack, "테스트 F연결", null,"Connect QnA-Maker","Connect QnA-Maker", "Connection A")
};
var response = MessageFactory.Attachment(card.ToAttachment());
await turnContext.SendActivityAsync(response, cancellationToken);
}
}
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
//QnA Maker연결
var qnaMaker = new QnAMaker(new QnAMakerEndpoint
{
KnowledgeBaseId = "fcd905a3-7269-4ea5-9a58-7b02c888ddb6",
EndpointKey = "7b9a938a-e7ac-46f6-ab69-97d4e2e04f66",
Host = "myfirstqa.azurewebsites.net"
},
null,
new System.Net.Http.HttpClient());
var conversationStateAccessors = _conversationState.CreateProperty<ConversationService>(nameof(ConversationService));
var conversationService = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationService());
var input = turnContext.Activity.Text;
if (String.IsNullOrEmpty(conversationService.currentService) && (input.Equals("Connection QnAMaker")))
{
conversationService.currentService = input;
await turnContext.SendActivityAsync(MessageFactory.Text("선택 : " + input + " service ,\n 입력할 내용 " + input + " question"), cancellationToken);
}
else if (String.IsNullOrEmpty(conversationService.currentService))
{
await turnContext.SendActivityAsync(MessageFactory.Text("select a service from hero card first"), cancellationToken);
}
else if (conversationService.currentService.Equals("Connection QnAMaker"))
{
//call your dy QNA service here
var result = qnaMaker.GetAnswersAsync(turnContext).GetAwaiter().GetResult();
if (result.Length == 0)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry , I can't find any answer for it"), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text(result[0].Answer), cancellationToken);
}
////await turnContext.SendActivityAsync(MessageFactory.Text(result[0].Answer), cancellationToken);
}
else if (conversationService.currentService.Equals("dy365"))
{
//call your dy 365 service here
await turnContext.SendActivityAsync(MessageFactory.Text("dy365 response"), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("error"), cancellationToken);
};
}
}
public class ConversationService
{
public string currentService { get; set; }
}
}
Dialog/RootDialog.cs
namespace Microsoft.BotBuilderSamples.Dialog
{
public class RootDialog : ComponentDialog
{
private const string InitialDialog = "initial-dialog";
public RootDialog(IBotServices services)
: base("root")
{
AddDialog(new QnAMakerBaseDialog(services));
AddDialog(new WaterfallDialog(InitialDialog)
.AddStep(InitialStepAsync));
// The initial child Dialog to run.
InitialDialogId = InitialDialog;
}
private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
// Set values for generate answer options.
var qnamakerOptions = new QnAMakerOptions
{
ScoreThreshold = QnAMakerBaseDialog.DefaultThreshold,
Top = QnAMakerBaseDialog.DefaultTopN,
Context = new QnARequestContext()
};
// Set values for dialog responses.
var qnaDialogResponseOptions = new QnADialogResponseOptions
{
NoAnswer = QnAMakerBaseDialog.DefaultNoAnswer,
ActiveLearningCardTitle = QnAMakerBaseDialog.DefaultCardTitle,
CardNoMatchText = QnAMakerBaseDialog.DefaultCardNoMatchText,
CardNoMatchResponse = QnAMakerBaseDialog.DefaultCardNoMatchResponse
};
var dialogOptions = new Dictionary<string, object>
{
[QnAMakerBaseDialog.QnAOptions] = qnamakerOptions,
[QnAMakerBaseDialog.QnADialogResponseOptions] = qnaDialogResponseOptions
};
return await stepContext.BeginDialogAsync(nameof(QnAMakerBaseDialog), dialogOptions, cancellationToken);
}
}
}
The above is the part that selects the desired function at the same time as the greeting.
Try the code below which based on this official demo .Replace the content of Bots/StateManagementBot.cs with the code below :
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Schema;
using Microsoft.Bot.Builder.AI.QnA;
namespace Microsoft.BotBuilderSamples
{
public class StateManagementBot : ActivityHandler
{
private BotState _conversationState;
private BotState _userState;
private string KBID = "<KBID>";
private string ENDPOINT_KEY = "<KEY>";
private string HOST = "<QnA maker host>";
public StateManagementBot(ConversationState conversationState, UserState userState)
{
_conversationState = conversationState;
_userState = userState;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Save any state changes that might have occured during the turn.
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
var card = new HeroCard();
card.Title = "";
card.Text = #"Welcome to Welcome Users bot sample! This Introduction card";
card.Images = new List<CardImage>() { new CardImage("https://www.google.com/url?sa=i&source=images&cd=&ved=2ahUKEwjQjeeS4obmAhUIfnAKHQGgCB0QjRx6BAgBEAQ&url=https%3A%2F%2Fdougame.tistory.com%2F98&psig=AOvVaw11Y-BZJtsxh1pTp0Qxzedb&ust=1574819546545481") };
card.Buttons = new List<CardAction>()
{
//new CardAction(ActionTypes.OpenUrl, "FAQ", null, "Get an overview", "Get an overview", "https://learn.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0"),
new CardAction(ActionTypes.PostBack, "Info Dynamics365", null, "Ask a question", "Ask a question", "dy365"),
new CardAction(ActionTypes.PostBack, "FAQ",null , "Ask a question", "Ask a question", "FAQ" ),
new CardAction(ActionTypes.OpenUrl, "Connect", null, "Learn how to deploy", "Learn how to deploy", "https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0"),
};
var response = MessageFactory.Attachment(card.ToAttachment());
await turnContext.SendActivityAsync(response, cancellationToken);
}
}
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
var qnaMaker = new QnAMaker(new QnAMakerEndpoint
{
KnowledgeBaseId = KBID,
EndpointKey = ENDPOINT_KEY,
Host = HOST
},
null,
new System.Net.Http.HttpClient());
var conversationStateAccessors = _conversationState.CreateProperty<ConversationService>(nameof(ConversationService));
var conversationService = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationService());
var input = turnContext.Activity.Text;
if (String.IsNullOrEmpty(conversationService.currentService) && (input.Equals("FAQ") || input.Equals("dy365")))
{
conversationService.currentService = input;
await turnContext.SendActivityAsync(MessageFactory.Text("using "+ input + " service , pls enter your " + input + " question"), cancellationToken);
} else if (String.IsNullOrEmpty(conversationService.currentService)) {
await turnContext.SendActivityAsync(MessageFactory.Text("select a service from hero card first"), cancellationToken);
}
else if (conversationService.currentService.Equals("FAQ"))
{
var result = qnaMaker.GetAnswersAsync(turnContext).GetAwaiter().GetResult();
if (result.Length == 0)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry , I can't find any answer for it"), cancellationToken);
}
else {
await turnContext.SendActivityAsync(MessageFactory.Text(result[0].Answer), cancellationToken);
}
}
else if (conversationService.currentService.Equals("dy365"))
{
//call your dy 365 service here
await turnContext.SendActivityAsync(MessageFactory.Text("dy365 response"), cancellationToken);
}
else {
await turnContext.SendActivityAsync(MessageFactory.Text("error"), cancellationToken);
};
}
}
public class ConversationService{
public string currentService { get; set; }
}
}
You can find all QnA related params on QnA portal after you publish it :
Result :
In brief , let user select a service first and save the service type into conversation state so that users' requests will be redirected to corresponding service .
Hope it helps .
I don't know if i can explain this well but please bear with me. There are codes and images below.
So I am migrating my codes from the first version of Bot Framework V4 to the latest version.
I am trying to make a base for a bot that can call other dialog and cancel the current dialog anytime. and also answer questions with QnAMaker when there are no active dialogs.
There are no errors but the bot is not behaving as expected.
Expecting: When user first interracts with "Get Started" the main menu will be called because I added "Get started" to the intents of Main menu.
Actual Results: The main menu is being called twice.
Expecting: When i call DialogA from interruption via intent. The dialogA will be called and if there is any active dialog it will be cancelled.
Actual Results: Dialog A is called and current active dialog is ended BUT dialogA also ends abruptly.(Even when you did not answer its choice prompt yet).
Note: when i call dialogA via choice prompt in the main menu. the dialog A is starting normally and not ending.
Expecting: when there are no active dialog(Example if you cancel dialogs) user can ask a question and the bot check for answers in the QnaMaker. Actual results: the bot answer the question then start main menu. and even when there are active dialog the bot still answer the questions.
Here are the codes:
DialogBot:
namespace SabikoBotV2.Bots
{
public class DialogBot<T> : ActivityHandler
where T : Dialog
{
public readonly IStatePropertyAccessor<DialogState> _dialogAccessor;
protected readonly Dialog Dialog;
protected readonly BotState ConversationState;
protected readonly BotState UserState;
protected readonly ILogger Logger;
private readonly IBotServices BotServices;
private DialogSet Dialogs { get; set; }
public DialogBot(IBotServices botServices, ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
ConversationState = conversationState;
UserState = userState;
Dialog = dialog;
Logger = logger;
BotServices = botServices;
Dialogs = new DialogSet(conversationState.CreateProperty<DialogState>(nameof(DialogBot<T>)));
RegisterDialogs(Dialogs);
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
string text = string.IsNullOrEmpty(turnContext.Activity.Text) ? string.Empty : turnContext.Activity.Text.ToLower();
string topIntent = string.Empty;
RecognizerResult luisRecognizerResult = null;
string topDispatch = string.Empty;
RecognizerResult dispatchRecognizerResult = null;
if (!string.IsNullOrEmpty(text))
{
dispatchRecognizerResult = await BotServices.DispatchService.RecognizeAsync(turnContext, cancellationToken);
var topScoringDispatch = dispatchRecognizerResult?.GetTopScoringIntent();
topDispatch = topScoringDispatch.Value.intent;
luisRecognizerResult = await BotServices.LuisService.RecognizeAsync(turnContext, cancellationToken);
var topScoringIntent = luisRecognizerResult?.GetTopScoringIntent();
topIntent = topScoringIntent.Value.intent;
turnContext.TurnState.Add("topDispatch", topDispatch);
turnContext.TurnState.Add("dispatchRecognizerResult", dispatchRecognizerResult);
turnContext.TurnState.Add("botServices", BotServices);
turnContext.TurnState.Add("topIntent", topIntent);
}
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await DispatchToTopIntentAsync(turnContext, topDispatch, dispatchRecognizerResult, cancellationToken);
break;
case DialogTurnStatus.Waiting:
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
private void RegisterDialogs(DialogSet dialogs)
{
dialogs.Add(new MainDialog());
dialogs.Add(new DialogA());
dialogs.Add(new DialogB());
}
private async Task DispatchToTopIntentAsync(ITurnContext turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
switch (intent)
{
case QnAModel:
await DispatchToQnAMakerAsync(turnContext, cancellationToken);
break;
}
}
private async Task DispatchToQnAMakerAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
if (!string.IsNullOrEmpty(turnContext.Activity.Text))
{
var results = await BotServices.QnaService.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
}
}
}
startup
namespace SabikoBotV2
{
public class Startup
{
public Startup()
{
}
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IBotFrameworkHttpAdapter, AdapterWithErrorHandler>();
services.AddSingleton<IStorage, MemoryStorage>();
services.AddSingleton<UserState>();
services.AddSingleton<ConversationState>();
services.AddSingleton<IBotServices, BotServices>();
services.AddTransient<MainDialog>();
services.AddTransient<DialogA>();
services.AddTransient<DialogB>();
services.AddTransient<IBot, DialogBot<MainDialog>>();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseMvc();
}
}
}
CancelAndHelpDialog
namespace SabikoBotV2.DialogsV2
{
public class CancelAndHelpDialog : ComponentDialog
{
public CancelAndHelpDialog(string id)
: base(id)
{
}
protected override async Task<DialogTurnResult> OnBeginDialogAsync(DialogContext innerDc, object options, CancellationToken cancellationToken = default(CancellationToken))
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnBeginDialogAsync(innerDc, options, cancellationToken);
}
protected override async Task<DialogTurnResult> OnContinueDialogAsync(DialogContext innerDc, CancellationToken cancellationToken = default)
{
var result = await IsTurnInterruptedAsyncHelpAndCancel(innerDc, cancellationToken);
if (result != null)
{
return result;
}
return await base.OnContinueDialogAsync(innerDc, cancellationToken);
}
protected virtual async Task<DialogTurnResult> IsTurnInterruptedAsyncHelpAndCancel(DialogContext innerDc, CancellationToken cancellationToken)
{
var topIntent = innerDc.Context.TurnState.Get<string>("topIntent");
var text = innerDc.Context.TurnState.Get<string>("text");
if (topIntent.Equals("Cancel"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.Context.SendActivityAsync("👍 Ok. I've cancelled our last activity.");
}
else
{
await innerDc.Context.SendActivityAsync("I don't have anything to cancel.");
}
}
if (topIntent.Equals("Help"))
{
await innerDc.Context.SendActivityAsync("Let me help you");
if (innerDc.ActiveDialog != null)
{
await innerDc.RepromptDialogAsync();
}
}
if (topIntent.Equals("MainDialog"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
else
{
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
}
if (topIntent.Equals("DialogA"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogA));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogA));
}
}
if (topIntent.Equals("DialogB"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(DialogB));
}
else
{
await innerDc.BeginDialogAsync(nameof(DialogB));
}
}
return null;
}
}
}
MainDialog
namespace SabikoBotV2.Dialogs
{
public class MainDialog : CancelAndHelpDialog
{
private const string InitialId = nameof(MainDialog);
public MainDialog()
: base(nameof(MainDialog))
{
InitialDialogId = InitialId;
WaterfallStep[] waterfallSteps = new WaterfallStep[]
{
FirstStepAsync,
SecondStepAsync,
ThirdStepAsync,
};
AddDialog(new WaterfallDialog(InitialId, waterfallSteps));
AddDialog(new ChoicePrompt(nameof(ChoicePrompt)));
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new DialogA());
AddDialog(new DialogB());
}
private static async Task<DialogTurnResult> FirstStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("Start of Main");
return await stepContext.PromptAsync(
nameof(ChoicePrompt),
new PromptOptions
{
Prompt = MessageFactory.Text($"What do you want to do next?"),
Choices = new List<Choice>
{
new Choice
{
Value = "choice1",
},
new Choice
{
Value = "choice2",
},
},
RetryPrompt = MessageFactory.Text($"Please choose one of the options."),
});
}
private static async Task<DialogTurnResult> SecondStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
switch ((stepContext.Result as FoundChoice).Value.ToString().ToLower())
{
case "choice1":
return await stepContext.BeginDialogAsync(nameof(DialogA));
case "choice2":
return await stepContext.BeginDialogAsync(nameof(DialogB));
default:
return await stepContext.ReplaceDialogAsync(nameof(MainDialog));
}
}
private static async Task<DialogTurnResult> ThirdStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
await stepContext.Context.SendActivityAsync("End of Main");
return await stepContext.EndDialogAsync();
}
}
}
DialogA and DialogB are same with maindialog but they inherit ComponentDialog instead of CancelAndHelpDialog.
some screenshot for reference:
when dialogA is called via choice of main menu it starts normally
dialogA is called via intent interruption on it end abruptly.
qna maker anwering questions even if there is an active dialog
Hello I managed to fix my problem. For future reference of others here is how i fixed it.
I fixed the running of dialog via intent interruption by adding this line in the cancelandhelpdialog
return new DialogTurnResult(DialogTurnStatus.Waiting);
if (topIntent.Equals("MainDialog"))
{
if (innerDc.ActiveDialog != null)
{
await innerDc.CancelAllDialogsAsync();
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
else
{
await innerDc.BeginDialogAsync(nameof(MainDialog));
}
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
and the QnaMaker to only answer when there are no active dialog by doing this in the OnTurnAsync:
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
var topDispatch = turnContext.TurnState.Get<string>("topDispatch");
var dispatchRecognizerResult = turnContext.TurnState.Get<RecognizerResult>("dispatchRecognizerResult");
var dc = await Dialogs.CreateContextAsync(turnContext, cancellationToken);
var dialogResult = await dc.ContinueDialogAsync();
if (!dc.Context.Responded)
{
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await DispatchToTopIntentAsync(turnContext, topDispatch, dispatchRecognizerResult, cancellationToken);
break;
case DialogTurnStatus.Waiting:
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
// Save any state changes that might have occured during the turn.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
await UserState.SaveChangesAsync(turnContext, false, cancellationToken);
}
Hope this will help somebody.
I'm trying to migrate my botframework APP v3 to v4, I followed the instruction from microsoft documentation and everything works fine except state control, in my bot the state is shared for all user and for all channels (directline, telegram).
I saw this topic UserProfile state persistent between users in bot v4 but even forcing web random ID the state still share between users.
My bot has been created with Core bot template provided by Microsoft. You can see my C# code below. I'll skip to post Maindialog due to only business rules there in a waterfall dialog, but if needed I can post too.
Thanks for the help.
Startup.cs
public class Startup
{
private const string BotOpenIdMetadataKey = "BotOpenIdMetadata";
private static readonly AzureBlobStorage _UserStorage = new AzureBlobStorage("DefaultEndpointsProtocol=https;AccountName=evabotwebbe9c;AccountKey=wsU18jImJVSX+2vq6l0flx9Ou83hcDyrxie0tUN7fjxMV3bfHhYJuFobmq0h/TXU/pBBOvfpGVUlHtuqn7cNVw==;EndpointSuffix=core.windows.net", "botuserstate");
private static readonly AzureBlobStorage _ConversationStorage = new AzureBlobStorage("DefaultEndpointsProtocol=https;AccountName=evabotwebbe9c;AccountKey=wsU18jImJVSX+2vq6l0flx9Ou83hcDyrxie0tUN7fjxMV3bfHhYJuFobmq0h/TXU/pBBOvfpGVUlHtuqn7cNVw==;EndpointSuffix=core.windows.net", "botconversationstate");
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
if (!string.IsNullOrEmpty(Configuration[BotOpenIdMetadataKey]))
ChannelValidation.OpenIdMetadataUrl = Configuration[BotOpenIdMetadataKey];
// Create the credential provider to be used with the Bot Framework Adapter.
services.AddSingleton<ICredentialProvider, ConfigurationCredentialProvider>();
// Create the Bot Framework Adapter with error handling enabled.
services.AddSingleton<IBotFrameworkHttpAdapter, WebSocketEnabledHttpAdapter>();
// Create the storage we'll be using for User and Conversation state. (Memory is great for testing purposes.)
services.AddSingleton<IStorage, AzureBlobStorage>();
// Create the User state. (Used in this bot's Dialog implementation.)
var userState = new UserState(_UserStorage);
// Create the Conversation state. (Used by the Dialog system itself.)
var conversationState = new ConversationState(_ConversationStorage);
// Add the states as singletons
services.AddSingleton(userState);
services.AddSingleton(conversationState);
services.AddSingleton(sp =>
{
userState = sp.GetService<UserState>();
conversationState = sp.GetService<ConversationState>();
// Create the Conversation state. (Used by the Dialog system itself.)
return new BotStateSet(userState, conversationState);
});
// The Dialog that will be run by the bot.
services.AddSingleton<MainDialog>();
// Create the bot as a transient. In this case the ASP Controller is expecting an IBot.
services.AddTransient<IBot, DialogAndWelcomeBot<MainDialog>>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
app.UseDefaultFiles();
app.UseStaticFiles();
app.UseWebSockets();
//app.UseHttpsRedirection();
app.UseMvc();
}
}
DialogAndWelcomeBot.cs
public class DialogAndWelcomeBot<T> : DialogBot<T> where T : Dialog
{
protected IConfiguration Configuration;
private BotState _conversationState;
private BotState _userState;
protected IStatePropertyAccessor<DialogState> accessor;
protected Dialogo dialogo;
protected Dialog Dialog;
public DialogAndWelcomeBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger, IConfiguration configuration)
: base(conversationState, userState, dialog, logger)
{
Configuration = configuration;
dialogo = new Dialogo();
_conversationState = conversationState;
_userState = userState;
Dialog = dialog;
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var member in membersAdded)
{
// Greet anyone that was not the target (recipient) of this message.
// To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
if (member.Id != turnContext.Activity.Recipient.Id)
{
dialogo = await VerificarDialogo();
await IniciarDialogo(turnContext, cancellationToken);
}
}
}
private async Task IniciarDialogo(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
accessor = _conversationState.CreateProperty<DialogState>("DialogState");
var dialog = Dialog;
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
var conversationStateAccessors = _conversationState.CreateProperty<ConversationData>(nameof(ConversationData));
var conversationData = await conversationStateAccessors.GetAsync(turnContext, () => new ConversationData());
var userStateAccessors = _userState.CreateProperty<UserProfile>(nameof(UserProfile));
var userProfile = await userStateAccessors.GetAsync(turnContext, () => new UserProfile());
await dialogContext.BeginDialogAsync(dialog.Id, dialogo, cancellationToken);
}
}
DialogBot.cs
public class DialogBot<T> : ActivityHandler where T : Dialog
{
private readonly Dialog Dialog;
private BotState _conversationState;
private BotState _userState;
protected readonly ILogger Logger;
public DialogBot(ConversationState conversationState, UserState userState, T dialog, ILogger<DialogBot<T>> logger)
{
_conversationState = conversationState;
_userState = userState;
Dialog = dialog;
Logger = logger;
}
public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
await base.OnTurnAsync(turnContext, cancellationToken);
// Client notifying this bot took to long to respond (timed out)
if (turnContext.Activity.Code == EndOfConversationCodes.BotTimedOut)
{
await turnContext.SendActivityAsync(MessageFactory.Text("Desculpe-me, mas parece que a conexão com a internet está ruim e isto irá afetar o desempenho da nossa conversa."), cancellationToken);
return;
}
await _userState.SaveChangesAsync(turnContext, false, cancellationToken);
await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Message Activity log");
// Run the Dialog with the new message Activity.
await Dialog.Run(turnContext, _conversationState.CreateProperty<DialogState>(nameof(DialogState)), cancellationToken);
}
}
DialogExtensions.cs
public static class DialogExtensions
{
public static async Task Run(this Dialog dialog, ITurnContext turnContext, IStatePropertyAccessor<DialogState> accessor, CancellationToken cancellationToken = default(CancellationToken))
{
var dialogSet = new DialogSet(accessor);
dialogSet.Add(dialog);
var dialogContext = await dialogSet.CreateContextAsync(turnContext, cancellationToken);
var results = await dialogContext.ContinueDialogAsync(cancellationToken);
if (results.Status == DialogTurnStatus.Empty)
{
await dialogContext.BeginDialogAsync(dialog.Id, null, cancellationToken);
}
else
{
await dialogContext.ReplaceDialogAsync(dialog.Id, null, cancellationToken);
}
}
}
Jscript code into HTML file.
<script>
function guid() {
return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
s4() + '-' + s4() + s4() + s4();
}
function s4() {
return Math.floor((1 + Math.random()) * 0x10000)
.toString(16)
.substring(1);
}
var user = {
id: guid().toUpperCase(),
name: 'User-' + Math.floor((1 + Math.random()) * 10000)
};
var botConnection = new BotChat.DirectLine({
token: 'MyToken',
user: user,
webSocket: 'true'
});
const speechOptions = {
speechRecognizer: new BotChat.Speech.BrowserSpeechRecognizer(),
speechSynthesizer: new BotChat.Speech.BrowserSpeechSynthesizer()
};
BotChat.App({
user: user,
botConnection: botConnection,
speechOptions: speechOptions,
bot: { id: 'Bot'+ Math.floor((1 + Math.random()) * 10000)},
resize: 'detect',
}, document.getElementById("bot"));
botConnection
.postActivity({
from: user,
name: 'none',
type: 'event',
value: 'none'
})
.subscribe(function (id) {
console.log('"trigger setUserIdEvent" sent');
});
</script>
What is _UserStorage? ... the UserState class should be utilizing your IStorage implementation, which it will do automatically since you are already injecting an IStorage If you just inject the UserState, di will ensure it is created using the IStorage already present: services.AddSingleton<UserState>();
There is not enough here to determine what is going wrong.
Also, what is happening here?
if (member.Id != turnContext.Activity.Recipient.Id)
{
dialogo = await VerificarDialogo();
await IniciarDialogo(turnContext, cancellationToken);
}
I found the problem, it was logical and not in the code, the problem is my global variable, in net core they are "common" for all instances so all users from diferent instances got the same values in my global variables.
I have Luis and QnA running simultaneously , by using Dispatch cli.
I was trying to implement Adaptive Card which is also working fine, i
can add/view any type easily. The issue arises when i try to get
response on Adaptive card submit button click. My bot backtracks it to
the switch statement where when the intent is not being traced a
default case is being triggered. On commenting the default i am
getting this error
System.Collections.Generic.KeyNotFoundException: The given key
‘luisResult’ was not present in the dictionary.
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at Microsoft.BotBuilderSamples.DispatchBot.DispatchToTopIntentAsync(ITurnContext`1
turnContext, String intent, RecognizerResult recognizerResult,
CancellationToken cancellationToken) in
F:\nikhil\Alfi\mts-qna+luis\Bots\DispatchBot.cs:line 76
at Microsoft.BotBuilderSamples.DispatchBot.OnMessageActivityAsync(ITurnContext`1
turnContext, CancellationToken cancellationToken) in
F:\nikhil\Alfi\mts-qna+luis\Bots\DispatchBot.cs:line 45
at Microsoft.Bot.Builder.BotFrameworkAdapter.TenantIdWorkaroundForTeamsMiddleware.OnTurnAsync(ITurnContext
turnContext, NextDelegate next, CancellationToken cancellationToken)
in
d:\a\1\s\libraries\Microsoft.Bot.Builder\BotFrameworkAdapter.cs:line
964
at Microsoft.Bot.Builder.MiddlewareSet.ReceiveActivityWithStatusAsync(ITurnContext
turnContext, BotCallbackHandler callback, CancellationToken
cancellationToken) in
d:\a\1\s\libraries\Microsoft.Bot.Builder\MiddlewareSet.cs:line 55
at Microsoft.Bot.Builder.BotAdapter.RunPipelineAsync(ITurnContext turnContext, BotCallbackHandler callback, CancellationToken
cancellationToken) in
d:\a\1\s\libraries\Microsoft.Bot.Builder\BotAdapter.cs:line 167
I have tried this -
if (turnContext.Activity.Value != null)
{
var activity = turnContext.Activity;
activity.Text = JsonConvert.SerializeObject(activity.Value);
await turnContext.SendActivityAsync(MessageFactory.Text("activity.Text"), cancellationToken);
}
On debugging i can see the values like this -
activity.Value = {{ "startdate": "2017-10-12", "enddate": "2017-10-12" }}
namespace Microsoft.BotBuilderSamples
{
public class DispatchBot : ActivityHandler
{
private ILogger<DispatchBot> _logger;
private IBotServices _botServices;
public DispatchBot(IBotServices botServices, ILogger<DispatchBot> logger)
{
_logger = logger;
_botServices = botServices;
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
// First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
var recognizerResult = await _botServices.Dispatch.RecognizeAsync(turnContext, cancellationToken);
// Top intent tell us which cognitive service to use.
var topIntent = recognizerResult.GetTopScoringIntent();
// Next, we call the dispatcher with the top intent.
await DispatchToTopIntentAsync(turnContext, topIntent.intent, recognizerResult, cancellationToken);
}
protected override async Task OnMembersAddedAsync(IList<ChannelAccount> membersAdded, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
const string WelcomeText = "I am here to make your experience much more easier";
foreach (var member in membersAdded)
{
if (member.Id != turnContext.Activity.Recipient.Id)
{
await turnContext.SendActivityAsync(MessageFactory.Text($"Hi {member.Name}, I am at your service . {WelcomeText}"), cancellationToken);
}
}
}
private async Task DispatchToTopIntentAsync(ITurnContext<IMessageActivity> turnContext, string intent, RecognizerResult recognizerResult, CancellationToken cancellationToken)
{
var activity = turnContext.Activity;
activity.Text = JsonConvert.SerializeObject(activity.Value);
switch (intent)
{
case "l_mts-bot-809f":
activity.Text = JsonConvert.SerializeObject(activity.Value);
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
break;
case "q_mts-bot":
await ProcessSampleQnAAsync(turnContext, cancellationToken);
break;
default:
await ProcessHomeAutomationAsync(turnContext, recognizerResult.Properties["luisResult"] as LuisResult, cancellationToken);
Console.WriteLine(">>>>>>>>>>>"+recognizerResult.Properties["luisResult"]);
break;
// _logger.LogInformation($"Dispatch unrecognized intent: {intent}.");
// activity.Text = JsonConvert.SerializeObject(activity.Value);
// await turnContext.SendActivityAsync(MessageFactory.Text($"Dispatch unrecognized intent: {intent}."), cancellationToken);
// break;
}
}
private Activity CreateResponse(IActivity activity, Attachment attachment)
{
var response = ((Activity)activity).CreateReply();
response.Attachments = new List<Attachment>() { attachment };
return response;
}
private async Task ProcessHomeAutomationAsync(ITurnContext<IMessageActivity> turnContext, LuisResult luisResult, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessHomeAutomationAsync");
// Retrieve LUIS result for Process Automation.
var result = luisResult.ConnectedServiceResult;
var topIntent = result.TopScoringIntent.Intent;
var entity = result.Entities;
if (topIntent == "welcome")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Hi,This is Alfie"), cancellationToken);
}
if (topIntent == "None")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry I didnt get you!"), cancellationToken);
}
if (topIntent == "LeaveApplication")
{
await turnContext.SendActivityAsync(MessageFactory.Text("Do you want to apply leaves for yourself or for someone else?"), cancellationToken);
}
if (topIntent == "LeaveSelfApplication")
{
var DatesRange = LeavesDatesAdaptiveCardAttachment();
var response = CreateResponse(turnContext.Activity, DatesRange);
await turnContext.SendActivityAsync(response, cancellationToken);
if (turnContext.Activity.Value != null)
{
var activity = turnContext.Activity;
activity.Text = JsonConvert.SerializeObject(activity.Value);
await turnContext.SendActivityAsync(MessageFactory.Text("2019-07-30"), cancellationToken);
}
//await turnContext.SendActivityAsync(MessageFactory.Text("2019-07-30"), cancellationToken);
}
if (topIntent == "LeavesDateTenure")
{
string jsonData = JsonConvert.SerializeObject(result);
dynamic json = JsonConvert.DeserializeObject(jsonData);
await turnContext.SendActivityAsync(MessageFactory.Text("Please Provide me with your Leaves Tenure"), cancellationToken);
}
}
//string jsonData = JsonConvert.SerializeObject(result);
private async Task ProcessSampleQnAAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
_logger.LogInformation("ProcessSampleQnAAsync");
var results = await _botServices.SampleQnA.GetAnswersAsync(turnContext);
if (results.Any())
{
await turnContext.SendActivityAsync(MessageFactory.Text(results.First().Answer), cancellationToken);
}
else
{
await turnContext.SendActivityAsync(MessageFactory.Text("Sorry, could not find an answer in the Q and A system."), cancellationToken);
}
}
// Load attachment from file.
private Attachment CreateAdaptiveCardAttachment()
{
// combine path for cross platform support
string[] paths = { ".", "Cards", "AddingLeaveDetails.json" };
string fullPath = Path.Combine(paths);
var adaptiveCard = File.ReadAllText(fullPath);
return new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCard),
};
}
private Attachment LeavesDatesAdaptiveCardAttachment()
{
// combine path for cross platform support
string[] paths = { ".", "Cards", "LeavesDates.json" };
string fullPath = Path.Combine(paths);
var adaptiveCard = File.ReadAllText(fullPath);
return new Attachment()
{
ContentType = "application/vnd.microsoft.card.adaptive",
Content = JsonConvert.DeserializeObject(adaptiveCard),
};
}
}
}