Give your bot some ‘manners’ with the BestMatchDialog

As a follow up to my earlier post which introduced the new BestMatch Dialog, now available via NuGet. One of the best uses for the BestMatch Dialog, and the reason I created it in the first place, is adding ‘manners’ to a bot. i.e. being able to respond to those common things that people say that fall outside of the usual conversation you would handle with your bot. Therefore this post will focus on and show how to use a BestMatch Dialog as a child dialog to respond to general messages like “hello” and “thanks”.  It is amazing what a huge difference handling these sorts of messages can make and how much more natural talking to your bot will feel for your end users.

Install the BestMatchDialog NuGet package

The first thing you need to do to get started is to install the BestMatchDialog NuGet package into your bot project.  You can either do this by searching the NuGet repository for it or running the following command at the NuGet console.

PM> Install-Package BestMatchDialog

Create a dialog to serve up common responses

Next we need to create a new BestMatchDialog.  In our case it will handle common responses, so lets call it CommonResponsesDialog.  The code for our new dialog is shown below.

There are a few things to notice about our dialog above;

  • We are handling 4 types of message here, greetings, goodbyes, status requests and thank yous.  For each one we have a list of common messages a user might use, but the incoming message doesn’t have to match exactly.  You can set a threshold of matching items in the list more or less exactly. e.g. “hello” could match “hello there”.  By default the threshold is set to 0.5.  You can also determine if you want the match to be case sensitive or if it should ignore non alphanumeric characters.  All of this can be set against each specific list and below is an example of how you can do it;
  • If we were using the BestMatch dialog as our root dialog then we would be calling context.wait(MessageReceieved) within each of our handlers in order to wait for the next message from the user, but here we want to return control to the parent dialog.  Therefore within each handler we are calling context.done(true).  The boolean value indicated if we have handled the incoming message or not.
  • We are overriding the NoMatchFound method to handle the situation where the incoming message doesn’t match with any of our lists.  We could send a message back to the user here, but instead you can handle that within the parent dialog.  To do this in this example we are calling context.Done(false) to indicate that we have not handled the incoming message.

Call our new Common Responses dialog from your root dialog

Now that we have our new instance of a BestMatch dialog to handle common messages we need to call it from our root dialog.  Consider an example where your root dialog is a LUIS dialog, which can complete a certain number of tasks for the end user, but if the user enters a general message, like “hello”, the dialog will default to the None intent method. Previously we might have just sent a general message to the user like, “I don’t understand what you want”, but now we can intercept the message and try and match it using our new dialog. Below is an example of how you would do this.

There are two things to note about the above;

  • As we are calling the dialog as a child dialog, as opposed to the root of your application, we need to pass the dialog the incoming message that we want to try and match.  Above you can see how we create the dialog and set the InitialMessage before calling context.Call to add the dialog to the stack.
  • In our resume method that is called after the dialog has finished, you will notice that the result that we get back from the dialog is a boolean, as we saw earlier in the dialog itself.  Here we can check if the result is true or false, with true meaning we found a match and sent a message to the user, and false meaning we still have not handled the message.  Therefore if the result is false here we sent an appropriate message to the user.  You could just as easily do anything here other than just send a message.

The example above is using a LUIS dialog as the root dialog in the app, but you don’t have to.  You can use any dialog you wish and fit the BestMatch dialog in wherever you feel fits best.

Summary

Please have a go at adding some ‘manners’ to your bot and let me know how you get on.  I will be more than happy to help you and would welcome any feedback.  My GitHub repository which contains a working sample (of using the BestMatch dialog as the root dialog in a bot) and the source code can be found at https://github.com/garypretty/botframework

