Contexts

Just like in real world conversations, in dialog scripts some user commands may have meaning only within a context. For example, the user can say: What about tomorrow? To answer this question in the best way, you need to understand the situation to which the user is referring.

To let you organize dialogs with such commands, Alan AI provides contexts. Contexts are useful if you create a multi-stage conversation where some commands or dialog branches do not make sense as standalone utterances. In this case, you can place them in contexts.

Consider the following script:

Here the What about tomorrow? and And the humidity? questions are meaningless if put as simple intents. That’s why they should be presented as a part of a context.

Every dialog script has one or more contexts. Alan AI implicitly creates the global context that wraps commands added directly to the script, and you can define contexts on your own whenever you need them. Every context has a unique ID that is assigned to it automatically, and a title, either assigned by Alan AI or created with the title() function.

Typically, contexts contain commands that are needed at a specific stage of the dialog. To let the user give commands from a context, this context must be activated. Once the context is active, voice and text commands in it become active, and Alan AI considers them for matching. When the part of the dialog presented as a context is no longer needed, the user exits the context, and the context is deactivated.

Warning

All dialog scripts added to the project can have a maximum of 50 contexts.

Defining contexts

You can define a context in the dialog script in the following way:

Dialog script
let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`Your order is: ${p.D.value}.`);
    })
});

Activating contexts

A context is activated with the then() function added to an intent in the dialog script. Such an intent becomes the parent intent for the context. The context activated from the parent intent is bound to it.

When the parent intent is matched, the context is activated, and all commands in the context become available for matching.

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`Your order is: ${p.D.value}. Is it correct?`);
        p.then(confirmOrder);
    })
});

let confirmOrder = context(() => {
    intent('Yes', p => {
        p.play('Your order has been confirmed');
    });

    intent('No', p => {
        p.play('Your order has been cancelled');
    });
});

In the example above, we have two inactive contexts: chooseDish and confirmOrder. The contexts contain commands that are required at specific stages of the dialog:

  1. When the user places the order

  2. When the user confirms the order

To activate these contexts, we use the then() function inside intents. As soon as the user asks: What do you have?, Alan AI activates the chooseDish context. After the user chooses the dish, Alan AI activates the confirmOrder context.

Note

If you need to activate several contexts in the dialog, you should activate one context from another context as shown in the example above. Technically, you can use the then() function in one intent several times to activate several contexts. However, such a conversation flow is strongly not recommended.

Exiting contexts

When you activate several contexts, Alan AI maintains a recursive context structure. Every newly activated context is bound to the parent intent from which it is activated, and to the parent context containing this intent.

If the user gives a command outside an active context, this context is deactivated. In case several contexts are activated one after another and the user gives a command outside the active contexts, these contexts are deactivated in a recursive manner.

Let’s use the previous example and add the Cancel the order intent to the global context.

Dialog script
 intent('Cancel the order', p => {
     p.play('Your order has been cancelled');
 });

After the user says: What do you have? > Get me a pizza, we have 3 active contexts:

  • global

  • chooseDish

  • confirmOrder

Now, if the user says: Cancel the order, which is outside the user-defined contexts scope, the chooseDish and confirmOrder contexts will be deactivated.

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

intent('Cancel the order', p => {
    p.play('Your order has been cancelled');
});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`Your order is: ${p.D.value}. Is it correct?`);
        p.then(confirmOrder);
    })
});

let confirmOrder = context(() => {
    intent('Yes', p => {
        p.play('Your order has been confirmed');
    });

    intent('No', p => {
        p.play('Your order has been cancelled');
    });
});

You can also let the user exit the context by deactivating it manually. To do this, you need to add the resolve() function to the context. In the example below, after the user says Yes, the confirmOrder context is deactivated immediately.

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

intent('Cancel the order', p => {
    p.play('Your order has been cancelled');
});

let confirmOrder = context(() => {
    intent('Yes', p => {
        p.play('Your order has been confirmed');
        p.resolve();
    });

    intent('No', p => {
        p.play('Your order has been cancelled');
    });
});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Is it correct?`);
        p.then(confirmOrder);
    })
});

Resetting contexts

In some cases, you need to provide the user with an ability to safely restart the dialog. For example, the user may fail to say an expected phrase, and as a result, one or more contexts will remain hanging.

To deactivate all active contexts at once, use the resetContext() function. When this function is called, the user exits all contexts that have been activated by the moment, and can only give the voice and text commands available in the global context.

In the example below, all current contexts are deactivated when the user says: Cancel the order. To start the dialog again, the user can only say: What do you have?

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

let confirmOrder = context(() => {
    intent('Yes', p => {
        p.play('Your order has been confirmed');
        p.resolve();
    });

    intent('No', p => {
        p.play('Your order has been cancelled');
        p.resolve();
    });

    // Resetting all current contexts
    intent('Cancel the order', p => {
        p.play('Your order has been cancelled');
        p.play('To start again, say what do you have?');
        p.resetContext();
    });
});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Is it correct?`);
        p.then(confirmOrder);
    })
});

