Выход из проверки пользовательских подсказок в Bot Framework V4 - PullRequest
0 голосов
/ 27 июня 2019

Я начал создавать диалог в Microsoft Framework Framework V4, и для этого я хочу использовать настраиваемую проверку приглашений. Пару месяцев назад, когда была выпущена версия 4.4, в PromptValidatorContext было добавлено новое свойство «AttemptCount». Это свойство дает информацию о том, сколько раз пользователь давал ответ. Очевидно, что было бы неплохо завершить текущий диалог, если пользователь был перепроверен несколько раз. Однако я не нашел способа выйти из этого состояния, потому что данный PromptValidatorContext не предлагает способ заменить диалог, в отличие от DialogContext (или WaterfallStepContext). Я задал этот вопрос на github , но не получил ответа.

public class MyComponentDialog : ComponentDialog
{
    readonly WaterfallDialog waterfallDialog;

    public MyComponentDialog(string dialogId) : (dialogId)
    {
        // Waterfall dialog will be started when MyComponentDialog is called.
        this.InitialDialogId = DialogId.MainDialog;

        this.waterfallDialog = new WaterfallDialog(DialogId.MainDialog, new WaterfallStep[] { this.StepOneAsync, this.StepTwoAsync});
        this.AddDialog(this.waterfallDialog);

        this.AddDialog(new TextPrompt(DialogId.TextPrompt, CustomTextValidatorAsync));
    }

    public async Task<DialogTurnResult> StepOneAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        var promptOptions = new PromptOptions
                            {
                                Prompt = MessageFactory.Text("Hello from text prompt"),
                                RetryPrompt = MessageFactory.Text("Hello from retry prompt")
                            };

        return await stepContext.PromptAsync(DialogId.TextPrompt, promptOptions, cancellationToken).ConfigureAwait(false);
    }

    public async Task<DialogTurnResult> StepTwoAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
    {
        // Handle validated result...
    }

    // Critical part:
    public async Task<bool> CustomTextValidatorAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
    {
        if (promptContext.AttemptCount > 3)
        {
            // How do I get out of here? :-/
        }

        if (promptContext.Context.Activity.Text.Equals("password")
        {
            // valid user input
            return true;    
        }

        // invalid user input
        return false;
    }
}

Если эта функция на самом деле отсутствует, возможно, я мог бы обойти эту проблему, сохранив информацию в TurnState и проверив ее в моем StepTwo. Примерно так:

promptContext.Context.TurnState["validation"] = ValidationEnum.TooManyAttempts;

Но это не совсем правильно ;-) У кого-нибудь есть идея?

Cheers, Andreas

Ответы [ 2 ]

1 голос
/ 09 июля 2019

У вас есть несколько опций в зависимости от того, что вы хотите сделать в функции валидатора и где вы хотите поместить код, управляющий стеком диалога.

Опция 1: вернуть false

Ваша первая возможность вытолкнуть диалоги из стека будет в самой функции валидатора, как я упоминал в комментариях.

if (promptContext.AttemptCount > 3)
{
    var dc = await BotUtil.Dialogs.CreateContextAsync(promptContext.Context, cancellationToken);
    await dc.CancelAllDialogsAsync(cancellationToken);
    return false;
}

Вы были правы, опасаясь этого, потому что это на самом деле может вызвать проблемыесли вы не делаете это правильно.SDK не ожидает, что вы будете манипулировать стеком диалога в функции валидатора, поэтому вам необходимо знать, что происходит, когда функция валидатора возвращается, и действовать соответственно.

Опция 1.1: отправка действия

Вы можете увидеть в исходном коде , что подсказка будет пытаться выполнить повторную репликацию без проверки того, находится ли подсказка в стеке диалога:

if (!dc.Context.Responded)
{
    await OnPromptAsync(dc.Context, state, options, true, cancellationToken).ConfigureAwait(false);
}

Это означает, что даже если вы очистите стек диалога внутри функции валидатора, после этого вы все равно попытаетесь выполнить повторную репликацию, когда вы вернете false.Мы не хотим, чтобы это произошло, потому что диалог уже был отменен, и если бот задает вопрос, на который он не будет принимать ответы, это будет выглядеть плохо и сбить с толку пользователя.Тем не менее, этот исходный код дает подсказку о том, как избежать повторной компоновки.Он будет перепроверен, только если TurnContext.Responded равен false.Вы можете установить его на true, отправив действие.

Опция 1.1.1: отправить сообщение активность

Имеет смысл сообщить пользователю, что он использовал все своипопыток, и если вы отправите пользователю такое сообщение в своей функции валидатора, вам не придется беспокоиться о каких-либо нежелательных автоматических повторных репликах:

await promptContext.Context.SendActivityAsync("Cancelling all dialogs...");

Опция 1.1.2: отправка активности события

Если вы не хотите отображать реальное сообщение пользователю, вы можете отправить невидимое событие, которое не будет отображаться в диалоге.При этом для TurnContext.Responded будет true:

await promptContext.Context.SendActivityAsync(new Activity(ActivityTypes.Event));

Опция 1.2: аннулировать приглашение

