Remember previous answers to guide conversation - botframework

I'm creating a chatbot using LUIS template and I have an intent called StartTest with utterances like these:
Begin [TestNameEntity] on [GearEntity] and [RPMEntity]
Begin [TestNameEntity] on [GearEntity]
Begin [TestNameEntity]
If the user input matches the first utterance, I have all entities I need. However, if its input matches the second utterance, I have to ask him the RPM. And for the third utterance I have to ask the gear and the RPM.
But for the second and third utterances I need to know what the user has said for the [TestNameEntity], like this:
User: Begin test 1 on second gear
Bot: What RPM?
User: 2500
How can I achieve this?

once your Intent method is called you can use this logic
result.Entities[0].Type to get entity name
so you can iterate result.Entities to get all the entities user has entered. If any of the three entities is missing you can ask for a prompt. This is a sample code of how to achieve it.
//global variables
public string CurrentGear { get; set; }
public string CurrentRpm { get; set; }
[LuisIntent("StartTest")]
public async Task StartTestIntent(IDialogContext context, LuisResult result)
{
if (result.Entities != null && result.Entities.Count >0)
{
//sample you will get selected entity type
//var ent1=result.Entities[0].Type;
var userValues=GetUserEntities(result);
if(userValues["GearEntity"]==null)
showGearPrompt(context);
if(userValues["RPMEntity"]==null)
showRpmPrompt(context);
}
}
private string[] GetUserEntities(LuisResult result)
{
//your logic here
//return list of entities;
}
private async Task showGearPrompt(IDialogContext context)
{
PromptDialog.Text(
context: context,
resume: OnGearOptionReceivedAsync,
prompt: "please enter Gear Value",
retry: "Sorry, I didn't understand that. Please try again."
);
}
public virtual async Task OnGearOptionReceivedAsync(IDialogContext context, IAwaitable<string> gear)
{
string response = await gear;
CurrentGear = response;
}
private async Task showRpmPrompt(IDialogContext context)
{
PromptDialog.Text(
context: context,
resume: OnRpmOptionReceivedAsync,
prompt: "please enter RPM Value",
retry: "Sorry, I didn't understand that. Please try again."
);
}
public virtual async Task OnRpmOptionReceivedAsync(IDialogContext context, IAwaitable<string> rpm)
{
string response = await rpm;
CurrentRpm = response;
}
Link to how to use prompts

Related

Comma removed in number-entity in LuisRecognizerResult

I'm using MS LUIS in my bot. In one intent I'd like to recognize numbers. When the user enters numbers containing a comma such as "1,25" the LuisRecognizer recognizes "1,25" as a number. I use the RecognizeAsync method like this:
var luisresult = _recognizer.RecognizeAsync<MyLuisModel>(context, cancellationToken);
MyLuisModel was generated using LUISgen:
public class M<LuisModel : IRecognizerConvert {
public enum Intent {
AnIntent,
....
}
[JsonProperty("intents")]
public Dictionary<Intent, IntentScore> Intents;
public class _Entities
{
// Built-in entities
public double[] number;
....
}
// More stuff
}
Now the problem is that when the number "1,25" was recognized, the entity number does not contain 1.25 as double value, but 125.
I'm from germany and we use "," as fraction delimiter.
According to this post on github I tried setting CultureInfo.CurrentCulture to german culture:
CultureInfo.CurrentCulture = CultureInfo.CreateSpecificCulture("de-DE");
to overwrite CurrentCulture which is used by double.TryParse() but without success.
How can I fix this?
I've found the answer. I think I still used the LUIS V2 endpoint instead of the newer V3. I've updated my Recognizer to now look like this:
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.AI.LuisV3;
namespace MyNamespace {
public class MyRecognizer : IRecognizer {
private LuisRecognizer _recognizer;
private RecognizerOptionsV3 _options;
public MyRecognizer(string appId, string apiKey, string host) {
_options = new LuisRecognizerOptionsV3(
new LuisApplication(appId, apiKey,host)
);
_recognizer = new LuisRecognizer(_options);
}
}
public virtual async Task<RecognizerResult> RecognizeAsync(ITurnContext turnContext, CancellationToken cancellationToken)
{
return await _recognizer.RecognizeAsync(turnContext, cancellationToken);
}
public virtual async Task<T> RecognizeAsync<T>(ITurnContext turnContext, CancellationToken cancellationToken) where T : IRecognizerConvert, new()
{
return await _recognizer.RecognizeAsync<T>(turnContext, cancellationToken);
}
}
Note the usage LuisRecognizerOptionsV3 in the constructor where I've used LuisRecognizerOptionsV2 before. Changing it to V3 was the crucial part and now "1,25" is correctly parsed into 1.25 double value.
CultureInfo.CurrentCulture now is set to "de-DE". When using V2 it was set to InvariantCulture. Maybe this was kind of a bug in V2.

