Configure Aura Bot in bypass mode

Orderly steps for the configuration of Aura Bot in bypass mode

Introduction

The main functionality of bypass mode, is that once we are in this mode within a conversation, any input message to aura-bot will be directly sent to the external service. Likewise, any message from the external service will be shown to the user without going through the bot recognition system.

In section bypass-mode-middleware you can find detailed information regarding the middleware that manages the bypass mode and the bypass mode model.

How to configure Aura Bot in bypass mode

The configuration of aura-bot in bypass mode implies three steps that are shown below.

Initialize bypass mode

To use bypass mode, the dialog must initialize the bypass object.

Params Bypass.initialize method:

Property Description
BypassState Current bypass state
context Current context of dialog
duration Bypass life time in minutes
initialData Initial data of bypass
payloadName Name of the property in the channelData.payload used to send data to bypass.ex: 'handover'.
For asynchronous APIs, it must be asyncCallback.
closeString Comma-separated string or array of string with the words that directly close the bypass
    const bypass: Bypass<BypassModelData> = await Bypass.initialize(
            stepContext.context,
            this.timeout,
            {},
            'handover',
            BypassState.Init,
            getLiteral(stepContext?.context)('factotum:bypass.close.words'));

Once the bypass mode is initialized, all user’s messages will be redirected to the dialog. In bypass mode, when the dialog finishes all the actions of the use case logic, the dialog must be closed.

    return await stepContext.endDialog();

Execute bypass mode

Bypass information can be loaded into the dialog by calling the Bypass.loadBypass method.

    const bypass = await Bypass.loadBypass(stepContext.context);

From its initialization, the dialog should monitor the status of the bypass that can be one of the following:

  • BypassState.Off
  • BypassState.Init
  • BypassState.Bypass
  • BypassState.Closed
    const bypass = await Bypass.loadBypass(stepContext.context);
    switch (bypass.state) {
        case BypassState.Off:
        case BypassState.Init: {
            .....
            break;
        }
        case BypassState.Bypass: {
            ....
            break;
        }
        case BypassState.Closed: {
            ....
            break;
        }
    }

Bypass data field is used to exchange information between bypass interations, it is an any object. To update the information in the bypass object, the bypass object is modified to invoke updateBypass.

    const bypass = await Bypass.loadBypass(stepContext.context);
    bypass.data.value = bypass.data.value + stepContext.context.activity.text + ' ';
    await bypass.updateBypass(stepContext.context, false);

Close bypass mode

At any time the dialog can close the bypass mode by using the Bypass.closeBypass method.

    await Bypass.closeBypass(stepContext.context);

The bypass may also be closed because of its expiration or because the user has said any of the closing words. In these cases, when the control returns to the dialog, the state of the bypass will change to BypassState.Closed and the closeReason field will indicate the reason for the closing.