The resetContext() function can also be used if you need to forward the user to a specific dialog branch. To do this, after resetting all current contexts, use then() to activate the context in which the user must be placed.

In the example below, when the getOrder() project API method is called, all current contexts are deactivated first and the chooseDish context becomes active, allowing the user to place an order.

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

let confirmOrder = context(() => {
    intent('Yes', p => {
        p.play('Your order has been confirmed');
        p.resolve();
    });

    intent('No', p => {
        p.play('Your order has been cancelled');
        p.resolve();
    });

});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Is it correct?`);
        p.then(confirmOrder);
    })
});

// Resetting all current contexts and forwarding the user to the chooseDish context
projectAPI.getOrder = (p, param, callback) => {
    p.play('You need to make an order, please choose something');
    p.resetContext().then(chooseDish);
}

Note

The resetContext() function does not wipe the data currently stored in the p.userData object. For more details, see userData.

Using commands in contexts

You can place the following types of commands in contexts:

Intents in contexts

Intents in contexts perform their regular functions — they let you fulfil user’s requests and answer users’ questions. However, such intents are context-dependent: they remain inactive until the context to which they belong is activated.

Dialog script
intent('What is the weather in Boston?', p => {
    p.play('45 °F, light cloud and a fresh breeze');
    p.then(weatherDetails);
});

let weatherDetails = context(() => {
    intent('What about tomorrow?', p => {
        p.play('42 °F, light rain and a moderate breeze');
    });

    intent('And the humidity?', p => {
        p.play('92%');
    });
});

In the example above, the What about tomorrow? and And the humidity? intents become active only after the parent intent to which the context is bound is matched — the user asks: What is the weather in Boston?

The intents in the context remain active until the context is deactivated. For details, see Exiting contexts.

noContext intents

In some cases, you may need to add voice and text commands that must not affect the user’s path in the conversation. For example, you can provide the user with the ability to ask general questions like: What information can I get? or What phrases can I use here? at any moment of the dialog, without deactivating the current context.

To define such commands, you can use noContext intents. When Alan AI matches such an intent, it fulfills the request defined by it, but does not switch to the context to which this intent belongs. As a result, the user remains in the same dialog branch as before, and the commands from this dialog branch (context) are still available to the user.

You can use noContext intents in the global context and user-defined contexts. To define a noContext intent, wrap the intent in the noContext() function:

Dialog script
noContext(() => {
    intent('What weather details can I get?',
    reply('Any details like temperature, humidity for any location'));
});

Or you can pass the noctx parameter to such an intent:

Dialog script
intent(noctx, 'What weather details can I get?',
    reply('Any details like temperature, humidity for any location')
);

In the example below, when the user asks about the weather in Boston, the weatherDetails context will become active. All context-dependent commands in this context will be available for matching.

However, the user can ask What weather details can I get? at any time later. Alan AI will answer this question, but will not switch back to the global context since the What weather details can I get? intent has the noctx parameter. The weatherDetails context will remain active, and the user will have an ability to go on with follow-up questions like: What about tomorrow? and And the humidity?

Dialog script
intent(noctx, 'What weather details can I get?',
    reply ('Any details like temperature, humidity for any location');
);

intent('What is the weather in Boston?', p => {
    p.play('45 °F, light cloud and a fresh breeze');
    p.then(weatherDetails);
});

let weatherDetails = context(() => {
    intent('What about tomorrow?', p => {
        p.play('42 °F, light rain and a moderate breeze');
    });

    intent('And the humidity?', p => {
        p.play('92%');
    });
});

Fallbacks in contexts

The fallback() function helps you handle unexpected user’s utterances. This command is triggered if Alan AI does not understand what the user is saying and cannot match the user’s utterance to any command in the context. You can add a fallback to the context to prompt the user for a specific type of input or fail gracefully.

Fallbacks can be used in user-defined and global contexts. For details, see Error handling and re-prompts.

Using async contexts

Alan AI fully supports asynchronous JavaScript programming in dialog scripts. For example, some parts of your dialog script may require making API calls. If you need to get the result of the API call before you proceed further in the voice or text command, you can use the await operator instead of callbacks. The await operator can also be used if you expect an additional input from the user.

In the example below, the arrow function is defined as async and Promise.resolve is used in the return statement:

Dialog script
intent("What is my location?", async p => {
    let addr = await getLocation(p);
    if (addr) {
        p.play(`You are at ${addr}`);
    }
    else {
        p.play("I can't understand where you are");
    }
});

function getLocation(p) {
    let address = "test address"; //Here will be some api call
    if (address) {
        return Promise.resolve(address);
    }
    else {
        return Promise.resolve();
    }
}

Using await stops the script execution until the function returns some result through resolve. If you do not use await here, your script will not wait for the address from API, and the p.play('You are at ${addr}') response will never be given.

Using await and resolve() with contexts can help you in some cases, for example, when you want to receive either the Yes or No response from the user. Let’s use one of our previous examples:

Dialog script
intent('What do you have?', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

let confirmOrder = context(() => {
    intent('$(A yes|no)', p => {
        return p.resolve(p.A.value);
    });
});

let chooseDish = context(() => {
    intent('Get me a $(D pizza|burger)', async p => {
        p.play(`You have ordered a ${p.D.value}. Do you confirm?`);
        let answer = await p.then(confirmOrder);
        if (answer === "yes") {
            p.play('Your order has been confirmed');
            p.resolve();
        } else {
            p.play('OK, please choose something else');
        }
    })
});

Now, when the user chooses a dish, Alan waits for the Yes/No response in the confirmOrder context and, depending on the response, we either resolve the chooseDish context (as the order has been finished and confirmed) or we leave it active, allowing the user to choose another dish.

Labeling contexts

In the dialog script, every context gets a unique identifier:

  • The global context is identified with the global label.

  • User-defined contexts are identified with numeric IDs.

You can check the context ID in Alan AI Studio logs. To do this, at the bottom of the code editor, open the logs section, make sure the Input filter is on and click the necessary command line. In the displayed pane, the Context field will display the ID assigned to the context.

If necessary, you can label contexts in the dialog script with user-friendly names. The context names are displayed in Alan AI Studio logs instead of numeric IDs, which allows for better debugging. To label a context, add the title() function to it. You can label both user-defined and global contexts.

Dialog script
// Labeling the global context
title('Main context');

intent('(Set|Change|Replace) the (delivery|) address',
    p => {
        p.play('What is the delivery address?');
        p.then(checkout);
    });

let checkout = context(() => {

    // Labeling a user-defined context
    title('Delivery address context');

    intent('$(LOC)', p => {
        p.play({
            command: "address",
            address: p.LOC.value
        });
        p.play({
            command: "highlight",
            id: "address"
         );
    });
});

Sharing information between contexts

To share information between intent calls, you can use the following means:

userData object

You can store the application state in a special userData variable that persists between dialog sessions. This way of data sharing can be used to store global application variables that must be accessible from any context of any project script and must be available during all user interactions.

Dialog script
intent('I want to order $(R latte|black coffee)', p => {
    p.userData.coffee = p.R.value;
    p.play(`Sure, wait a bit I'll get you some ${p.R.value}`);
});

