Integrating LUIS with FormFlow - c#

I have created a bot, having a FormFlow in it. Now if you type I want to launch a product, LUIS will tell which dialog it has to go to :
internal static IDialog<AssesmentHelper> CreateProduct()
{
return Chain.From(() => FormDialog.FromForm(AssesmentHelper.BuildForm))
.Do(async (context, profileForm) =>
{
try
{
var completed = await profileForm;
}
catch (FormCanceledException<AssesmentHelper> e)
{
string reply;
if (e.InnerException == null)
{
reply = $"You quit on {e.Last}--maybe you can finish next time!";
}
else
{
reply = Responses.ShortCircuit;
}
await context.PostAsync(reply);
}
});
}
public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{
stLuis = await LuisHelper.ParseUserInput(activity.Text);
switch (stLuis.topScoringIntent.intent)
{
case "CreateProduct":
await Conversation.SendAsync(activity, CreateProduct);
break;
case "EditProduct":
await Conversation.SendAsync(activity, EditProduct);
break;
case "None":
break;
default:
break;
}
}
Now, once it enters dialog, it asks user to select numbers:
Please select numbers:
Azure
Windows
Now if I reply 1,2. Luis returns it as None intent, so my message does not go to the corresponding dialog. It always go to None case.
The code for dialog is:
public static IForm<AssesmentHelper> BuildForm()
{
return new FormBuilder<AssesmentHelper>()
.Message(Responses.NumberSelection)
.Field(nameof(Program))
.Field(nameof(Product))
.Build();
}
Enum for program and product:
[Serializable]
public enum Program
{
None = 0,
A = 1,
};
[Serializable]
public enum Product
{
None = 0,
Azure = 1,
Windows = 2
};
As soon as I enter this dialog, It asks me to select number for selecting program. Now if i select 1,2. Luis returns it as None intent. So Case "None", is executed.
What I want is, redirect the control to same dialog. I have similar dialog for Edit Product as well. That's why I cannot train my luis app to understand numbers as Product Intent. Otherwise whenever i select number for Edit Product, it will go to different case.
Earlier it was identifying correct intents somehow, but today i republished my luis app and it stopped identifying.

Related

How not to lose dialog flow with chatbot v4?