How to have multiple FAQs(KBs) with same questions but different answers?

I am unable to keep the context(FAQ) of the chatbot conversation.
I have successfully integrated LUIS+QnAmaker based on this documentation.
https://learn.microsoft.com/en-us/azure/cognitive-services/qnamaker/tutorials/integrate-qnamaker-luis
I have about 3 KBs that contain the same questions but different answers. The chatbot should be able to filter to the desired FAQ and the following answers should be from the chosen FAQ from the user. Currently, it only returns the first KB for the answers unless I phrase my question this way:
Can I have the answer for FAQ1? or
Can I have the answer for FAQ2?
Hope I can have some help from the community here. Thank you!
When you are using multiple KBs with same questions, you can segregate the answers by adding different metadata to your question/answer sets. A metadata is just a collection of name / value pairs. When using the QnAMakerDialog, you have the ability to set either filtering or boosting metadata, which will be passed as part of any queries to the QnA Maker Service.
You can create your QnA Maker dialog and add a single name / value metadata pair to the MetadataFilter property on the dialog. This will then cause the dialog to only receive answers marked with the same name / value metadata.
MessagesController:
public class MessagesController : ApiController
{
internal static IDialog<object> MakeRoot()
{
var qnaDialog = new Dialogs.MyQnADialog
{
MetadataFilter = new List<Metadata>()
{
new Metadata()
{
Name = "knowledgeBase",
Value = "kb1"
}
}
};
return qnaDialog;
}
/// <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, MakeRoot);
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
}
Similarly, if you want to use the other knowledge base, you can pass the other metadata name/value pairs.e.g, { Name="knowledgeBase",Value="kb2" }
QnAMAkerDialog:
[QnAMakerService("https://xxxx.azurewebsites.net/qnamaker/", "{EndpointKey_here}", "{KnowledgeBaseId_here}",1)]
public class MyQnADialog : QnAMakerDialog<object>
{
public override async Task NoMatchHandler(IDialogContext context, string originalQueryText)
{
await context.PostAsync($"Sorry, I couldn't find an answer for '{originalQueryText}'.");
context.Done(false);
}
public override async Task DefaultMatchHandler(IDialogContext context, string originalQueryText, QnAMakerResult result)
{
if (result.Answers.FirstOrDefault().Score > 80)
{
await context.PostAsync($"I found {result.Answers.Length} answer(s) that might help...{result.Answers.First().Answer}.");
}
else
{
await context.PostAsync($"Sorry, I couldn't find an answer for '{originalQueryText}'.");
}
context.Done(true);
}
}
Example :
Metadata tagging
When you test it, the result will be as follows:
With knowledgeBase:kb1
With knowledgeBase:kb2
Hope this helps!!!

Set formflow dialog field value without getting prompted later