10 thoughts to “Give your bot some ‘manners’ with the BestMatchDialog”

  1. Hi,

    This seems to be a very useful little addon for the current BotFramework. I just seem to be a little stuck on some error in this example. Namely I try to also call the new CommonResponsesDialog from my Luis “None” handler, but am stuck on the error message on this line:

    context.Call(dialog, AfterCommonResponseHandled);

    The error message is:
    Argument 2: cannot convert from ‘method group’ to ‘ResumeAfter’

    and I really seem to be unable to get rid of it. Any help appreciated

    1. Hey Imre.

      As I cannot see your code it is difficult for me to diagnose the problem. Can you check if your AfterCommonResponseHandled method looks similar to the one below?

      private async Task AfterCommonResponseHandled(IDialogContext context, IAwaitable result)
      {
      var messageHandled = await result;

      if (!messageHandled)
      {
      await context.PostAsync("I’m not sure what you want");
      }

      context.Wait(MessageReceived);
      }

      Note that it is an async method with an IAwaitable as the second param.

      Let me know how you get on.

  2. Nice tutorial.

    This is extremely useful as I was trying to figure out a way to intercept the messages before they hit LUIS and check for matches (to preserve the amount of LUIS calls made). Now with this, I can free up some of my “less useful” intents, use best matches instead and save the more complicated messages for LUIS.

    Thank you.

  3. Hello,

    I have implemented this and no matter what I type, I am always getting “I am not sure what you want” returned to me.. Any ideas?

    Here is my CommonResponsesDialog class:

    [Serializable]
    public class CommonResponsesDialog : BestMatchDialog
    {
    [BestMatch(new string[] { “Hi”, “Hi There”, “Hello there”, “Hey”, “Hello”,
    “Hey there”, “Greetings”, “Good morning”, “Good afternoon”, “Good evening”, “Good day” },
    threshold: 0.5, ignoreCase: true, ignoreNonAlphaNumericCharacters: true)]
    public async Task HandleGreeting(IDialogContext context)
    {
    var responsesFilePath = Content.GreetingsKnowsNameFilePath;
    var _userName = “Chris”;

    var response = Helper.ReturnResponseFromTextFile(responsesFilePath, _userName);
    await context.PostAsync(response);
    context.Done(true);
    }

    public override async Task NoMatchHandler(IDialogContext context, string messageText)
    {
    context.Done(false);
    }
    }

    And here is my DefaultDialog class:

    [Serializable]
    public class DefaultDialog : LuisDialog
    {
    private readonly IDialogPromptsProvider _promptsProvider;

    public DefaultDialog(ILuisService luis, IDialogPromptsProvider promptsProvider) : base(luis)
    {
    if (promptsProvider == null) throw new ArgumentNullException(nameof(promptsProvider));
    _promptsProvider = promptsProvider;
    }

    [LuisIntent(“”)]
    public async Task None(IDialogContext context, LuisResult result)
    {
    var dialog = new CommonResponsesDialog {InitialMessage = result.Query};
    context.Call(dialog, AfterCommonResponseHandled);
    }

    private async Task AfterCommonResponseHandled(IDialogContext context, IAwaitable result)
    {
    var messageHandled = await result;

    if (!messageHandled)
    {
    await context.PostAsync(“I’m not sure what you want.”);
    }

    context.Wait(MessageReceived);
    }
    }

  4. Hi Gary – i’ve tried this out, and have a strange repeatable situation when used in conjunction with a LUIS intent.
    1) Start the bot
    2) Enter text that matches a LUIS intent – the response comes back correctly.
    3) Enter text that matches a BestMatchDialog, – the reponse comes back correctly from BestMatchDialog e.g. HandleGreeting.
    4) Enter the same text from step 2 – the response comes back from BestMatchDialog NoMatchHandler.

    This continues until I restart the bot. Is something being cached somewhere?

  5. I’ve dispalyed audio and video cards through QnA maker. But i am not able to use pdf and ppt.
    As for video and audio i have refer your blog from which i added the attachment tag in my knowledge base, and its working perfectly.
    i want to use ppt and pdf document. how to use it? can u please help?

    1. I don’t support those type of attachments in the QnAMaker yet. Happy to receive pull requests on the repo though if you wanted to help add this 🙂

Leave a Reply

Your email address will not be published. Required fields are marked *