Microsoft Bot Framework .NET - Build flexible conversations using Scorable Dialogs

By: James E Mann

9   0   1088

Uploaded on 04/18/2017

In this video we take a look at how we can use a technique to allow users to change their minds or back out of conversations. Scorable dialogs allow this by decoupling the conversation hierarchy.

We take a look at an existing hierarchical bot using a traditional conversation tree and see how to improve it using scorable dialogs.

https://github.com/jamesemann/scorablebot

Take a look at Gary Pretty's blog where he explains in detail about setting up Scorable Dialogs http://www.garypretty.co.uk/2017/04/13/using-scorables-for-global-message-handling-and-interrupt-dialogs-in-bot-framework/

Comments (6):

By anonymous    2017-09-20

This is how I did it, I made a scorable for the word "cancel" this way from any point in the conversation you can start over. Also here is a good simple video to watch

    namespace PizzaBot.Dialogs
{
    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using Microsoft.Bot.Builder.Dialogs.Internals;
    using Microsoft.Bot.Builder.Internals.Fibers;
    using Microsoft.Bot.Connector;
    using Microsoft.Bot.Builder.Scorables.Internals;

    public class CancelScorable : ScorableBase<IActivity, string, double>
    {
        private readonly IDialogTask task;

        public CancelScorable(IDialogTask task)
        {
            SetField.NotNull(out this.task, nameof(task), task);
        }

        protected override async Task<string> PrepareAsync(IActivity activity, CancellationToken token)
        {
            var message = activity as IMessageActivity;

            if (message != null && !string.IsNullOrWhiteSpace(message.Text))
            {
                if (message.Text.Equals("cancel", StringComparison.InvariantCultureIgnoreCase))
                {
                    return message.Text;
                }
            }
            return null;
        }

        protected override bool HasScore(IActivity item, string state)
        {
            return state != null;
        }

        protected override double GetScore(IActivity item, string state)
        {
            return 1.0;
        }

        protected override async Task PostAsync(IActivity item, string state, CancellationToken token)
        {
            this.task.Reset();
        }

        protected override Task DoneAsync(IActivity item, string state, CancellationToken token)
        {
            return Task.CompletedTask;
        }
    }
}

then in my message controller i built a special case to handle it where await SendGetStarted(iActivity, activity); just starts the bot from the beginning:

    public async Task<HttpResponseMessage> Post([FromBody]Activity activity)
{

    if (activity.Type == ActivityTypes.Message)
    {               
        try
        {
           await Conversation.SendAsync(activity, () => new RootDialog(activity.ChannelId));
        }
        catch (Exception e)
        {
            IActivity iActivity = activity as IActivity;
            await SendGetStarted(iActivity, activity);
        }
    }
    else
    {
        await HandleSystemMessage(activity);
    }
    if (activity.Text == "cancel")
    {
        IActivity iActivity = activity as IActivity;
        await SendGetStarted(iActivity, activity);
    }
    var response = this.Request.CreateResponse(HttpStatusCode.OK);
    return response;
}

you also nneed to add something like this to your Golbal.asax

       var builder = new ContainerBuilder();
        builder.RegisterType<CancelScorable>()
            .As<IScorable<IActivity, double>>()
            .InstancePerLifetimeScope();

        builder.Update(Conversation.Container);`

Original Thread

By anonymous    2017-09-20

There is a few options you have here.

  1. You could use a list entity in LUIS. Although it does not sound as if you are using LUIS already.
  2. You could use scorable or an if statement in your ActivityType.Message section to catch the word "search" (or something similar) then implement logic to search a collection of some sort based on the user's next message. great video on scorables here
  3. You could create a card with a text box input that you could implement logic to use that to search some sort of collection.

    There is node/c# versions of all of this

    rich cards samples
    Rich cards doc
    Adaptive cards doc
    Adaptive cards sample

    I'm sure there is more solutions others can add as well

Original Thread

By anonymous    2017-11-20

This may be a good use case for a Scorable. There will be some tradeoffs though.

Your user would just have to type "unsubscribe" at any point or click a button with the postback/imback value of "unsubscribe". You can choose any text you desire as the recognized value. This way your unsubscribe logic will run independent of any other logic and you will not get trapped in your flow as you are now. Since the unsubscribe event will be handled by the global handler (Scorable) you can just handle any other message sent by the user as you normally would, in your case in a LuisDialog.

If you do not want your user to be able to unsubscribe at any point this solution will probably not work for you. In this case please post your code as @Ezequiel asked so we can better understand your flow. This would not be the only way in which to handle your flow.

Here is some info to look over:
Scorable Video

Scorable Blog

Scorable Blog

Original Thread

By anonymous    2018-01-22

This is probably a great opportunity to use Scorables. That way no matter where your user is in the dialog you can intercept and act on commands like "quit" or "help". There is a great video here.

Another way you can do this if to use a switch statement or if statements on Activity.Text when using ImBack or MessageBack to do different things depending what your user types code example below:

    var message = await result as Activity;
    switch (message.Text.ToLower())
    {
        case "yes":
            //do yes stuff
            break;
        case "no":
            //do no stuff
            break;
        case "quit":
            //do quit stuff
            break;
        case "help":
            //do help stuff
            break;
    }

the below code is "working" but I'm not exactly sure what your goal is. I would first move the prompt out of MessageReceivedAsync. anyways here is the code:

[Serializable]
public class RootDialog : IDialog<object>
{
    public Task StartAsync(IDialogContext context)
    {
        context.Wait(MessageReceivedAsync);

        return Task.CompletedTask;
    }
    private bool proceed;
    private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
    {
        PromptDialog.Choice(context,
            this.ResumeAfter,
            options: new string[] { "Yes", "No" },
            prompt: "Are you ready to continue?",
            retry: "Not a valid option",
            attempts: 3);

    }

    public async Task ResumeAfter(IDialogContext context, IAwaitable<string> argument)
    {
        var message = await argument;

        //this sets bool-proceed to false if message is QUIT, for example
        await ProcessTextMessage(context, message);

        bool proceed =true;
        if (proceed)
        {
            bool result;
            if (bool.TryParse(message, out result) && result)
            {
                //do stuff for YES
            }
            else
            {
                //this is NO
                await context.PostAsync("What else can I help you with?");
                context.Done("DONE");
            }
        }
    }

    private async Task ProcessTextMessage(IDialogContext context, string message)
    {
        switch (message)
        {
            case "QUIT":
                proceed = false;

                await context.PostAsync($"**QUIT** Application was triggered. What else can I help you with today?");
                context.Done("QUIT");
                break;

            case "RESET":
                proceed = false;

                await context.PostAsync($"**RESET** Application was triggered.");
                await StartAsync(context);
                break;

            case "HELP":
                await context.PostAsync($"Some other actions you can use: **QUIT**, **RESET**, **BACK**.");
                break;

            case "BACK":
                break;
            default:
                break;
        }
    }

Original Thread

Popular Videos 40

Submit Your Video

If you have some great dev videos to share, please fill out this form.