Form Flow bot customization issue - c#

I want to build a bot which can make use of QnA api and google drive's search api. I will ask user if he wants to query Knowledge base or he wants to search a file in drive. For this, I chose Form Flow bot template of Bot Framework. In this case, if user chooses to query qna api then I want to post the question to QNA api. How can I implement this in my bot? Where can I find user's selection in flow.
Here is MessageController.cs
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, MakeRootDialog);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
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
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
internal static IDialog<UserIntent> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(UserIntent.BuildForm));
}
Form Builder
public static IForm<UserIntent> BuildForm()
{
return new FormBuilder<UserIntent>()
.Message("Welcome to the bot!")
.OnCompletion(async (context, profileForm) =>
{
await context.PostAsync("Thank you");
}).Build();
}

FormFlow is more for a guided conversation flow. It doesn't seem to me like it meets your requirements. You can just use a PromptDialog to get the user's answer for which type of search they prefer, then forward the next message to the corresponding dialog. Something like:
[Serializable]
public class RootDialog : IDialog<object>
{
const string QnAMakerOption = "QnA Maker";
const string GoogleDriveOption = "Google Drive";
const string QueryTypeDataKey = "QueryType";
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
if(context.UserData.ContainsKey(QueryTypeDataKey))
{
var userChoice = context.UserData.GetValue<string>(QueryTypeDataKey);
if(userChoice == QnAMakerOption)
await context.Forward(new QnAMakerDialog(), ResumeAfterQnaMakerSearch, activity);
else
await context.Forward(new GoogleDialog(), ResumeAfterGoogleSearch, activity);
}
else
{
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: new[] { QnAMakerOption, GoogleDriveOption },
prompt: "Hi. How would you like to perform the search?",
retry: "That is not an option. Please try again.",
promptStyle: PromptStyle.Auto
);
}
}
private Task ResumeAfterGoogleSearch(IDialogContext context, IAwaitable<object> result)
{
//do something after the google search dialog finishes
return Task.CompletedTask;
}
private Task ResumeAfterQnaMakerSearch(IDialogContext context, IAwaitable<object> result)
{
//do something after the qnamaker dialog finishes
return Task.CompletedTask;
}
private async Task ChoiceReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var userChoice = await result;
context.UserData.SetValue(QueryTypeDataKey, userChoice);
await context.PostAsync($"Okay, your preferred search is {userChoice}. What would you like to search for?");
}
}

