Comma removed in number-entity in LuisRecognizerResult - azure-language-understanding

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.

Related

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!!!

Remember previous answers to guide conversation

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

Xamrin.Forms(Android) MessagingCenter Not Working

The created MessagingCenter is not working,
The value generated by the Guid variable is not moved to the other page.
my Code in CustomWebviewRenderer.cs
Guid guid = Guid.NewGuid();
MessagingCenter.Send<CustomWebViewRenderer, Guid>(this, "Hi", guid);
MainActivity.cs
Guid guid;
MessagingCenter.Subscribe<CustomWebViewRenderer, Guid>(this, "Hi",(arg1, arg2) =>
{
guid = arg2;
});
For me this is also buggy as hell so i just made a messager class in shared code that works well, and call it from Android.
Android code example:
Messager.Notify.TabBarClicked((int)tab.Position);
Shared code example:
public class Messager
{
public readonly static Messager Notify = new Messager();
//-------------------------------------------------------------------
public void TabBarClicked(int tag)
//-------------------------------------------------------------------
{
MessagingCenter.Send(this, "TabBarClicked", tag.ToString());
}
// something more...
}
Adapt to your case..
Another buggy point is that i have much fails with messages if they have less or more than 1 arg parameter. And for me they also hate if parameters are not String type. No idea why, so just using workarounds..

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();
}
}

How to send an array via a URI using Attribute Routing in Web API?

I'm following the article on Attribute Routing in Web API 2 to try to send an array via URI:
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([FromUri]int[] ids)
This was working when using convention-based routing:
http://localhost:24144/api/set/copy/?ids=1&ids=2&ids=3
But with attribute routing it is no longer working - I get 404 not found.
If I try this:
http://localhost:24144/api/set/copy/1
Then it works - I get an array with one element.
How do I use attribute routing in this manner?
The behavior you are noticing is more related to Action selection & Model binding rather than Attribute Routing.
If you are expecting 'ids' to come from query string, then modify your route template like below(because the way you have defined it makes 'ids' mandatory in the uri path):
[HttpPost("api/set/copy")]
Looking at your second question, are you looking to accept a list of ids within the uri itself, like api/set/copy/[1,2,3]? if yes, I do not think web api has in-built support for this kind of model binding.
You could implement a custom parameter binding like below to achieve it though(I am guessing there are other better ways to achieve this like via modelbinders and value providers, but i am not much aware of them...so you could probably need to explore those options too):
[HttpPost("api/set/copy/{ids}")]
public HttpResponseMessage CopySet([CustomParamBinding]int[] ids)
Example:
[AttributeUsage(AttributeTargets.Parameter, Inherited = false, AllowMultiple = false)]
public class CustomParamBindingAttribute : ParameterBindingAttribute
{
public override HttpParameterBinding GetBinding(HttpParameterDescriptor paramDesc)
{
return new CustomParamBinding(paramDesc);
}
}
public class CustomParamBinding : HttpParameterBinding
{
public CustomParamBinding(HttpParameterDescriptor paramDesc) : base(paramDesc) { }
public override bool WillReadBody
{
get
{
return false;
}
}
public override Task ExecuteBindingAsync(ModelMetadataProvider metadataProvider, HttpActionContext actionContext,
CancellationToken cancellationToken)
{
//TODO: VALIDATION & ERROR CHECKS
string idsAsString = actionContext.Request.GetRouteData().Values["ids"].ToString();
idsAsString = idsAsString.Trim('[', ']');
IEnumerable<string> ids = idsAsString.Split(',');
ids = ids.Where(str => !string.IsNullOrEmpty(str));
IEnumerable<int> idList = ids.Select(strId =>
{
if (string.IsNullOrEmpty(strId)) return -1;
return Convert.ToInt32(strId);
}).ToArray();
SetValue(actionContext, idList);
TaskCompletionSource<object> tcs = new TaskCompletionSource<object>();
tcs.SetResult(null);
return tcs.Task;
}
}

Resources