I have created a chatbot v4 with Microsoft Bot Framework and it was working fine. We have two environments QA and PROD. As time passed the bot has more functions and Dialogs.
We have discovered that in PROD (that has the same code as QA) it's not working fine, sometimes it exits from the current Dialog and returns to the init.
I have already tried to connect QnA QA base to PROD to see if it is a DB problem, but it hasn't solved the problem.
this is part of my code
in Bot.cs
in OnTurnAsync
if (activity.Type == ActivityTypes.Message)
{
// Continue the current dialog
var dialogResult = await dc.ContinueDialogAsync();
// examine results from active dialog
switch (dialogResult.Status)
{
case DialogTurnStatus.Empty:
await NewConversationFlow(turnContext, dc, conversationId, cancellationToken);
break;
case DialogTurnStatus.Waiting:
// The active Dialog is waiting for a response from the user, so do nothing.
break;
case DialogTurnStatus.Complete:
await dc.EndDialogAsync();
// do things
await NewConversationFlow(turnContext, dc, conversationId, cancellationToken);
break;
default:
await dc.CancelAllDialogsAsync();
break;
}
}
in Bot.cs
in NewConversationFlow
var response = await _services.QnAServices["QnA"].GetAnswersAsync(turnContext, new QnAMakerOptions() { Top = 5, ScoreThreshold = 0.1F });
QueryResult qnaAnswer = GetQnaAnswer(response, 0.60);
await _flowService.ShortDelayWithTypingActionAsync(turnContext);
await turnContext.SendActivityAsync(response.Answer, cancellationToken: cancellationToken);
var flowValue = response.Metadata?.Where(metadata => metadata.Name == "flow").Select(metadata => metadata.Value).FirstOrDefault();
if (!string.IsNullOrEmpty(flowValue))
{
switch (flowValue)
{
case ONE:
...
default:
await dc.BeginDialogAsync(nameof(OneAnswerDialog));
break;
case TWO:
...
await dc.BeginDialogAsync(nameof(TwoAnswerDialog));
break;
case SEARCH:
await dc.BeginDialogAsync(nameof(SearchDialog));
break;
}
}
in OneAnswerDialog
// Dialog IDs
profileDialog = nameof(OneAnswerDialog);
// Add control flow dialogs
var firstCaseWaterfallSteps = new WaterfallStep[]
{
GetAnswerAsync,
SearchStepAsync,
};
AddDialog(new WaterfallDialog(profileDialog, firstCaseWaterfallSteps));
AddDialog(new TextPrompt(ResponsePrompt, ValidateResponseAsync));
Usually, it fails after that the user insert an answer after the first question (the one that is printed in NewConversationFlow). I don't know if it fails in GetAnswerAsync or if in ValidateResponseAsync because I can't debug the code in PRD.
in GetAnswerAsync
return await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
in ValidateResponseAsync
PositiveResponse = false;
var value = promptContext?.Recognized?.Value?.Trim() ?? string.Empty;
if (value != string.Empty)
{
promptContext.Recognized.Value = value;
bool userSayYes = false;
var response = await _services.QnAServices["QnA"].GetAnswersAsync(promptContext.Context, new QnAMakerOptions() { Top = 5, ScoreThreshold = 0.1F });
if (response != null && response.Length > 0)
{
var responses = response.Where(resp => resp.Metadata?.Any(metadata => metadata.Name == "metadata") ?? false).Select(x => x.Metadata);
PositiveResponse = responses.Any(metadatas => metadatas.Any(metadata => metadata.Value == "more" || metadata.Value == "no"));
if (!PositiveResponse && responses.Any(metadatas => metadatas.Any(metadata => metadata.Value == "yes")))
{
userSayYes = true;
await _flowService.DelayWithTypingActionAsync(promptContext.Context);
await promptContext.Context.SendActivityAsync("Can I help you with anything else?", cancellationToken: cancellationToken);
}
}
if (response == null || response.Length <= 0 || (!PositiveResponse && !userSayYes))
{
await _flowService.AddOrUpdateQuestion(promptContext.Context.Activity.Conversation.Id, value);
}
else if (userSayYes)
{
await _flowService.RemoveFlowTypeAndQuestion(promptContext.Context.Activity.Conversation.Id);
}
}
return true;
(if PositiveResponse is false, in the successive pass the Dialog end and returns in Bot.cs)
For some reason in PROD the bot is "confused" and exit from the Dialog in GetAnswerAsync or ValidateResponseAsync and recall the NewConversationFlow.
I have read in another post that the delay with the dot typing can cause this problem, I have removed them but the problem persists...
What could be the problem?
What can cause a premature exit from a Dialog?
Thanks in advance.
-------------------------- EDIT --------------------------
I've tunnelled PRD with ngrok and debug it with VS, in this case, the bot it's working...
What can be? There is any Azure configuration that can cause it? I have already checked the billing plan and it isn't the free one.
-------------------------- EDIT 2 --------------------------
I have updated GetAnswerAsync to be sure to that the bot is entering in the Dialog:
protected async Task<DialogTurnResult> GetAnswerAsync(
WaterfallStepContext stepContext,
CancellationToken cancellationToken)
{
await stepContext.Context.SendActivityAsync("What do you think? Does it answer your question?");
await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
return new DialogTurnResult(DialogTurnStatus.Waiting);
}
It seems to exit from the dialog at this point, when waiting the user prompt...
("Oh, sorry to hear that!" Is chit chat, it's not the next step of the WaterFall)
Sometimes, the bot works. It seems to be something "random".
-------------------------- EDIT 3 --------------------------
I still have this problem... I have tried to remove all the settings from Azure to force the app to read it from the file. I have used the same settings in PRD and QA but nothing... Any idea?
In your "GetAnswerAsync" file, try replacing your code with the below snippet. I usually work in Node and occasionally I've experienced the dialog will "reset" if I only return the awaited "send" / "prompt" activity. If I explicitly send the DialogTurnStatus, then the flow moves as expected (i.e. on to the following step).
await stepContext.PromptAsync(ResponsePrompt, new PromptOptions());
return new DialogTurnStatus(DialogTurnStatus.waiting);
If not that, could the QnA response scores be too close to the .60 threshold in GetQnaAnswer()? Since results are non-deterministic (meaning the score can fluctuate from instance to instance), perhaps the response isn't always meeting the threshold?
Hope of help!

Different response from bot emulator and chatbot in web

I have created a chatbot in web channel and direct line.
When i tested in bot emulator i get the right response and when i try to test the same intent in localhost ( webchat) i got different response.
I will show you and example :
call an agent
give me your customer number
( after custemer number was sended ) are you sure ?
if you click Yes ...the data are stored in database ( sql server )
If you do the save in localhost you get : You cancelled the form ( in fact i havent cancell any form
Here is the luisdialog here i call the form :
[LuisIntent("human")]
public async Task human(IDialogContext context, LuisResult result)
{
var form = new FormDialog<Human>(
new Human(),
Human.BuildForm,
FormOptions.PromptInStart,
result.Entities);
context.Call<Human>(form, LossFormCompleted)
}
private async Task LossFormCompleted(IDialogContext context,
IAwaitable<Human> result)
{
HumanCall form = null;
try
{
form = await result;
}
catch (OperationCanceledException)
{
}
if (form == null)
{
await context.PostAsync("You cancelled the form.");
}
else
{
//call the LossForm service to complete the form fill
var message = $"Your data are stored in database";
await context.PostAsync(message);
}
context.Wait(this.MessageReceived);
}
The form model is :
[Serializable]
public class Human
{
[Prompt("What is your contract number?")]
public string contract;
public static IForm<Human> BuildForm()
{
OnCompletionAsyncDelegate<HumanCall> wrapUpRequest = async (context, state) =>
{
using (BotModelDataContext BotDb = new BotModelDataContext())
{
tblBot bot = new tblBot();
bot = BotDb.tblBots.SingleOrDefault(q => q.Reference == state.contract);
if (bot != null)
{
using (bbbserviceSoapClient cws = new bbbserviceSoapClient())
{
viewc a= new viewc();
a.Lastname = bot.Lastname;
}
}
}
};
return new FormBuilder<Human>().Message
("can you send us some info ?")
.Field(nameof(contract))
.OnCompletion(wrapUpRequest)
.Confirm("Are you sure: Yes or No. ")
.Build();
}
}
}
Can someone help me where i'm wrong ? What can i do to retrieve the same response ? It's about timeout problem or what do you thing ?
I do a test based on the code that you provided and make slight modifications, and I find that if some exceptions occur in wrapUpRequest method, it would show "You cancelled the form" instead of the message "Your data are stored in database".
So I suspect that exceptions occurring in wrapUpRequest method (perhaps database query issue or request sent by bbbserviceSoapClient is timeout etc) when you do test via web chat, which causes the issue.
To troubleshoot the issue, you can try to implement/write custom log to detect if any exception occurs within wrapUpRequest method when you test via web chat.

Skip displaying form fields based on user confirmation

I have some 10 properties in a class and based on those properties I have a form that asks for user input. I want to know if there's any mechanism that after initial 4-5 questions I ask user if he/she wants to ans more and if reply is yes then next set of fields/questions are asked.
I tried doing it with SetDefine but issue with SetDefine is that its called with each field so it asks the user to confirm with each fiels but I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
public async Task StartAsync(IDialogContext context)
{
await context.PostAsync($"Welcome to the Order helper!");
var orderFormDialog = FormDialog.FromForm(BuildOrderAdvanceStepSearchForm, FormOptions.PromptInStart);
context.Call(orderFormDialog, ResumeAfterOrdersFormDialog);
}
private IForm<OrderSearchQuery> BuildOrderAdvanceStepSearchForm()
{
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(OrderSearchQuery.ItemNumber))
.Field(nameof(OrderSearchQuery.Draft))
.Field(nameof(OrderSearchQuery.Dispatched))
.Field(nameof(OrderSearchQuery.InTransit))
.Field(nameof(OrderSearchQuery.Delivered))
//.Confirm("Do you want to search with more options?.")
//.Field(nameof(OrderSearchQuery.AddField1))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField1))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField2))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField3))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField4))
.SetDefine(OrderAdvanceStepConfirmation))
.Field(new FieldReflector<OrderSearchQuery>(nameof(OrderSearchQuery.AddField5))
.SetDefine(OrderAdvanceStepConfirmation))
.Build();
}
private static async Task<bool> OrderAdvanceStepConfirmation(OrderSearchQuery state, Field<OrderSearchQuery> field)
{
field.SetPrompt(new PromptAttribute($"Do you want to search with more options?."));
return true;
}
private async Task ResumeAfterordersFormDialog(IDialogContext context, IAwaitable<OrderSearchQuery> result)
{
try
{
var searchQuery = await result;
await context.PostAsync($"Ok. Searching for orders...");
var count = 100;
if (count > 1)
{
await context.PostAsync($"I found total of 100 orders");
await context.PostAsync($"To get order details, you will need to provide more info...");
}
else
{
await context.PostAsync($"I found the order you were looking for...");
await context.PostAsync($"Now I can provide you information related to Consumer Package, Multi-Pack, Shelf Tray & Unit Load for this order.");
}
}
catch (FormCanceledException ex)
{
string reply;
if (ex.InnerException == null)
{
reply = "You have canceled the operation. Quitting from the order Search";
}
else
{
reply = $"Oops! Something went wrong :( Technical Details: {ex.InnerException.Message}";
}
await context.PostAsync(reply);
}
finally
{
//context.Done<object>(null);
}
}
I want it to only confirm with 1st optional field and based on the answer either skip all or get all.
You can use SetNext of FieldReflector.
For example create a enum for confirmation for example like this:
public enum Confirmation
{
Yes,
No
}
public Confirmation? Corfirm { get; set; }
And then you can build the Form like this:
return new FormBuilder<OrderSearchQuery>()
.Field(nameof(ItemNumber))
.Field(nameof(Draft))
.Field(new FieldReflector<OrderSearchQuery>(nameof(Corfirm))
.SetNext((value, state) =>
{
var selection = (Confirmation)value;
if (selection == Confirmation.Yes)
{
//go to step 1
return new NextStep();
}
else
{
//skip the following steps
state.Stpe1 = null;
state.Step2 = null;
return new NextStep(StepDirection.Complete);
}
})
)
.Field(nameof(Stpe1))
.Field(nameof(Step2)).Build();