intent('What is in the cart?', p => {
    p.play(`You have ordered ${p.userData.coffee}`);
});

state object

Each context contains a special object named state. state can be used as a dictionary, and you can put there anything you want. Everything you put in the state will be saved between each intent calls (activation).

Dialog script
intent('I want to order $(R latte|black coffee)', p => {
    p.state.coffee = p.R.value;
    p.play(`Sure, wait a bit I'll get you some ${p.R.value}`);
});

intent('What is in the cart?', p => {
    p.play(`You have ordered ${p.state.coffee}`);
});

state can be passed or overwritten when a new context is activated using the then() function. The example below shows how to pass the state of the current context to a new one on activation.

Dialog script
let coffeeContext = context(() => {
    intent('What is in the cart?', p => {
        p.play(`You have ordered ${p.state.coffee}`);
    });
});

intent('I want to order $(R latte|black coffee)', p => {
    p.state.coffee = p.R.value;
    p.play(`Sure, wait a bit I'll get you some ${p.R.value}`);
    p.then(coffeeContext, {state: p.state});
});

You can also create a new state object and pass it to the context.

Dialog script
let coffeeContext = context(() => {
    intent('What is in the cart?', p => {
        p.play(`You have ordered ${p.state.coffee}`);
    });
});

intent('I want to order $(R latte|black coffee)', p => {
    let state = {coffee:p.R.value};
    p.play(`Sure, wait a bit I'll get you some ${p.R.value}`);
    p.then(coffeeContext, {state: state});
});

Note

For more details on userData and state objects, see Predefined script objects.

resolve() function

If necessary, you can pass values between contexts with the resolve() function. In the example below, the user’s rating is captured with the userRating context and passed to the global context:

Dialog script
let userRating = context(() => {
    intent(`$(NUMBER)`, async p => p.resolve(p.NUMBER.value))
});

intent('I want to take survey', async p => {
    p.play('Please rate the product on the scale of 1 to 5');
    let rating = await p.then(userRating);
    p.play(`Thank you for sharing your opinion. Your rating is: ${rating}`);
});

Using onEnter() function

onEnter() is a special predefined function activated each time the script enters the context. Take a look at the following example:

Dialog script
let countContext = context(() => {
    onEnter(p => {
        p.state.result = 0;
    });

    intent('Yes', p => {
        p.state.result += 1;
        p.play(p.state.result.toString());
    });
});

intent("Count how many times I've said yes", p =>{
    p.play("Sure, Let's go");
    p.then(countContext);
});