Using Scorables for global message handling and interrupting dialogs in Bot Framework

If you have been using Bot Framework for any length of time, you will likely be familiar with dialogs and the dialog stack – the idea that you have a root dialog and you can pass control to child dialogs which, when finished, will pass control back up to their parent dialog.  This method of dialog management gives us a lot of flexibility when designing our conversational flow.  For example, using the LUIS service to determine a user’s intent, but then falling back to a QnA Maker dialog if no intent can be recognised.

However, there are times when we might want to be able to interrupt our current dialog stack to handle an incoming request, for example, responding to common messages, such as “hi”, “thanks”, “how are you” etc.  Scorables are a special type of dialog that we can use within Bot Framework to do just this – global message handlers if you will!

Scorable dialogs monitor all incoming messages to a bot and decide if they should try to handle a message.  If they should then they set a score, between 0 and 1 as to what priority they should be given – this allows you to have multiple scorable dialogs and whichever one has the highest score will be the one that handles the message.  If a scorable matches against an incoming message (and has the highest score if there are multiple matches) then it can then handle the response to the user rather than it being picked up by the current dialog in the stack.

Simple example

Below is an example simple scorable dialog that is designed to respond to some common requests as described above.

Let’s discuss what’s happening in the code above.

I the PrepareAsync method, our scorable dialog accepts the incoming message activity and checks the incoming message text to see if it matches one of the phrases that we want to respond to.  If the incoming message is found to be a match then we return that message, otherwise we return null.  This sets the state of our dialog which is then passed to some of the other methods within the dialog to decide what to do next.

Next, the HasScore method checks the state property in order to determine if the dialog should provide a score and flag that it wants to handle the incoming message.  In this instance the dialog is simple checking to see if the PrepareAsync method set our state to a string.  If it did then HasScore returns true, but if not (in which case state would be null) it returns false.  If the dialog returns false at this point then the message will not be responded to by this dialog.

If the HasScore returns true then the GetScore method kicks in to determine the score that the dialog should post so that it can be prioritised against other scorables that have also returned a score.  In this case, to keep things simple, we are returning a value of 1.0 (the highest possible score) to ensure that the dialog handles the response to the message.  There are other scenarios where we might wish to return an actual score, for example you might have several scorables, each sending the incoming message to a different QnA Maker service and if an answer is found the score could be determined based on the response from the QnA Maker service.  In this scenario the dialog that receives the highest confidence answer from it’s service would win and respond to the message.

At this point, if the dialog has returned a score and it has the highest score amongst any other competing scorables, the PostAsync method is called. Within the PostAsync method we can then hand off the task of responding to another dialog by adding it to the dialog stack, so that it becomes the active dialog.  In the example we are checking to see which phrase the incoming message matches and returning an appropriate response to the user by passing the response to a very basic dialog shown below (hint: it’s a very basic dialog to illustrate the point, but you could add any dialog here).

Once the dialog above is completed and calls context.Done, we are passed back to our scorable and the DoneAsync method is called and the process is complete.

The next message that gets received by the bot, providing it doesn’t match again with a scorable dialog, will pick up exactly where it left off in the conversation.

Registering a scorable

In order for scorables to respond to incoming messages, we need to register them.  To register the scorable in the example above we first create a module that registers the scorable.

Then register the new module with the conversation container in Global.asax.cs.

Summary

In this post we have seen how you can use scorable dialogs to perform global message handling.  There are many potential use cases for using scorables, including implementing things like settings dialogs or having some form of global cancel operation for a user to call, bot of which can be seen in one of the samples over at the Bot Builder Samples GitHub Repo.

Personally, I love scorables and I think you will too.

5 thoughts to “Using Scorables for global message handling and interrupting dialogs in Bot Framework”

  1. Hi Gary,
    this article solves another problem for me. Thanks a lot 😉

    But, when Skype for Business chat comes into play, does this work too? As far as I learnt, the class WebApiApplication and its method Application_Start won´t be triggered.

    1. I haven’t tried this with Skype for Business yet I’m afraid. Let me know if you try it as I would be interested in the results.

  2. Great explanation; it adds the bits that are missing from the official docs.
    One small thing: what is the purpose of the line await this.task.PollAsync(token);?

  3. In the most recent version of BotBuilder there’s an IDialogTask extension called InterruptAsync that wraps all the few lines of internal voodoo code you have in your example.

Leave a Reply

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