Example of dialog with Aura Bot in bypass mode

    export default class AuraBotTestBypass extends ComponentDialog {
    /**
     * Dialog id for the base Aura Bot app dialog.
     */
    public static readonly id: string = 'test-bypass';
    public TTL_BYPASS_MIN: number = 0.25; // 15 seconds
    /**
     * logger
     */
    public readonly logger = new AuraLogger.AuraBusEmitter('AuraTestBypass');
    public readonly autoRegister: boolean = true;
    protected configuration: Configuration;
    /**
     * Constructor for Bypass Handover.
     *
     * @param {Configuration} configuration The configuration joi schema.
     */
    constructor(configuration: Configuration) {
        super(AuraBotTestBypass.id);
        super.initialDialogId = AuraBotTestBypass.id;
        this.configuration = configuration;
        super.addDialog(new WaterfallDialog(AuraBotTestBypass.id, [
            this.handoverStart.bind(this),
            this.handoverContinue.bind(this)
        ]));
    }
    /**
     * Check Bypass Status
     * INIT: Start Bypass Mode and create Bypass model into userData.
     * BYPASS: Execute handoverContinue step dialog.
     * CLOSE: Remove Bypass Model from userData and close Dialog.
     *
     * @param {WaterfallStepContext} stepContext The current step context.
     */
    private async handoverStart(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
        const corr = ContextUtils.getCorrelator(stepContext.context);
        try {
            let bypass = await Bypass.loadBypass(stepContext.context);
            switch (bypass.state) {
                case BypassState.Off:
                case BypassState.Init:
                    bypass = await Bypass.initialize(stepContext.context, this.TTL_BYPASS_MIN, { value: '' }, 'test', BypassState.Bypass, ['exit','disconnect','disable']);
                    this.logger.info({ msg: `Bypass for: ${bypass.intent.intent} initialized`, corr });
                    await stepContext.context.sendActivity('Welcome to the CONCATENATOR, type "exit" to finish.');
                    return await stepContext.endDialog();
                case BypassState.Bypass:
                    this.logger.info({ msg: `Bypass for: ${bypass.intent.intent} continue`, corr });
                    return await stepContext.next(bypass);
                case BypassState.Closed:
                    await Bypass.closeBypass(stepContext.context);
                    this.logger.info({ msg: `Bypass for: ${bypass.intent.intent} closed`, corr });
                    if (bypass.closeReason === BypassCloseReason.BypassExpired) {
                        await stepContext.context.sendActivity('The CONCATENATOR has expired!');
                    }
                    await stepContext.context.sendActivity('The final result was: [ ' + bypass.data.value + ']');
                    return await stepContext.endDialog();
            }
        } catch (reason) {
            this.logger.error({
                corr,
                msg: 'Error handover bypass',
                error: reason.message,
                stck: reason.stack
            });
            await stepContext.context.sendActivity(reason.message);
            return await stepContext.endDialog();
        }
    }
    /**
     * Executes the logic for Bypass
     *
     * @param {WaterfallStepContext} stepContext The current step context.
     */
    private async handoverContinue(stepContext: WaterfallStepContext ): Promise<DialogTurnResult> {
        const bypass = stepContext.result;
        bypass.data.value = bypass.data.value + stepContext.context.activity.text + ' ';
        await bypass.updateBypass(stepContext.context, false);
        await stepContext.context.sendActivity(bypass.data.value);
        return await stepContext.endDialog();
    }
}

How to use prompts in bypass mode

Prompts can be used within a dialog working in bypass mode.

    public DEFAULT_RETRIES: number = 2;
    public promptsNames = {
        COMMAND_CHOICE_PROMPT: 'command-choice-prompt'
    };
    constructor(configuration: Configuration) {
        ....
        // adds the prompt dialog to the Dialogs Set
        this.customPrompt = new ChoicePrompt(this.promptsNames.COMMAND_CHOICE_PROMPT,
            PromptUtils.getRetriesValidator(this.DEFAULT_RETRIES));
        ...
    }
  • The prompt will be instantiated, when the bypass mode is initialized.
    this.customPrompt = new ChoicePrompt(this.promptsNames.COMMAND_CHOICE_PROMPT, PromptUtils.getRetriesValidator(retries));
    // To change retries we need replace original dialog for prompt
    (this.dialogs as any).dialogs['command-choice-prompt'] = this.customPrompt;
    return await Bypass.initialize(
        context,
        parseFloat(timeoutMin),
        { value: '' },
        'test',
        BypassState.Bypass,
        closeWords
    );
  • The prompt can be sent to the user and and the returned value can be collected in the dialog.
    private async sendPrompt(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
        const choicesText: Choice[] = [
            { value: 'id-0001', action: { title: 'Option 1 (id-0001)', type: '', value: '' }, synonyms: [] },
            { value: 'id-0002', action: { title: 'Option 2 (id-0002)', type: '', value: '' }, synonyms: [] },
            { value: 'id-0003', action: { title: 'Option 3 (id-0003)', type: '', value: '' }, synonyms: [] },
            { value: 'id-0004', action: { title: 'Option 4 (id-0004)', type: '', value: '' }, synonyms: [] },
            { value: 'id-0005', action: { title: 'Option 5 (id-0005)', type: '', value: '' }, synonyms: [] }
        ];
        const promptOptions: PromptOptions = {
            prompt: 'Hello, select one option: write the number, cardinal, ordinal or text',
            choices: ChoiceFactory.toChoices(choicesText)
        };
        return await stepContext.prompt(this.promptsNames.COMMAND_CHOICE_PROMPT, promptOptions);
    }
    private async fallbackStep(stepContext: WaterfallStepContext): Promise<DialogTurnResult> {
        await stepContext.context.sendActivity(`Option selected: ${stepContext.result.value}`);
        return await stepContext.endDialog();
    }