r3plica
r3plica

Reputation: 13367

Microsoft Bot Framework, LUIS and waterfalling

I am trying to build a bot. At first I was using Node.js but it because apparent that c# was better for my client, so I have started creating the Bot in c#. I have also created a LUIS application.

Now, the idea behind this Bot is that it will ask you a series of questions before combining your results at the end and picking out a solution based on your answers. The problem I have is that I have no idea how I can set the bot up to do that. In node, it talked about waterfalling, but I can't see anything like that in the c# version. Also, I am not sure if it should be in LUIS where I should be focusing my attention.

As an example, let's say I want the user to choose an object, then based on that object ask 3 questions like:

  1. Is it small?
  2. Is it light?
  3. What colour do you prefer?

And at the end say "You want a black, small, light camera." I can't seem to get that to work. I know some of you will say use FormFlow but the questions are dynamic and so is the object, which is why I used LUIS.

Does anyone know where I can find a good article on how to use LUIS to create a flow similar to that?

Upvotes: 1

Views: 764

Answers (2)

desflan
desflan

Reputation: 468

If FormFlow is too restrictive, you should consider using Dialogs. They allow for a much more open conversation.

I would do the following:

1) Ask the user to choose a category.

If possible, give the user a list of categories to choose from, using buttons. This means you will not have to ask LUIS for the category selected by the user.

Otherwise, allow the user to type an category and pass it to LUIS. The response from LUIS will contain an Entity. The Entity will hold the name of the category.

2) Forward to Dialog for that Category

Depending on the Entity returned from LUIS, forward to the appropriate Dialog which will be responsible for asking the next question

[LuisIntent("AskQuestionAboutCategory")]
    public async Task AskQuestion(IDialogContext context, LuisResult result)
    {
       //get Entity from LUIS response
        string category = result.Entities.FirstOrDefault(e => e.Type == "Category")?.Entity;

        switch (category)
        {
            case "Category 1":

                //forward to Dialog for Category1

                await
                    context.Forward(new Category1Dialog(), ResumeAfter,
                        new Activity {Text = result.Query}, CancellationToken.None);
                break;

            case "Category 2":

                //forward to Dialog for Category2

                await
                    context.Forward(new Category2Dialog(), ResumeAfter,
                        new Activity {Text = result.Query}, CancellationToken.None);
                break;

        }
    }

    private async Task ResumeAfter(IDialogContext context, IAwaitable<object> result)
    {
        context.Wait(MessageReceived);
    }

3) Ask Questions inside Dialog

Inside the Dialog, use a PromptDialog to ask Question 1. Use a switch on the answer of Question 1, to determine what to ask for Question 2. Continue the conversation like this.

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

    public virtual async Task MessageReceivedAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
    {

        var prompt = "what is the answer to question 1 ?";

        //ASK QUESTION 1
        PromptDialog.Text(context, ResumeAfterQuestion1, prompt);
    }

    private async Task ResumeAfterQuestion1(IDialogContext context, IAwaitable<string> result)
    {
        var input = await result;

        switch (input)
        {
            //ASK QUESTION 2, DEPENDING ON WHAT WAS ANSWERED FOR QUESTION 1
            case "Answer A":
                PromptDialog.Text(context, ResumeAfterQuestion2, "what is the answer to question 2 ?");
                break;
            case "Answer B":
                PromptDialog.Text(context, ResumeAfterQuestion2, "what is the answer to question 2 ?");
                break;
        }
    }


    private async Task ResumeAfterQuestion2(IDialogContext context, IAwaitable<string> result)
    {
        var input = await result;

        switch (input)
        {
            //ASK QUESTION 3
            case "Answer C":
                PromptDialog.Text(context, ResumeAfterQuestion3, "what is the answer to next question after Answer C ?");
        break;
            case "Answer D":
                PromptDialog.Text(context, ResumeAfterQuestion3, "what is the answer to next question after Answer D ?");
        break;
    }
    }

It looks like you will need to use switch statements to determine what questions to ask next.

click here for more info on Dialogs

Upvotes: 1

Ezequiel Jadib
Ezequiel Jadib

Reputation: 14787

Here is another alternative, if you want to manually handling this, you can set different context.Wait methods for each of your questions, having kind of Waterfall approach.

So let's say you have a LuisDialog with the ChooseCategory intent. In that method you will determine the category and base on some custom logic you will ask a new question.

That can be done with: a PromptDialog/ResumeAfter or a manual context.PostAsync/ context.Wait (which basically defines the method that will listen for the next message).

I don't know how is your custom logic, but you should be able to dynamically decide which is the next method that will listen for the upcoming message.

[LuisIntent("Choose category")]
public async Task ChooseCategory(IDialogContext context, LuisResult result)
{
    // get category logic..

    await context.PostAsync("This is my first question?");
    context.Wait(CaptureFirstQuestionAnswerAsync);
}

public async Task CaptureFirstQuestionAnswerAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
    IMessageActivity message = await argument;
    switch (message.Text.ToLower())
    {
        case "answer 1":
            // do something
            break;
        case "answer 2":
           // do something different
            break;
        default:
           // do something ...
            break;
    }

    await context.PostAsync($"This is my second question?");

    context.Wait(CaptureSecondQuestionAnswerAsync);
}

public async Task CaptureSecondQuestionAnswerAsync(IDialogContext context, IAwaitable<IMessageActivity> argument)
{
  //...
}

Upvotes: 1

Related Questions