Microsoft Bot Framework and the IScorable interface

Using the IScorable interface and hook your implementation into the AutoFac dependency injection container supplied by the Microsoft Bot Framework team, you can create multiple separate global chat logic handlers:

BOT: Hi and Welcome!
Human: Do you like candy?
-> CandyResponseHandler
BOT: I certainly do! Licorice is my favourite!
Human: How are you feeling today?
-> FeelingResponseHandler
BOT: Never better! I just had some licorice a little while ago!

Let's create the candy response handler together before we move on:

public class CandyResponseHandler : IScorable
{
    private readonly IDialogStack _stack;

    public CandyResponseHandler(IDialogStack stack)
    {
        SetField.NotNull(out _stack, nameof(stack), stack);
    }

    public async Task PrepareAsync(IActivity item, CancellationToken token)
    {
        var message = item?.AsMessageActivity();
        var messageText = message?.Text;
        if (String.IsNullOrWhiteSpace(messageText))
        {
            // No text, no response!
            return Task.FromResult(null);
        }
        
        if (messageText == "Do you like candy?")
            return "I certainly do! Licorice is my favourite!";
        return null; // I only know that I like licorice candy ... 
    }

    public bool HasScore(IActivity item, object state) => state is string; // We only deal with string responses

    public double GetScore(IActivity item, object state) => state is string ? 1 : 0; // If we *have* a response, we consider ourselves mighty important - 100% scoring; otherwise, we're insignificant - 0% scoring

    public Task PostAsync(IActivity item, object state, CancellationToken token)
    {
        var message = item as IMessageActivity;
        IDialog dialog = new MessageOutputDialog(state as string);
        var interruption = dialog.Void(_stack);
        _stack.Call(interruption, null);
        return Task.CompletedTask;
    }

    public Task DoneAsync(IActivity item, object state, CancellationToken token)
        => Task.CompletedTask;
}

I left some comments in there so that you know what's going on: When hooked up, your handler will run for each message that arrives to your bot.

The handlers get a chance to prepare their data in their PrepareAsync method. Here's where you decide whether or not you understand what's going on.

Our handler understand what's going on, if the very specific question Do you like candy? is passed in. Otherwise, it's clueless.

Returning the data to the framework allows it to pass it back in for the two methods HasScore and GetScore.

HasScore is meant to quickly determine whether or not calling GetScore is useful.

GetScore returns this instance judgements of its validity (or the confidence of its decision/lookup/computation).

It's worth noting here, that a handler returning 100% confidence (that is score=1) short-circuits the handler execution, so the handlers are an excellent place to handle special bot commands ('help', 'reset', ...), language detection and other magic.

If the handler is elected for execution, its PostAsync method is invoked.

Those of you who have experience with the Bot framework will now question what this MessageOutputDialog is. It looks like this:

[Serializable]
public class MessageOutputDialog : IDialog
{
    private readonly string _message;

    public MessageOutputDialog(string message)
    {
        _message = HttpUtility.HtmlDecode(message);
    }

    public async Task StartAsync(IDialogContext context)
    {
        await context.PostAsync(_message);
        context.Done((object)null);
    }
}

The dialog allows you to pass a message to the user, participating in the already set-up dialog stack.

OK, so how do you actually inject your global message handler? That happens in your WebApiApplication.Application_Start method in Global.asax.cs:

    var builder = new ContainerBuilder();

    // Order of execution
    builder.RegisterType()
        .As< IScorable< IActivity, double >>()
        .InstancePerLifetimeScope();
    builder.RegisterType()
        .As< IScorable< IActivity, double >>()
        .InstancePerLifetimeScope();

    builder.Update(Conversation.Container);

Right. Enjoy your licorice! :-)

Comments

Popular posts from this blog

Auto Mapper and Record Types - will they blend?

Unit testing your Azure functions - part 2: Queues and Blobs

Testing WCF services with user credentials and binary endpoints