First you need to create your LUIS application over the LUIS portal, add your intents over there with the utterances, for eg:- intent is "KnowlegeBase", inside that you can create as many utterances which will return this intent.
Inside the application, create a LUISDialog class and call it from your BuildForm:-
await context.Forward(new MyLuisDialog(), ResumeDialog, activity, CancellationToken.None);
LuisDialog class:-
public class MyLuisDialog : LuisDialog<object>
{
private static ILuisService GetLuisService()
{
var modelId = //Luis modelID;
var subscriptionKey = //Luis subscription key
var staging = //whether point to staging or production LUIS
var luisModel = new LuisModelAttribute(modelId, subscriptionKey) { Staging = staging };
return new LuisService(luisModel);
}
public MyLuisDialog() : base(GetLuisService())
{
}
[LuisIntent("KnowledgeBase")]
public async Task KnowledgeAPICall(IDialogContext context, LuisResult result)
{
//your code
}
To utilize the message passed by user, you can use it from the context in the parameter.

Related

Once the message forwarded from LUIS to QnA depending on the intent, not returning to LUIS in c# from the second instance. What to do?

I was trying to connect LUIS and QnA, but from the message controller on the first instance it is going to luis and if required accordingly going to QnA, but once in QnA next messages are not being sent to LUIS only executed by QnA. Someone could Help?
messageControler.cs
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
//await Conversation.SendAsync(activity, () => new BasicLuisDialog());
// check if activity is of type message
if (activity.GetActivityType() == ActivityTypes.Message)
{
//await Conversation.SendAsync(activity, () => new BasicQnAMakerDialog());
await Conversation.SendAsync(activity, () => new BasicLuisDialog());
}
else
{
//await Conversation.SendAsync(activity, () => new BasicQnAMakerDialog());
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
private Activity HandleSystemMessage(Activity message)
{
if (message.Type == ActivityTypes.DeleteUserData)
{
// Implement user deletion here
// If we handle user deletion, return a real message
}
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
}
else if (message.Type == ActivityTypes.ContactRelationUpdate)
{
// Handle add/remove from contact lists
// Activity.From + Activity.Action represent what happened
}
else if (message.Type == ActivityTypes.Typing)
{
// Handle knowing tha the user is typing
}
else if (message.Type == ActivityTypes.Ping)
{
}
return null;
}
}
}
BasicLuisDialog.cs
This is the code for basic luisdialog, from here if the intent matches then it is supposed to provide the required reply else if none it will redirected the search to the basic qna. this is performing only for the first instance. from second instance onwards if it is in qna it does not starting from luis.
public class BasicLuisDialog : LuisDialog<object>
{
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var mForward = await message as Activity;
var username = context.Activity.From.Name;
string reply = $"Hello {username}! Your query we are taking forward, as we are not aware about what exactly you want to know.";
//await context.PostAsync(reply);
await context.Forward(new IDialog(), this.ResumeAfterQnA, mForward, CancellationToken.None);
}
private async Task ResumeAfterQnA(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceived);
}
[LuisIntent("leave.apply")]
public async Task ApplyLeave(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var username = context.Activity.From.Name;
string reply = $"Hello {username}! we are processing it";
await context.PostAsync(reply);
}
[LuisIntent("it")]
public async Task IT(IDialogContext context, IAwaitable<IMessageActivity> message, LuisResult result)
{
var username = context.Activity.From.Name;
string reply = $"Hello {username}! we would look into your IT problems shortly";
await context.PostAsync(reply);
}
}
BasicQnAMakerDialog
Basic QnA code is given below. please help me to find where exactly is the problem.
public class IDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
/* Wait until the first message is received from the conversation and call MessageReceviedAsync
* to process that message. */
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
/* When MessageReceivedAsync is called, it's passed an IAwaitable<IMessageActivity>. To get the message,
* await the result. */
var message = await result;
var activity = await result as Activity;
var qnaAuthKey = ConfigurationManager.AppSettings["QnAAuthKey"];
var qnaKBId = ConfigurationManager.AppSettings["QnAKnowledgebaseId"];
var endpointHostName = ConfigurationManager.AppSettings["QnAEndpointHostName"];
// QnA Subscription Key and KnowledgeBase Id null verification
if (!string.IsNullOrEmpty(qnaAuthKey) && !string.IsNullOrEmpty(qnaKBId))
{
// Forward to the appropriate Dialog based on whether the endpoint hostname is present
if (string.IsNullOrEmpty(endpointHostName)) {
await context.Forward(new BasicQnAMakerPreviewDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
else
{
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
}
else
{
await context.PostAsync("Please set QnAKnowledgebaseId, QnAAuthKey and QnAEndpointHostName (if applicable) in App Settings. Learn how to get them at https://aka.ms/qnaabssetup.");
}
//var activity = await result as Activity;
//await context.Forward(new BasicLuisDialog(), ResumeAfterLuisDialog, activity, CancellationToken.None);
}
You need to call context.Done(new MyDialogResult()) when the dialog has finished doing what it was supposed to do. The bot framework keeps a stack of dialogs per conversation and whenever you perform a context.Forward it pushes a new dialog to the stack and every message to the bot will always go the dialog who is at the top of the stack and skip the others below, so when you perform context.Done it pops the current dialog from the stack and the conversation returns to the previous dialog.

How can I create process after selecting "None of the Above" option on a list of dialog by a user?

I am trying to create a process after selecting "None of the above" by a user.
As you know, QnA Maker replies related multiple questions list when the user input a fuzzy message. At the bottom of the list, we can see "None of the above".
I would like to create a process after the user selects "None of the above" box. I have already tried to override BasicQnAMakerDialog(), but cannot do it.
If someone knows about how to do it, please tell me.
I would like to create a process after the user selects "None of the above" box.
You can try to override DefaultWaitNextMessageAsync method and detect if user select "None of the above" option, the following code snippet is for your reference.
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
if (message.Text.Equals("None of the above."))
{
// your code logic
await context.PostAsync("You selected 'None of the above.'");
}
await base.DefaultWaitNextMessageAsync(context, message, result);
}
Test result:
Update:
"What kind of thing do you want to ask?" -> user inputs "blah blah blah"
You can sent "What kind of thing do you want to ask?" to user inside DefaultWaitNextMessageAsync method, and wait for user inputs.
Complete sample code:
namespace Microsoft.Bot.Sample.QnABot
{
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
/* Wait until the first message is received from the conversation and call MessageReceviedAsync
* to process that message. */
context.Wait(this.MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
/* When MessageReceivedAsync is called, it's passed an IAwaitable<IMessageActivity>. To get the message,
* await the result. */
var message = await result;
context.ConversationData.SetValue<bool>("isnotdefaultmes", false);
var qnaAuthKey = GetSetting("QnAAuthKey");
//var qnaKBId = Utils.GetAppSetting("QnAKnowledgebaseId");
var qnaKBId = ConfigurationManager.AppSettings["QnAKnowledgebaseId"];
var endpointHostName = ConfigurationManager.AppSettings["QnAEndpointHostName"];
// QnA Subscription Key and KnowledgeBase Id null verification
if (!string.IsNullOrEmpty(qnaAuthKey) && !string.IsNullOrEmpty(qnaKBId))
{
// Forward to the appropriate Dialog based on whether the endpoint hostname is present
if (string.IsNullOrEmpty(endpointHostName))
await context.Forward(new BasicQnAMakerPreviewDialog(), AfterAnswerAsync, message, CancellationToken.None);
else
await context.Forward(new BasicQnAMakerDialog(), AfterAnswerAsync, message, CancellationToken.None);
}
else
{
await context.PostAsync("Please set QnAKnowledgebaseId, QnAAuthKey and QnAEndpointHostName (if applicable) in App Settings. Learn how to get them at https://aka.ms/qnaabssetup.");
}
}
private async Task AfterAnswerAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
// wait for the next user message
context.Wait(MessageReceivedAsync);
}
public static string GetSetting(string key)
{
//var value = Utils.GetAppSetting(key);
var value = ConfigurationManager.AppSettings[key];
if (String.IsNullOrEmpty(value) && key == "QnAAuthKey")
{
//value = Utils.GetAppSetting("QnASubscriptionKey"); // QnASubscriptionKey for backward compatibility with QnAMaker (Preview)
value = ConfigurationManager.AppSettings["QnASubscriptionKey"];
}
return value;
}
}
// Dialog for QnAMaker Preview service
[Serializable]
public class BasicQnAMakerPreviewDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: subscriptionKey, knowledgebaseId,
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerPreviewDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), ConfigurationManager.AppSettings["QnAKnowledgebaseId"], "No good match in FAQ.", 0.5)))
{ }
}
// Dialog for QnAMaker GA service
[Serializable]
public class BasicQnAMakerDialog : QnAMakerDialog
{
// Go to https://qnamaker.ai and feed data, train & publish your QnA Knowledgebase.
// Parameters to QnAMakerService are:
// Required: qnaAuthKey, knowledgebaseId, endpointHostName
// Optional: defaultMessage, scoreThreshold[Range 0.0 – 1.0]
public BasicQnAMakerDialog() : base(new QnAMakerService(new QnAMakerAttribute(RootDialog.GetSetting("QnAAuthKey"), ConfigurationManager.AppSettings["QnAKnowledgebaseId"], "No good match in FAQ.", 0.5, 5, ConfigurationManager.AppSettings["QnAEndpointHostName"])))
{ }
protected override async Task DefaultWaitNextMessageAsync(IDialogContext context, IMessageActivity message, QnAMakerResults result)
{
if (message.Text.Equals("None of the above."))
{
// your code logic
await context.PostAsync("What kind of thing do you want to ask?");
//await context.PostAsync("You selected 'None of the above.'");
}
await base.DefaultWaitNextMessageAsync(context, message, result);
}
}
}
Test result:

