MS Bot: Can't add a new custom Q & A - botframework

Trying out MS Bot QnA Maker.
I was able to load an FAQ from a site. After running the sandbox bot couldn't find a way to add an answer to a new question. Where can this be done?

As a start point you can use this simple sample provided by Microsoft.
There is a file called MessageController where all the messages are handled.
To make a simple Q&A just modify this function of that file:
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);
}
To something like this:
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
// check if activity is of type message
if (activity != null && activity.GetActivityType() == ActivityTypes.Message)
{
if (activity.Text.ToLower() == "what is your name?")
{
var reply = activity.CreateReply("Superbot!");
await Conversation.SendAsync(activity, () => new EchoDialog());
}
else
{
HandleSystemMessage(activity);
}
return new HttpResponseMessage(System.Net.HttpStatusCode.Accepted);
}
}
And you'll get a bot which answers "Superbot!" to question "What is your name?".

Related

I get invalid need: expected Wait, have None in my Bot code

I get "invalid need: expected Wait, have None" exception in RootDialog, MessageReceivedAsync method's context.Forward line. Why could this be happening? what correction should I make? Please find my code below.
I use -C#, Visual Studio 2015, Microsoft Bot Framework, Bot emulator. This is for Web Chat.
MessageController
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
else
if (activity.Type == ActivityTypes.ConversationUpdate)
{
var greeting = activity.CreateReply("Hi! I'm Cmon. Enter the Id of the Employee you want to know about.");
await connector.Conversations.ReplyToActivityAsync(greeting);
}
else
{
this.HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
RootDialog -
public async Task StartAsync(IDialogContext context)
{
context.Wait(this.MessageReceivedAsync);
}
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
try
{
if (message.Text != null)
{
await context.Forward(new DirectoryDialog(), null, message,CancellationToken.None);
}
}
catch (Exception ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
DirectoryDialog -
public async Task StartAsync(IDialogContext context)
{
var message = context.Activity as IMessageActivity;
var query = DirectoryQuery.Parse(message.Text);
await context.PostAsync($"Searching for {query.SearchKey}...");
try
{
await SearchEmployee(context, query);
}
catch (FormCanceledException ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
private async Task SearchEmployee(IDialogContext context, DirectoryQuery searchQuery)
{
DirectoryDal dal = new DirectoryDal();
DataSet ds = dal.GetEmployeeSearch(searchQuery.SearchKey);
if (ds.Tables.Count > 0 & ds.Tables[0].Rows.Count > 0)
{
StringBuilder employeeSearchMessageBuilder = new StringBuilder(100);
//do something with data table data
var employeeSearchReply = context.MakeMessage();
employeeSearchReply.Type = ActivityTypes.Message;
employeeSearchReply.TextFormat = TextFormatTypes.Markdown;
employeeSearchReply.Text = employeeSearchMessageBuilder.ToString();
await context.PostAsync(employeeSearchReply);
}
}
By passing null here:
await context.Forward(new DirectoryDialog(), null, message,CancellationToken.None);
you are breaking the dialog chain. You have to define your Resume method.
The Troubleshooting section of the documentation has a good section about that, here:
Ensure all dialog methods end with a plan to handle the next message.
All IDialog methods should complete with IDialogStack.Call,
IDialogStack.Wait, or IDialogStack.Done. These IDialogStack methods
are exposed through the IDialogContext that is passed to every IDialog
method. Calling IDialogStack.Forward and using the system prompts
through the PromptDialog static methods will call one of these methods
in their implementation.
I was able to resolve the issue by making the following modifications to my DirectoryDialog.
DirectoryDialog code -
public async Task StartAsync(IDialogContext context)
{
var message = context.Activity as IMessageActivity;
var query = DirectoryQuery.Parse(message.Text);
await context.PostAsync($"Searching for {query.SearchKey}...");
try
{
//await SearchEmployee(context, query);
context.Wait(MessageReceivedAsync);
}
catch (FormCanceledException ex)
{
await context.PostAsync($"Oops! Something went wrong :-(");
}
}
RootDialog code -
public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
try
{
if (message.Text != null)
{
await context.Forward(new DirectoryDialog(), ResumeAfterOptionDialog, message,CancellationToken.None);
}
}
catch (Exception ex)
{
await context.PostAsync($"Oops! Something went wrong");
}
finally{
context.Done(true);
}
}
private async Task ResumeAfterOptionDialog(IDialogContext context, IAwaitable<object> result)
{
context.Wait(MessageReceivedAsync);
}
Thank you Tanmoy and Nicolas for helping! :)
context.wait(MessageReceivedAsync);
at the end of MessageReceivedAsync(IDialogContext context, IAwaitable result) method of Root Dialog.
and
context.done(true);
at the end of StartAsync method of DirectoryDialog or within both try and catch block

Continous conversation based on last message in Microsoft Bot framework

I want continuous conversation chat in Microsoft Bot framework.
if(user says hello)
{
reply = what u want to listen hi or hello
-----if(user says hi)
-----{
--------reply= hi
-----}
----if(user says hello)
-- {
-------reply= hello
---}
}
means bot should also ask question and answer it accordingly..
Assuming this is C# and that you are using an IDialog<T> based dialog, you could do the following:
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
if (message.Text.Equals("hello", StringComparison.InvariantCultureIgnoreCase))
{
PromptDialog.Text(context, this.ResumeAfterPrompt, "What u want to listen hi or hello");
}
else
{
// do something
context.Wait(this.MessageReceivedAsync);
}
}
private async Task ResumeAfterPrompt(IDialogContext context, IAwaitable<string> result)
{
try
{
var userMessage = await result;
switch (userMessage.ToLowerInvariant())
{
case "hi":
await context.PostAsync("hi");
break;
case "hello":
await context.PostAsync("hello");
break;
default:
// do something;
break;
}
}
catch (TooManyAttemptsException)
{
// do something with the exception
}
context.Wait(this.MessageReceivedAsync);
}

Nested dialogs: message being sent twice

I have a bot where I am doing something like this:
1) A new Activity (message) arrives.
2) I dispatch the message to a RootDialog.
3) Depending on some Logic, RootDialog can:
a) Call a LuisDialog (handling natural language)
b) Call a CustomDialog (handles some business logic).
But when the user state is reset, and the flow leads to an intention inside the LuisDialog, it calls the intention method twice. Just the first time the state is empty, then it works fine.
Let me show you the code:
MessagesController:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
if (activity.Type == ActivityTypes.Message)
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
try
{
await Conversation.SendAsync(activity, () => new RootDialog());
}
catch (HttpRequestException e)
{
...
}
}
RootDialog:
public class RootDialog : IDialog<object>
{
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> awaitable)
{
bool value = DoSomeCustomLogic();
if (value)
{
string message = DoSomething();
await context.PostAsync(message);
} else {
bool value2 = DoSomeCustomLogic2();
if (value2)
{
var answerValidationDialog = new ValidateAnswerWithUserDialog();
context.Call(answerValidationDialog, ValidateAnswerWithUserDialogCompletedCallback);
} else {
var luisDialog = new LuisDialog();
await context.Forward(luisDialog,LuisDialogCompletedCallback, context.Activity, CancellationToken.None);
}
}
}
Callbacks only do context.Done(true);
And LuisDialog has an Intention which goes like this:
[LuisIntent(LuisUtils.INTENT_MENU_SALUTE)]
public async Task SaluteOrMenu(IDialogContext context, LuisResult result)
{
if (LuisUtils.IntentScoreIsHighEnough(result))
{
string userName = context.Activity.From.Name;
ContextHelper helper = new ContextHelper(MessageReceived);
await helper.AskUserToDoSomeInitialAction(context, saluteWord, userName);
context.Done(true);
}
else
{
await None(context, result);
}
}
And finally class ContextHelper:
public class ContextHelper
{
private Func<IDialogContext, IAwaitable<IMessageActivity>, Task> MessageReceived;
public ContextHelper(Func<IDialogContext, IAwaitable<IMessageActivity>, Task> messageReceived)
{
MessageReceived = messageReceived;
}
public async Task AskUserToDoSomeInitialAction(IDialogContext context, string saluteWord, string userName)
{
var reply = context.MakeMessage();
List<CardAction> buttons = BuildInitialOptionActions();
List<CardImage> images = BuildInitialOptionImages();
string initialText = $"Hi stranger!"
var card = new HeroCard
{
Title = "Hello!"
Text = initialText,
Buttons = buttons,
Images = images
};
reply.Attachments = new List<Attachment> { card.ToAttachment() };
await context.PostAsync(reply);
context.Wait(AfterUserChoseOptionInSalute);
}
private async Task AfterUserChoseOptionInSalute(IDialogContext context, IAwaitable<IMessageActivity> result)
{
await ReDispatchMessageReceivedToDialog(context);
}
private async Task ReDispatchMessageReceivedToDialog(IDialogContext context)
{
await MessageReceived(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
}
The SaluteOrMenu Intention gets called twice (only the first time I interact with the bot or when I delete the state. After Debugging I saw that after doing context.Wait(AfterUserChoseOptionInSalute);, the bot calls that function (instead of waiting for an event to call it)
Any ideas?
Thanks in advance.
I found the line that was wrong. It was on the first dialog (the RootDialog):
public async Task StartAsync(IDialogContext context)
{
await MessageReceivedAsync(context, Awaitable.FromItem(context.Activity.AsMessageActivity()));
}
That is a line that re dispatches a message with the incoming activity. I had it somewhere in some chunk of code and (don't know why), thought it was a good idea to use it on StartAsync. So, because of that, two calls were occurring.
Dumb me.
I just changed it to this and worked:
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}

couldn't send retry but the message has been sent

In the bot web chat, when I type a message, the bot says "sending" first and then changes to "couldn't send, Retry". But the message is sent and I am getting the reply. How can I avoid this? Do I need to increase the message timeout? If so, where I need to set it?
This is the code snippet. I am using C# SDK where I have coded in MessageReceivedASync method
namespace Bot_Application1.Dialogs
{
public class HRBotDialog : IDialog<object>
{
public static string dialogcontext = "";
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
// Get the text passed
var message = await argument;
string typedText = message.Text.ToLower();
string answer = "My Answer";
if ((typedText == "hi") || (typedText == "hello"))
{
answer = message.Text;
}
else if ((typedText == "how many personal days do i have left") || (typedText.Contains("personal days")))
{
answer = "Looks like you have 2 remaining for this year";
}
I am adding the controller code here
//[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)
{
await Conversation.SendAsync(activity, () => new HRBotDialog());
}
else
{
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
var reply = HandleSystemMessage(activity);
if (reply != null)
await connector.Conversations.ReplyToActivityAsync(reply);
}
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)
{
string replyMessage = string.Empty;
if (message.MembersAdded.Any(o => o.Id == message.Recipient.Id))
{
replyMessage += $"How can I help you? \n";
return message.CreateReply(replyMessage);
}
}
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;
}
}
Please try adding the Serializable attribute to your HRBotDialog like this:
[Serializable]
public class HRBotDialog : IDialog<object>
{
//...code...
}

show welcome message in Microsoft teams using Microsoft bot framework

I have used below code for showing a welcome message to user.
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)
{
string replyMessage = string.Empty;
replyMessage = Responses.Greeting;
return message.CreateReply(replyMessage);
}
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;
}
Below method is used to call HandleSystemMessage, if activity type is not message.
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
string reply = "";
ConnectorClient connector = new ConnectorClient(new Uri(activity.ServiceUrl));
if (activity.Type == ActivityTypes.Message)
{
stLuis = await LuisHelper.ParseUserInput(activity.Text);
string userResponse = activity.Text.ToLower();
switch (stLuis.topScoringIntent.intent)
{
case "Greetings":
reply = Responses.Greeting;
break;
case "None":
reply = Responses.None;
break;
default:
break;
}
}
if (reply != "")
await connector.Conversations.ReplyToActivityAsync(activity.CreateReply(reply));
}
else
{
var reply1 = HandleSystemMessage(activity);
if (reply1 != null)
await connector.Conversations.ReplyToActivityAsync(reply1);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
This code works with Skype. But when I add same bot in Microsoft teams, it doesn't show a welcome message.
By now (2016-12-30) MSFT Teams does not send any message at all when you add a bot to "contact list". This is a known limitation and to be addressed in the nearest future, as MSFT guys say.
In the meantime to get a ConversationUpdate message to the bot, the user will have to first initiate a conversation with the bot.
As a workaround you can handle special text sent from user, like "start", or just the very first incoming message, if your bot is stateful enough.

Resources