I'm using SetDefine() to provide a value for one of the fields on my bot dialog..
return builder
.Field(new FieldReflector<CarValuationDialog>(nameof(UserName))
.SetDefine(async (state, field) =>
{
field.SetValue(state, userName);
return await Task.FromResult(true);
}))
userName is just a variable in the function that's calling the return builder line. The property UserName is defined as..
public string UserName { get; set; }
The issue I have is that, when I run the bot in the emulator, the first thing I see if this..
How can I configure the property UserName so that it does not get prompted for a value in the bot?
Since you are defining your username field and you don't want the bot the prompt for that field you can use .SetActive
.Field(new FieldReflector<CarValuationDialog>(nameof(UserName))
.SetDefine(async (state, field) =>
{
field.SetValue(state, "username");
return await Task.FromResult(true);
})
.SetActive((state) => String.IsNullOrEmpty(state.UserName)))
So a prompt will be only initiated if the field is Null or empty. You can try out other functions which return back a bool to match your usecase better.
Create a new class that holds all of the items you want in the form. Then create a static method in that class that returns an IForm<class>.
[Serializable]
public class CallNotesForm
{
[Prompt("What is the subject of the call?")]
public string Subject { get; set; }
[Prompt("What are the call details?")]
public string Notes { get; set; }
public static IForm<CallNotesForm> BuildForm()
{
return new FormBuilder<CallNotesForm>()
.Message("Please enter some details about your call. Enter 'Help' for more information")
.Build();
}
}
Then in your method that calls the form,
IForm<CallNotesForm> formVM = new CallNotesForm() { Direction = CallNotes.CallDirection.Outgoing };
IFormDialog<CallNotesForm> form = new FormDialog<CallNotesForm>(formVM, CallNotesForm.BuildForm, FormOptions.PromptInStart);
context.Call(form, CallDetailsAsync);

Use Bing Spell Check before Microsoft Translate which is called before call to LUIS

