简体   繁体   中英

Validate prompt with luis in waterfall dialog (Bot framework V4)

I am building a bot with the Bot Dialogs extension, but i'm having an issue on a Waterfall Dialog in my project. If the user says to the bot "i want to enter a new message" the bot will respond with a series of questions like "when do you want the message to be delivered to the user", "what do you want the message to say", etc. What i'm searching for is that when the user is asked for a date, if it responds "tomorrow" luis will return the respective date, but if the user says "foo" the bot will ask for a date again.

What i've come to do this is a code like this:

public class NewMessageDialog : ComponentDialog
{
    private readonly BotStateService _botStateService;
    private readonly BotServices _botServices;
    public NewMessageDialog(string dialogId, BotStateService botStateService, BotServices botServices) : base(dialogId)
    {
        _botStateService = botStateService ?? throw new ArgumentNullException(nameof(botStateService));
        _botServices = botServices ?? throw new ArgumentNullException(nameof(botServices));

        InitializeWaterfalls();
    }
    private void InitializeWaterfalls()
    {
        var waterfallSteps = new WaterfallStep[]
        {
            DateToSendStep,
            ExpirationTimeStep,
            BodyContentStep,
            SummaryStep
        };
        AddDialog(new WaterfallDialog($"{nameof(NewMessageDialog)}.mainFlow", waterfallSteps));
        AddDialog(new DateTimePrompt($"{nameof(NewMessageDialog)}.dateCreation")); //tb
        AddDialog(new NumberPrompt<double>($"{nameof(NewMessageDialog)}.expirationTime"));
        AddDialog(new TextPrompt($"{nameof(NewMessageDialog)}.body.content"));

        InitialDialogId = $"{nameof(NewMessageDialog)}.mainFlow";
    }
    private async Task<DialogTurnResult> DateCreatedStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        return await stepContext.PromptAsync($"{nameof(NewMessageDialog)}.dateCreation", new PromptOptions
            {
                Prompt = MessageFactory.Text("What is the date you want to send the message?"),
                RetryPrompt = MessageFactory.Text("Please enter a date")
            }, cancellationToken);
    }
    private async Task<DialogTurnResult> ExpirationTimeStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        stepContext.Values["dateToSend"] = Convert.ToDateTime(((List<DateTimeResolution>)stepContext.Result).FirstOrDefault().Value);
        return await stepContext.PromptAsync($"{nameof(NewMessageDialog)}.expirationTime", new PromptOptions
        {
            Prompt = MessageFactory.Text("What is the expiration time?"),
            RetryPrompt = MessageFactory.Text("Please enter a decimal number")
        }, cancellationToken);
    }

    private async Task<DialogTurnResult> BodyContentStep(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {

        stepContext.Values["expirationTime"] = Convert.ToDateTime(((List<DateTimeResolution>)stepContext.Result).FirstOrDefault().Value);
        return await stepContext.PromptAsync($"{nameof(NewMessageDialog)}.body.content", new PromptOptions
        {
            Prompt = MessageFactory.Text("What is the message body?")
        }, cancellationToken);
    }

And so on...

I haven't come to a proper way to do this: I could ask LUIS twice (once in a validator to see if the intent is to set a date, and once in the next step to get the correct datetime from an entity), I could ask luis in the validator and then save it into a class property to finally get the date value from there... Is there an efficient way to do this?

Also I don't know anything about chat maker, I have seen it in another websites while searching...

I recommend using a timex recognizer, like the one used in Core-Bot in the official samples repo:

// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using Microsoft.Bot.Schema;
using Microsoft.Recognizers.Text.DataTypes.TimexExpression;

namespace Microsoft.BotBuilderSamples.Dialogs
{
    public class DateResolverDialog : CancelAndHelpDialog
    {
        private const string PromptMsgText = "When would you like to travel?";
        private const string RepromptMsgText = "I'm sorry, to make your booking please enter a full travel date including Day Month and Year.";

        public DateResolverDialog(string id = null)
            : base(id ?? nameof(DateResolverDialog))
        {
            AddDialog(new DateTimePrompt(nameof(DateTimePrompt), DateTimePromptValidator));
            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), new WaterfallStep[]
            {
                InitialStepAsync,
                FinalStepAsync,
            }));

            // The initial child Dialog to run.
            InitialDialogId = nameof(WaterfallDialog);
        }

        private async Task<DialogTurnResult> InitialStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var timex = (string)stepContext.Options;

            var promptMessage = MessageFactory.Text(PromptMsgText, PromptMsgText, InputHints.ExpectingInput);
            var repromptMessage = MessageFactory.Text(RepromptMsgText, RepromptMsgText, InputHints.ExpectingInput);

            if (timex == null)
            {
                // We were not given any date at all so prompt the user.
                return await stepContext.PromptAsync(nameof(DateTimePrompt),
                    new PromptOptions
                    {
                        Prompt = promptMessage,
                        RetryPrompt = repromptMessage,
                    }, cancellationToken);
            }

            // We have a Date we just need to check it is unambiguous.
            var timexProperty = new TimexProperty(timex);
            if (!timexProperty.Types.Contains(Constants.TimexTypes.Definite))
            {
                // This is essentially a "reprompt" of the data we were given up front.
                return await stepContext.PromptAsync(nameof(DateTimePrompt),
                    new PromptOptions
                    {
                        Prompt = repromptMessage,
                    }, cancellationToken);
            }

            return await stepContext.NextAsync(new List<DateTimeResolution> { new DateTimeResolution { Timex = timex } }, cancellationToken);
        }

        private async Task<DialogTurnResult> FinalStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            var timex = ((List<DateTimeResolution>)stepContext.Result)[0].Timex;
            return await stepContext.EndDialogAsync(timex, cancellationToken);
        }

        private static Task<bool> DateTimePromptValidator(PromptValidatorContext<IList<DateTimeResolution>> promptContext, CancellationToken cancellationToken)
        {
            if (promptContext.Recognized.Succeeded)
            {
                // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part.
                // TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year.
                var timex = promptContext.Recognized.Value[0].Timex.Split('T')[0];

                // If this is a definite Date including year, month and day we are good otherwise reprompt.
                // A better solution might be to let the user know what part is actually missing.
                var isDefinite = new TimexProperty(timex).Types.Contains(Constants.TimexTypes.Definite);

                return Task.FromResult(isDefinite);
            }

            return Task.FromResult(false);
        }
    }
}

This is not a call to LUIS, it's just a check that you've entered a valid datetime, set it it's own small waterfall dialog. As you can see from the following check, it works for the test case you explained ('tomorrow' vs 'foo'):

示例聊天

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM