If you are not familiar with the command pattern, click here to read about it.
In this article we’ll show you how you can implement the Command Pattern
using scorables, which will allow you to create an easily scalable architecture such that you can seamlessly add new features (commands) to your Bot in the future.
What are Scorables?
Within the Bot Framework .NET SDK, Scorables
allows you to create dialogs as flexible components in lieu of the traditional model.
If you’ve built a Bot using the Microsoft Bot Framework before, you likely have some familiarity with the dialog stack. In the example below, let’s say a user was in the GetWeather Dialog
but decided they wanted to switch to the RealEstate Dialog
. Under traditional dialog flow, a user would need to navigate through the following dialogues (SomeDialog
, OtherDialog
, potentially more), then resolve each dialog on the stack all the way to the RootDialog
before being able to access the RealEstate Dialog
that the user wanted.
Scorable dialogs within a bot monitor all incoming messages, and determine whether or not that message is actionable in some way. Messages that are scorable are then given a score between 0-1 (1 being the highest) by each Scorable dialog within the bot, the scorable dialogue which determines the highest score then handles the response to the user. When a user triggers a Scorable Dialog, that dialog will now be added to the top of the existing dialog stack, and upon resolution the conversation can continue from where it left off.
With scorables, we can build dialogs for a bot as a reusable component rather than dealing with the old strict dialogue structure to create more flexible conversations, and handle some common things a user might want to do.
Creating a Scorable dialog
In order to create a scorable dialogue, you’ll need to implement the IScorable
interface, which defines several methods you must implement:
public interface IScorable<in Item, out Score> { Task<object> PrepareAsync(Item item, CancellationToken token); bool HasScore(Item item, object state); Score GetScore(Item item, object state); Task PostAsync(Item item, object state, CancellationToken token); Task DoneAsync(Item item, object state, CancellationToken token); }
- PrepareAsync: This first method in the scorable instance accepts incoming message activity, analyzes it and sets the dialog’s
state
, which is passed to all the other methods in the interface. - HasScore: This boolean method checks the state property to determine if the Scorable Dialog should provide a score for the message. If it returns false, the message will be ignored by the Scorable dialog.
- GetScore: Will only trigger if
HasScore
returns true, provision the logic here to determine the score for a message between 0 – 1.0 - PostAsync: This method is called if the scorable “wins” (observes the highest score between all the Scorable dialogs), in here core actions are performed.
- DoneAsync: Here you should dispose of any scoped resources as it is called when the scoring process has completed.
As stated above, the state
instance produced in the PrepareAsync
method is passed to all the other methods in the interface. Each method will use this state
to implement its’ logic. HasScore
will return a true/false if the message includes a state that identifies it as scorable. Assuming HasScore
returns true, the GetScore
method will be called to compare this scorable among other possible instances, and compare each instance’s assigned score from 0-1. Finally, the PostAsync
method will be triggered by whichever scorable dialogue observes the highest score among those instances.
Now that we’ve explained what Scorables are, we’ll focus on how to leverage them to create a command pattern architecture for bots.
Creating a command IScorable
Back to setting up a command pattern for a bot, we can implement this by creating an abstract base class (which uses the IScorable<IActivity, double>
interface) for your commands to inherit. Custom logic can then be defined in each individual command which inherits from this abstract class.
We can define this abstract base class like this:
public abstract class CommandScorable : ScorableBase<IActivity, bool, double> { protected readonly IBotToUser BotToUser; public CommandScorable(IBotToUser botToUser) { SetField.NotNull(out this.BotToUser, nameof(botToUser), botToUser); } public abstract string Command { get; } protected override Task DoneAsync(IActivity item, bool state, CancellationToken token) { return Task.CompletedTask; } protected override double GetScore(IActivity item, bool state) { return state ? 1 : 0; } protected override bool HasScore (IACtivity item, bool state) { return state; } protected override Task<bool> PrepareAsync(IActivity item, CancellationToken token) { var message = item.AsMessageActivity(); if(message == null){ return Task.FromResult(false); } return Task.FromResult(message.Text.ToLowerInvariant().Contains(this.Command.ToLowerInvariant())); } }
The PrepareAsync
method receives the user’s message to the bot and checks whether the message contains a specific Command
property value. The Command
property in this class defines what user input the command instance will respond to. If the message does contain the corresponding command, this base command returns true. Finally, the PostAsync
operation from the IScorable
interface will run in order to provide an answer to the user by using the protected IBotToUser
instance.
With this abstract class set up to handle the Scorable interface for our commands, let’s go over an example command using this new abstract class. Suppose you wanted to define a command which reacts to a user asking for help
:
public class HelpCommand: CommandScorable { public HelpCommand(IBotToUser botToUser) : base(botToUser) { } public override string Command { get{ return "Help"; } } protected override async Task PostAsync(IActivity item, bool state, CancellationToken token) { var message = item.AsMessageActivity(); await this.BotToUser.PostAsync(await this.GetHelpAsync(message)); } private async Task<string> GetHelpAsync(IMessageActivity message) { // Here you should get the help info for the user // You can inspace the message to search for specific help like 'help products' // and provide help related to the products you have return string.Empty; } }
How does this relate to the command pattern? The four key concepts of the command pattern are command, receiver, invoker, and client.
- Command: Represented by each non-abstract scorable class in the solution having the
Command
value overriden. - Receiver: On each command itself, represented by the logic performed at the
PostAsync
method overriden from the base command class. - Invoker: The bot builder framework’s scorable evaluation logic processes a set of scorable instances, evaluates them in order to get highest scoring one, and if there is any, dispatches it.
- Client: The bot itself levering the bot builder SDK, the bot has access to registered IScorable types, the order to instance them, and provides those instances to the scorable evaluation logic in order to dispatch the best result (which would be the
invoker
).
Registering your Commands
You now need to register your commands within the bot builder framework message handling pipeline. If you’ve created your own bots in the past, this operation may seem familiar.
To register the commands you want your bot to handle, while interacting with the user, you could simply update the Conversation.Container
as shown below within your app initialization at global.asax:
var builder = new ContainerBuilder(); builder .RegisterType<HelpCommand>() .Keyed<HelpCommand>(typeof(HelpCommand).Name) .AsImplementedInterfaces() .InstancePerMatchingLifetimeScope(DialogModule.LifetimeScopeTag); builder.Update(Conversation.Container);
Once done, you can run your bot and if the user submits a message like:
Help me open an account
the HelpCommand
will intercept it and execute the logic you implemented at its PostAsync
method in order to provide an appropriate answer.
You should still maintain a root
dialog in case none of a user’s messages correspond to a command. You should instance it from the WebAPI controller method that usually handles /api/messages as shown below:
public async Task<HttpResponseMessage> Post([FromBody]Activity activity) { if(activity.Type == ActivityTypes.Message) { await Conversation.SendAsync(activity, () => new RootDialog()); } else { this.HandleSystemMessage(activity); } return Request.CreateREsponse(HttpStatusCode.OK); }
Note: If you implement the
IScorable
interface asIScorable<IActivity, double>
, then you can register those types within the Conversation.Container in orderto score the IActivity received during the handling pipeline.
A More complex scenario
Many channels provide the ability to attach interactive elements such as cards. The Bot Framework supports Hero Card, Thumbnail Card, Receipt Card, Sign-In Card, Animation Card, Video and Audio Cards, and the recently introduced Adaptive Cards. Once a card type has been built, it is mapped into an Attachment
data structure to be returned to as a reply the user.
Perhaps your bot needs to showcase more than one of these card attachments. Each one of the afforementioned cards is generated in a specific way, but those results are being sent as attachments. Instead of building multiple handlers for each card scenario, we could create a shared abstract core which attaches the card to the reply back to the user (in the PostAsync
method), providing an overridable method for custom descendents to generate card contents.
We’ll create a RichCardSCorable
class as shown below. The message.Attachments
property of the message activity is assigned with a new list of card attachments containing the attachment
public class SigninCardScorable : RichCardScorable { public SigninCardScorable(IBotToUser botToUser) : base(botToUser) { } public override string Command { get { return "Signin"; } } protected override IList<Attachment> GetCardAttachment() { return new SigninCard { Text = "BotFramework Sign-in Card", Buttons = new List<CardAction> { new CardAction(ActionTypes.Signin, "Sign-in", value: "https://login.microsoftonline.com/") } }.ToAttachment(); } }
Summary
In this article we described what scorables are, and shown how you can leverage them to implement a command pattern-like architecture within your bots. To see a detailed bot sample where all these benefits are showcased, see the Test Bot live sample.
You can also check out the following links for more information about Scorables in the .NET SDK:
Source code for the card attachments test bot
Using Scorables for global message handling and interrupting dialogs, by Gary Pretty, Microsoft MVP
VIDEO – Build flexible conversations using Scorable Dialogs by James Mann
Sample – Simple Bot implementing scorables with dialogs
Sample – Global Message Handlers
Happy Making!
Pablo Constantini, Ezequiel Jadib and Matthew Shim from the Bot Framework Team