4
votes

So they have this nice example inside the EchoBot sample to demonstrate chains

 public static readonly IDialog<string> dialog = Chain.PostToChain()
        .Select(msg => msg.Text)
        .Switch(
            new Case<string, IDialog<string>>(text =>
                {
                    var regex = new Regex("^reset");
                    return regex.Match(text).Success;
                }, (context, txt) =>
                {
                    return Chain.From(() => new PromptDialog.PromptConfirm("Are you sure you want to reset the count?",
                    "Didn't get that!", 3)).ContinueWith<bool, string>(async (ctx, res) =>
                    {
                        string reply;
                        if (await res)
                        {
                            ctx.UserData.SetValue("count", 0);
                            reply = "Reset count.";
                        }
                        else
                        {
                            reply = "Did not reset count.";
                        }
                        return Chain.Return(reply);
                    });
                }),
            new RegexCase<IDialog<string>>(new Regex("^help", RegexOptions.IgnoreCase), (context, txt) =>
                {
                    return Chain.Return("I am a simple echo dialog with a counter! Reset my counter by typing \"reset\"!");
                }),
            new DefaultCase<string, IDialog<string>>((context, txt) =>
                {
                    int count;
                    context.UserData.TryGetValue("count", out count);
                    context.UserData.SetValue("count", ++count);
                    string reply = string.Format("{0}: You said {1}", count, txt);
                    return Chain.Return(reply);
                }))
        .Unwrap()
        .PostToUser();
}

However instead of using a REGEX to determine my conversation path I would much rather use a LUIS Intent. I'm using this nice piece of code to extract the LUIS Intent.

public static async Task<LUISQuery> ParseUserInput(string strInput)
    {
        string strRet = string.Empty;
        string strEscaped = Uri.EscapeDataString(strInput);

        using (var client = new HttpClient())
        {
            string uri = Constants.Keys.LUISQueryUrl + strEscaped;
            HttpResponseMessage msg = await client.GetAsync(uri);

            if (msg.IsSuccessStatusCode)
            {
                var jsonResponse = await msg.Content.ReadAsStringAsync();
                var _Data = JsonConvert.DeserializeObject<LUISQuery>(jsonResponse);
                return _Data;
            }
        }
        return null;
    }

Now unfortunately because this is async it doesn't play well with the LINQ query to run the case statement. Can anyone provide me some code that will allow me to have a case statement inside my chain based on LUIS Intents?

1
Why didn't use context.call and context.Forward to chain your dialog (inside the dialog) instead of "chain dialog" in bot framework?OmG

1 Answers

0
votes

Omg is right in his comment.

Remember that IDialogs have a TYPE, meaning, IDialog can return an object of a type specified by yourself:

public class TodoItemDialog : IDialog<TodoItem>
{
   // Somewhere, you'll call this to end the dialog
   public async Task FinishAsync(IDialogContext context, IMessageActivity activity)
   {
      var todoItem = _itemRepository.GetItemByTitle(activity.Text);
      context.Done(todoItem);
   }
}

the call to context.Done() returns the object that your Dialog was meant to return. Whever you're reading a class declaration for any kind of IDialog

public class TodoItemDialog : LuisDialog<TodoItem>

It helps to read it as:

"TodoItemDialog is a Dialog class that returns a TodoItem when it's done"

Instead of chaining, you can use context.Forward() which basically forwards the same messageActivity to another dialog class..

The difference between context.Forward() and context.Call() is essencially that context.Forward() allows you to forward a messageActivity that is immediately handled by the dialog called, while context.Call() simply starts a new dialog, without handing over anything.

From your "Root" dialog, if you need to use LUIS to determine an intent and return a specific object, you can simply forward the messageActivity to it by using Forward and then handling the result in the specified callback:

await context.Forward(new TodoItemDialog(), AfterTodoItemDialogAsync, messageActivity, CancellationToken.None);

private async Task AfterTodoItemDialogAsync(IDialogContext context, IAwaitable<TodoItem> result)
{
    var receivedTodoItem = await result;

    // Continue conversation
}

And finally, your LuisDialog class could look something like this:

[Serializable, LuisModel("[ModelID]", "[SubscriptionKey]")]
public class TodoItemDialog : LuisDialog<TodoItem>
{
    [LuisIntent("GetTodoItem")]
    public async Task GetTodoItem(IDialogContext context, LuisResult result)
    {
        await context.PostAsync("Working on it, give me a moment...");
        result.TryFindEntity("TodoItemText", out EntityRecommendation entity);
        if(entity.Score > 0.9)
        {
            var todoItem = _todoItemRepository.GetByText(entity.Entity);
            context.Done(todoItem);
        }
    }
}

(For brevity, I have no ELSE statements in the example, which is something you of course need to add)