So...
I'm trying to use the Bot Framwork with LUIS in Swedish.
Using the samples I implemented translation of the input from Swedish to English and then called the LUIS functionality.
It worked perfect until we got some very strange intent hits from LUIS.
What we found out was that a very small spelling error (in Swedish) caused the translation to create a message that triggered wrong intent.
We can solve the problem by checking the score of the received intent, but the message back to the user "I didn't understand that" isn't especially helpful.
Running the same message through Bing Spell Check and replace the faulty text with the correct one will produce a correct behaviour (mostly).
What I would like to do is to use the result from the Spell Check to ask the user if the text he/she should be replace with the result from Bing.
Now the problem: I can't find a proper way to implement the dialog to the user. (If possible, I would like to avoid using the PromptDialog.Confirm since it is tricky to localize)
What I have now (that doesn't work) is roughly:
if (activity.Type == ActivityTypes.Message)
{
correctedText = await sc.BingSpellCheck(activity.Text, spellValues);
if (spellValues.Count > 0)
{
// Ask the client if the correction is ok
await Conversation.SendAsync(activity, () => new CorrectSpellingDialog());
}
Translate.Current.ToEnglish(activity.Text, "en");
await Conversation.SendAsync(activity, () => new MeBotLuisDialog());
}
What I would like here is to create a CorrectSpellingDialog() that just returns true or false, nad if it is true I will call the ...MeBotLuisDialog().
Sorry for all the text but it's a long problem :-)
Any takers on this?
(The other solution I had was to create an Intent "SpellCheckError" that is trigged from the Bing Spell Check and the in the intent send a message with the corrected message back to the bot (even though I don't know I that is doable in a proper way))
// Tommy
To enable Bing Spell Check API in your bot, you will first get the key from Bing service, then in the Web.config file add the BingSpellCheckApiKey and together enable IsSpellCorrectionEnabled for example:
<appSettings>
<!-- update these with your BotId, Microsoft App Id and your Microsoft App Password-->
<add key="BotId" value="YourBotId" />
<add key="MicrosoftAppId" value="" />
<add key="MicrosoftAppPassword" value="" />
<add key="BingSpellCheckApiKey" value="YourBingSpellApiKey" />
<add key="IsSpellCorrectionEnabled" value="true" />
</appSettings>
Then you can create a component for your bot to use Bing Spell Api, for more information, you can refer to Quickstart for Bing Spell Check API with C#. Here you can a service in your app for example like this Service.
Then in MessagesController, check the spell first before sending message to dialog. and since your want to show a confirm dialog to user if the text should be replace with the result from Bing, you can send a FormFlow first to let user to have a choice. For example:
Code of MessagesController:
private static readonly bool IsSpellCorrectionEnabled = bool.Parse(WebConfigurationManager.AppSettings["IsSpellCorrectionEnabled"]);
private BingSpellCheckService spellService = new BingSpellCheckService();
/// <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)
{
if (IsSpellCorrectionEnabled)
{
try
{
var text = await this.spellService.GetCorrectedTextAsync(activity.Text);
if(text != activity.Text)
{
//if spelling is wrong, go to rootdialog
await Conversation.SendAsync(activity, () => new Dialogs.RootDialog(activity.Text, text));
}
else
{
//if right, direct go to luisdialog
await Conversation.SendAsync(activity, () => new Dialogs.MyLuisDialog());
}
}
catch(Exception ex)
{
Trace.TraceError(ex.ToString());
}
}
else
{
await Conversation.SendAsync(activity, () => new Dialogs.MyLuisDialog());
}
}
else
{
HandleSystemMessage(activity);
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
Code of my RootDialog:
[Serializable]
public class RootDialog : IDialog<object>
{
private string otext;
private string ntext;
public RootDialog(string oldtext, string newtext)
{
otext = oldtext;
ntext = newtext;
}
public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}
public async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> result)
{
var message = await result;
var form = new FormDialog<Confirmation>(new Confirmation(otext, ntext),
Confirmation.BuildForm, FormOptions.PromptInStart, null);
context.Call(form, this.GetResultAsync);
}
private async Task GetResultAsync(IDialogContext context, IAwaitable<Confirmation> result)
{
var state = await result;
await context.Forward(new MyLuisDialog(), null, context.Activity, System.Threading.CancellationToken.None);
}
}
[Serializable]
public class Confirmation
{
public string Text { get; set; }
private static string otext;
private static string ntext;
public Confirmation(string oldtext, string newtext)
{
otext = oldtext;
ntext = newtext;
}
public static IForm<Confirmation> BuildForm()
{
return new FormBuilder<Confirmation>()
.Field(new FieldReflector<Confirmation>(nameof(Text))
.SetType(null)
.SetDefine(async(state, field) =>
{
field
.AddDescription(otext, otext)
.AddTerms(otext, otext)
.AddDescription(ntext,ntext)
.AddTerms(ntext, ntext);
return true;
}))
.Build();
}
}

Can intercept a message in FormFlow before it reaches recognizers?

We have a bot that will be collecting information and we would like to utilize FormFlow. Using the custom prompter we can customize the outgoing messages, but is there any similar facility to let us intercept incoming messages before they hit the recognizers? The specific use case is based on the user input, we may want to immediately exist out of a flow and redirect to a different dialog.
You can use business logic to process user input when using FormFlow, where you can immediately exit out of a form flow and redirect to a different dialog.
Since the validate function cannot pass context, you can store the context in a form variable that is populated when constructed.
public MyFormClass(IDialogContext context)
{
Context = context;
}
public IDialogContext Context { get; set; }
public int IntegerField { get; set; }
Later, call the validate function for a specific field. Here, you can use the stored context to start a new dialog.
return new FormBuilder<MyFormClass>()
.Field(nameof(IntegerField),
validate: async (state, value) =>
{
var result = new ValidateResult { IsValid = true };
if (state.IntegerField > 10)
{
await state.Context.Call(new Dialog(), Dialog.ResumeMethod);
return result;
}
else
{
return result;
}
}
)
.Build();
Note:
Even though the first return statement will never be reached, it is required to avoid throwing an error.
You may need to implement additional steps to properly manage the bot's dialog stack.
Even though the first return statement will never be reached, it is required

Resources