Differentiate whether IDialog was called using context.Call() or context.Forward() in Microsoft Bot Framework

I have a subdialog in a bot built using MS bot framework that starts as follows - the standard way:
public async Task StartAsync(IDialogContext context)
{
var msg = "Let's find your flights! Tell me the flight number, city or airline.";
var reply = context.MakeMessage();
reply.Text = msg;
//add quick replies here
await context.PostAsync(reply);
context.Wait(UserInputReceived);
}
This dialog is called using two different ways, depending on whether in the previous screen the user tapped a button that says "Flights" or immediately entered a flight number. Here is the code from the parent dialog:
else if (response.Text == MainOptions[2]) //user tapped a button
{
context.Call(new FlightsDialog(), ChildDialogComplete);
}
else //user entered some text instead of tapping a button
{
await context.Forward(new FlightsDialog(), ChildDialogComplete,
activity, CancellationToken.None);
}
Question: how can I know (from within the FlightsDialog) whether that dialog was called using context.Call() or context.Forward()? This is because in the case of context.Forward(), StartAsync() shouldn't output the prompt asking the user to enter the flight number - they already did this.
The best idea I have is to save a flag in the ConversationData or user data, as below, and access it from the IDialog, but I thought there could be a better way?
public static void SetUserDataProperty(Activity activity, string PropertyName, string ValueToSet)
{
StateClient client = activity.GetStateClient();
BotData userData = client.BotState.GetUserData(activity.ChannelId, activity.From.Id);
userData.SetProperty<string>(PropertyName, ValueToSet);
client.BotState.SetUserDataAsync(activity.ChannelId, activity.From.Id, userData);
}
Unfortunately Forward actually calls Call (and then does some other stuff afterwards), so your Dialog wouldn't be able to differentiate.
void IDialogStack.Call<R>(IDialog<R> child, ResumeAfter<R> resume)
{
var callRest = ToRest(child.StartAsync);
if (resume != null)
{
var doneRest = ToRest(resume);
this.wait = this.fiber.Call<DialogTask, object, R>(callRest, null, doneRest);
}
else
{
this.wait = this.fiber.Call<DialogTask, object>(callRest, null);
}
}
async Task IDialogStack.Forward<R, T>(IDialog<R> child, ResumeAfter<R> resume, T item, CancellationToken token)
{
IDialogStack stack = this;
stack.Call(child, resume);
await stack.PollAsync(token);
IPostToBot postToBot = this;
await postToBot.PostAsync(item, token);
}
From https://github.com/Microsoft/BotBuilder/blob/10893730134135dd4af4250277de8e1b980f81c9/CSharp/Library/Dialogs/DialogTask.cs#L196