MS Bot greets twice after added in webchat

I have the following scenario: A bot is provided via webchat in a webpage. When the webpage is loaded, the bot greets and immediately displays some options a user can pick from. Here is the code, used in MessagesController:
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity.Type == ActivityTypes.Message || activity.Type == ActivityTypes.ConversationUpdate)
{
if (activity.Type == ActivityTypes.ConversationUpdate &&
!activity.MembersAdded.Any(r => r.Name == "Bot"))
return Request.CreateResponse(System.Net.HttpStatusCode.OK);
await Conversation.SendAsync(activity, () =>
new RootDialog());
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(System.Net.HttpStatusCode.OK);
return response;
}
and part from the RootDialog class:
[Serializable]
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
var message = context.MakeMessage();
var attachment = GetHeroCard();
message.Attachments.Add(attachment);
await context.PostAsync(message);
context.Wait(this.ShowFirstCategoryRefinement);
}
public virtual async Task ShowFirstCategoryRefinement(IDialogContext context, IAwaitable<IMessageActivity> activity)
{
var message = await activity;
IEnumerable<string> options = this.mainCategories;
PromptDialog.Choice(
context: context,
resume: ChoiceReceivedAsync,
options: options,
prompt: "Please select product category:",
retry: "Selected category not available. Please try again.",
promptStyle: PromptStyle.Auto
);
}
...
}
What actually happens is: user opens the page, the bot greets and shows some options. When the user picks an option, the bot greets again and shows the same options. If the user again picks an option the dialog continues normally.
How shall this be fixed? Thanks.

