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

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

Related

Load Image Async from Api

I have a Blazor Server .Net 6 app. It has a Synfusion grid which has an ImageViewer componen that I have built. When the grid is loaded it passes a DocumentID to the ImageViewer for each row. The ImageViwer takes the DocumenID and loads an image via a web API service from a database. Teh problem I have is that the image does not fully load, it works if I use the OnInitializedAsync method but thsi does not work if I filter the data. Any ideads the best method to load such images
<SfGrid>
<MyImageViewer AuthCookieValue="#AuthCookieValue" DocumentID="#data.DocumentID" />
<SfGrid>
//THIS IS MY IMAGE CONTROL
#inject HttpClient httpClient
#if (DocumentFileData != null)
{
<img src="data:image;base64,#(Convert.ToBase64String(DocumentFileData))" />
}
#code {
public int _DocumentID { get; set; }
[Parameter] public string AuthCookieValue { get; set; }
[Parameter] public int DocumentID
{
get { return _DocumentID; }
set
{
_DocumentID = value;
//I know this is naughty to call a method via a property and does not work but thought I would try to trigger the change of the image when I refresh the grid
Task.Run(() => GetDocument());
}
}
private byte[] DocumentFileData { get; set; }
protected async override Task OnInitializedAsync()
{
//THIS METHOD WORKS BUT DOES NOT WORK WHEN I CHANGE THE GRID
if (DocumentID != 0)
{
await GetDocument();
}
}
private async Task GetDocument()
{
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + AuthCookieValue);
MyServices.DocumentService documentService;documentService = new(httpClient);
documentService = new(httpClient);
DocumentModel doc = await documentService.GetDocumentAsync(_DocumentID);
DocumentFileData = doc.FileData;
}
}
Many thanks in advance
Make two small changes:
// //I know this is naughty to call a method via a property and does not work but thought I would try to trigger the change of the image when I refresh the grid
// Task.Run(() => GetDocument());
and
//protected async override Task OnInitializedAsync()
protected async override Task OnParametersSetAsync()
{
See the Lifecycle events documentation.
OnInitializedAsync() is only called once in the lifetime of a component. OnParametersSetAsync() is called each time DocumentID changes, and the side benefit is that you don't need that Task.Run() anymore.
The fact that Task.Run() was not awaited here made your UI fall out of sync and not show the image. It was being loaded but not rendered.

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

MS bot reply twice

At localhost in the emulator, as soon as I connect to the bot I get the sign in card but in the azure registered channel web chat I don't get the sign in card instead I need to type something then I get it twice. webchat screenshot
How do I get the sign in card without the need to type something in chat.
I've turned the always on option and it still didn't help.
The sign in card come from the community auth middleware Link here
and i use the following code in the onTurn's bot class:
public async Task OnTurn(ITurnContext turnContext)
{
if (turnContext.Activity.UserHasJustJoinedConversation() || turnContext.Activity.UserHasJustSentMessage())
{
var state = turnContext.GetConversationState<Dictionary<string, object>>();
var dialogCtx = dialogs.CreateContext(turnContext, state);
await dialogCtx.Continue();
if (!turnContext.Responded)
{
await dialogCtx.Begin("mainDialog", new Dictionary<string, object>
{
["Value"] = turnContext.Activity.Text
});
}
}
}
and i also use the following two functions
public static bool UserHasJustSentMessage(this Activity activity)
{
return activity.Type == ActivityTypes.Message;
}
public static bool UserHasJustJoinedConversation(this Activity activity)
{
return activity.Type == ActivityTypes.ConversationUpdate && activity.MembersAdded.FirstOrDefault().Id != activity.Recipient.Id;
}
There are two ConversationUpdates that get emitted when a conversation starts: one for the user joining the conversation and one for the bot joining the conversation so you need filter out the bot's one:
public static bool UserHasJustJoinedConversation(this Activity activity)
{
return activity.Type == ActivityTypes.ConversationUpdate
&& activity.MembersAdded.Any(channelAccount => channelAccount.Id != "YourBotName");
}

Closing an async Response

I'm trying to close the current response but nothing happens when I try HttpContext.Response.Body.Close() and Response.End() does not exist.
The reason I'm trying to achieve this is because of legacy validator functions that write an error and close the response, or at least stopping the parent WebAPI method.
Example:
private async Task Register_v2()
{
//Read JSON to object
UserRegisterRequest userRegisterRequest = Request.ReadBody().FromJson<UserRegisterRequest>();
//Validate object (legacy static method with a lot of logic)
//Validate() should end the response if object not validated
userRegisterRequest.Validate(isJson: true, isThrowHttpError: true);
//Code still reaches here and request does not close
string test = "hey I'm alive";
}
Can I workaround this with middleware somehow?
Thanks
There are two ways to terminate the Request pipeline.
Use app.Run in Startup.Configure
Do not invoke _next(context) in Middleware.InvokeAsync
For your scenario, you could try second option by determining whether to invoke _next(context).
public class FirstMiddleware
{
private readonly RequestDelegate _next;
public FirstMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
await context.Response.WriteAsync($"This is { GetType().Name }");
//decide whether to invoke line below based on your business logic
//await _next(context);
bool isValid = userRegisterRequest.Validate(isJson: true, isThrowHttpError: true);
//change userRegisterRequest.Validate to reutrn whether the model is valid
if(!isValid)
{
await context.Response.WriteAsync($"Model is not valid");
}
else
{
await _next(context);
}
}
}

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

Resources