Can I get a phone number by user id via Telegram Bot API?

I am using Telegram Bot API for sending instant messages to users.
I have installed nuget package. This package is recommend by telegram developers.
I have created a telegram bot and successfully got access to it by using code. When I send messsage to bot, bot gets some info about sender.
I need the phone numbers of users to identify them in our system and send the information back to them.
My question is Can i get a user phone number by telegramUserId?
I'm doing it for user convenience. If I could to get a user phone number I should't have to ask for it from the user.
Now my command like this:
debt 9811201243
I want
debt
It's possible with bots 2.0 check out bot api docs.
https://core.telegram.org/bots/2-0-intro#locations-and-numbers
https://core.telegram.org/bots/api#keyboardbutton
No, unfortunately Telegram Bot API doesn't return phone number. You should either use Telegram API methods instead or ask it explicitly from the user. You cannot get "friends" of a user as well.
You will definitely retrieve the following information:
userid
first_name
content (whatever it is: text, photo, etc.)
date (unixtime)
chat_id
If user configured it, you will also get last_name and username.
With Telegram Bot API, you can get the phone number only when you request it from the user, but the user does not have to write the number, all he must do is to press a button in the conversation and the number will be sent to you.
When user clicks on /myNumber
The user has to confirm:
You will get his number
This. is the console output:
Take a look at this Simple console application, but you need to do some changes to handle the number:
In Handler.ch add the following lines to BotOnMessageReceived
if (message.Type == MessageType.Contact && message.Contact != null)
{
Console.WriteLine($"Phone number: {message.Contact.PhoneNumber}");
}
This is the piece of code needed in case the repository is deleted someday:
Program.cs
public static class Program
{
private static TelegramBotClient? bot;
public static async Task Main()
{
bot = new TelegramBotClient(/*TODO: BotToken hier*/);
User me = await bot.GetMeAsync();
Console.Title = me.Username ?? "My awesome bot";
using var cts = new CancellationTokenSource();
ReceiverOptions receiverOptions = new() { AllowedUpdates = { } };
bot.StartReceiving(Handlers.HandleUpdateAsync,
Handlers.HandleErrorAsync,
receiverOptions,
cts.Token);
Console.WriteLine($"Start listening for #{me.Username}");
Console.ReadLine();
cts.Cancel();
}
}
Handlers.cs
internal class Handlers
{
public static Task HandleErrorAsync(ITelegramBotClient botClient, Exception exception, CancellationToken cancellationToken)
{
var errorMessage = exception switch
{
ApiRequestException apiRequestException => $"Telegram API Error:\n[{apiRequestException.ErrorCode}]\n{apiRequestException.Message}",
_ => exception.ToString()
};
Console.WriteLine(errorMessage);
return Task.CompletedTask;
}
public static async Task HandleUpdateAsync(ITelegramBotClient botClient, Update update, CancellationToken cancellationToken)
{
var handler = update.Type switch
{
UpdateType.Message => BotOnMessageReceived(botClient, update.Message!),
_ => UnknownUpdateHandlerAsync(botClient, update)
};
try
{
await handler;
}
catch (Exception exception)
{
await HandleErrorAsync(botClient, exception, cancellationToken);
}
}
private static async Task BotOnMessageReceived(ITelegramBotClient botClient, Message message)
{
Console.WriteLine($"Receive message type: {message.Type}");
if (message.Type == MessageType.Contact && message.Contact != null)
{
// TODO: save the number...
Console.WriteLine($"Phone number: {message.Contact.PhoneNumber}");
}
if (message.Type != MessageType.Text)
return;
var action = message.Text!.Split(' ')[0] switch
{
"/myNumber" => RequestContactAndLocation(botClient, message),
_ => Usage(botClient, message)
};
Message sentMessage = await action;
Console.WriteLine($"The message was sent with id: {sentMessage.MessageId}");
static async Task<Message> RequestContactAndLocation(ITelegramBotClient botClient, Message message)
{
ReplyKeyboardMarkup requestReplyKeyboard = new(
new[]
{
// KeyboardButton.WithRequestLocation("Location"), // this for the location if you need it
KeyboardButton.WithRequestContact("Send my phone Number"),
});
return await botClient.SendTextMessageAsync(chatId: message.Chat.Id,
text: "Could you please send your phone number?",
replyMarkup: requestReplyKeyboard);
}
static async Task<Message> Usage(ITelegramBotClient botClient, Message message)
{
const string usage = "/myNumber - to send your phone number";
return await botClient.SendTextMessageAsync(chatId: message.Chat.Id,
text: usage,
replyMarkup: new ReplyKeyboardRemove());
}
}
private static Task UnknownUpdateHandlerAsync(ITelegramBotClient botClient, Update update)
{
Console.WriteLine($"Unknown update type: {update.Type}");
return Task.CompletedTask;
}
}

Categories

Resources