Возможно, нам не нужно будет избегать вызова приглашения на его OnPromptAsync, если конкретныйТип приглашения позволяет избежать перепроверки внутри OnPromptAsync.Опять же, посмотрев на исходный код, но на этот раз в TextPrompt.cs , мы можем увидеть, где OnPromptAsync выполняет его перекомпоновку:

if (isRetry && options.RetryPrompt != null)
{
    await turnContext.SendActivityAsync(options.RetryPrompt, cancellationToken).ConfigureAwait(false);
}
else if (options.Prompt != null)
{
    await turnContext.SendActivityAsync(options.Prompt, cancellationToken).ConfigureAwait(false);
}

Так что еслимы не хотим отправлять какие-либо действия пользователю (видимые или иные), мы можем запретить повторное сопоставление текстового приглашения, просто установив для его свойств Prompt и RetryPrompt значение null:

promptContext.Options.Prompt = null;
promptContext.Options.RetryPrompt = null;

Вариант 2: возврат true

Вторая возможность отменить диалоги, когда мы поднимаемся вверх по стеку вызовов из функции валидатора, находится на следующем шаге водопада, как вы упомянули в своем вопросе.Это может быть вашим лучшим вариантом, потому что он наименее хакерский: он не зависит от какого-либо особого понимания внутреннего кода SDK, который может быть изменен.В этом случае вся ваша функция валидатора может быть такой простой:

private Task<bool> ValidateAsync(PromptValidatorContext<string> promptContext, CancellationToken cancellationToken)
{
    if (promptContext.AttemptCount > 3 || IsCorrectPassword(promptContext.Context.Activity.Text))
    {
        // valid user input
        // or continue to next step anyway because of too many attempts
        return Task.FromResult(true);
    }

    // invalid user input
    // when there haven't been too many attempts
    return Task.FromResult(false);
}

Обратите внимание, что мы используем метод с именем IsCorrectPassword, чтобы определить, верен ли пароль.Это важно, потому что эта опция зависит от повторного использования этой функциональности на следующем этапе водопада.Вы упоминали о необходимости сохранять информацию в TurnState, но это не нужно, поскольку все, что нам нужно знать, уже находится в контексте поворота.Проверка основана на тексте действия, поэтому мы можем просто проверить этот же текст снова на следующем шаге.

Опция 2.1: использовать WaterfallStepContext.Context.Activity.Text

Текст, введенный пользователем, будет по-прежнемубыть доступным для вас в WaterfallStepContext.Context.Activity.Text, чтобы ваш следующий шаг водопада мог выглядеть следующим образом:

async (stepContext, cancellationToken) =>
{
    if (IsCorrectPassword(stepContext.Context.Activity.Text))
    {
        return await stepContext.NextAsync(null, cancellationToken);
    }
    else
    {
        await stepContext.Context.SendActivityAsync("Cancelling all dialogs...");
        return await stepContext.CancelAllDialogsAsync(cancellationToken);
    }
},

Опция 2.2: используйте WaterfallStepContext.Result

Контексты шага водопада имеют встроенное свойство Resultэто относится к результату предыдущего шага.В случае текстового приглашения это будет строка, возвращенная этим приглашением.Вы можете использовать его так:

if (IsCorrectPassword((string)stepContext.Result))

Вариант 3: выбросить исключение

Продвигаясь дальше вверх по стеку вызовов, вы можете обрабатывать вещи в обработчике сообщений, который первоначально вызывал DialogContext.ContinueDialogAsync, вызывая исключение в вашей функции валидатора, как CameronL, упомянутый в удаленной части их ответа. Хотя обычно считается плохой практикой использование исключений для запуска преднамеренных путей кода, это очень похоже на то, как работали ограничения повторов в Bot Builder v3, который вы упомянули, желая повторить.

Вариант 3.1: использовать базу Exception тип

Вы можете выбросить только обычное исключение. Чтобы было проще отличить это исключение от других исключений при его обнаружении, вы можете дополнительно включить некоторые метаданные в свойство Source исключения:

if (promptContext.AttemptCount > 3)
{
    throw new Exception("Too many attempts") { Source = ID };
}

Тогда вы можете поймать это так:

try
{
    await dc.ContinueDialogAsync(cancellationToken);
}
catch (Exception ex)
{
    if (ex.Source == TestDialog.ID)
    {
        await turnContext.SendActivityAsync("Cancelling all dialogs...");
        await dc.CancelAllDialogsAsync(cancellationToken);
    }
    else
    {
        throw ex;
    }
}

Опция 3.2: использовать производный тип исключения

Если вы определяете свой собственный тип исключения, вы можете использовать его, чтобы перехватить только это конкретное исключение.

public class TooManyAttemptsException : Exception

Вы можете бросить это так:

throw new TooManyAttemptsException();

Тогда вы можете поймать это так:

try
{
    await dc.ContinueDialogAsync(cancellationToken);
}
catch (TooManyAttemptsException)
{
    await turnContext.SendActivityAsync("Cancelling all dialogs...");
    await dc.CancelAllDialogsAsync(cancellationToken);
}
0 голосов
/ 28 июня 2019

Объект контекста средства проверки подсказки является более конкретным объектом, связанным только с передачей или сбоем средства проверки.

** удален неправильный ответ **

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...