BOT Framework - Unable to Connect Luis- AI Dialog

Below are the source Code for communicating to LUIS- AI from my Bot application. When I try to communicate I am always getting Access Denied response. I don't know what I am missing here.
[LuisModel("8c9285fb-198a-4f49-8fe4-b08ac5541ac2", "5c47c63887e346c2aee24d1755e07d29")]
[Serializable]
public class LUISDialog:LuisDialog<RoomReservation>
{
private readonly BuildFormDelegate<RoomReservation> Reservation;
public LUISDialog(BuildFormDelegate<RoomReservation> reservceRoom)
{
this.Reservation = reservceRoom;
}
[LuisIntent("")]
[LuisIntent("None")]
public async Task None(IDialogContext dialogContext, LuisResult luisResult)
{
await dialogContext.PostAsync("I am sorry I don't know what you mean ");
dialogContext.Wait(MessageReceived);
}
[LuisIntent("Greeting")]
public async Task Greeting(IDialogContext dialogContext, LuisResult luisResult)
{
dialogContext.Call(new GreetingDialog(), CallBack);
}
private async Task CallBack(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceived);
}
[LuisIntent("Reservation")]
public async Task RoomReservation(IDialogContext dialogContext, LuisResult luisResult)
{
FormDialog<RoomReservation> enrollmentForm =new FormDialog<RoomReservation>(new RoomReservation(),this.Reservation, FormOptions.PromptInStart);
dialogContext.Call(enrollmentForm, CallBack);
}
[LuisIntent("QueryAmenities")]
public async Task QueryAmenities(IDialogContext dialogContext, LuisResult luisResult)
{
foreach (var entity in luisResult.Entities.Where(entity=>entity.Type=="Amenity"))
{
var value = entity.Entity.ToLower();
if (value == "pool" || value == "gym" || value == "wifi" || value == "towels")
{
await dialogContext.PostAsync("Yes we have that");
dialogContext.Wait(MessageReceived);
return;
}
await dialogContext.PostAsync("I'am sorry we don't have that");
dialogContext.Wait(MessageReceived);
return;
}
await dialogContext.PostAsync("I'am sorry we don't have that");
dialogContext.Wait(MessageReceived);
}
}
Screen shot of error I am getting
Controller Code
[BotAuthentication]
public class MessagesController : ApiController
{
/// <summary>
/// POST: api/Messages
/// Receive a message from a user and reply to it
/// </summary>
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
//ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
//// calculate something for us to return
//int length = (activity.Text ?? string.Empty).Length;
//// return our reply to the user
//Activity reply = activity.CreateReply($"You sent {activity.Text} which was {length} characters");
//await connector.Conversations.ReplyToActivityAsync(reply);
// await Conversation.SendAsync(activity, () => HotelBotDialog.dialog);
await Conversation.SendAsync(activity, MakeLuisDialog);
}
else
{
await HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
LUIS Intent
Please help me to resolve this
I just tested a connection to your LUIS and it seems to be working: https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/8c9285fb-198a-4f49-8fe4-b08ac5541ac2?subscription-key=5c47c63887e346c2aee24d1755e07d29&verbose=true&q=hello
Probrably you are using the endpoint key instead the Programmatic Key API. Check this:
enter image description here

Is it possible to grab the user's message from the IDialogContext parameter?

In one of the examples on the Microsoft Bot Framework page, they have the following code:
[Serializable]
public class EchoDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
var message = await argument;
await context.PostAsync("You said: " + message.Text);
context.Wait(MessageReceivedAsync);
}
}
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new EchoDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
Is it possible to grab the user's message that is passed from Post to the StartAsync method via the context parameter? Is it also possible to store things in it? The documentation is going way over my head and I'd just like to know if this thing is modifiable at all.
You can add a constructor to EchoDialog that stores whatever data you like e.g. the Activity instance.

Categories

Resources