Documentation

Powered by Algolia

Contexts

Just like in real world conversations, in voice scripts some user commands may have meaning only within a context. For example, the user can say: 'What is the temperature now?' To answer this question in the best way, you need to understand the situation to which the user is referring: at least know the user location.

To let you organize dialogs with context-dependent commands, Alan offers contexts. Contexts are useful if you create a multi-step conversation where some commands or parts of the dialog do not make sense as standalone utterances. In this case, you can place such commands or dialog parts in contexts.

Consider the following example:

intent("How are you (doing|)?",
    reply("I'm (fine|great), (and|what about) you?",
          "I'm (fine|good|great) how are you (doing|)?",
          "Fantastic, and you?",
        follow("me too", "same here",
            reply("I'm glad!", "(That's|) great!")),
        follow("(I'm|) $(Q good|fine|fantastic|okay)", "I am $(Q good|fine|fantastic|okay)",
            reply("Glad you are $(Q)")),
        follow("(I'm|I am|) (bad|sad|depressed)",
            reply("Sorry to heard that"))))

Here the ‘me too’ phrase is meaningless if put as a simple intent. That's why it is better presented as a part of the context.

Every voice script can include one or more contexts. Alan implicitly creates the global context, and you can define contexts on your own in the voice script when needed. Each context has a unique ID assigned to it automatically, and a title, either assigned by Alan or created with the optional title() function.

All voice scripts in the project can have a maximum of 50 contexts.

Typically, contexts contain commands that need to be run at a specific step of the dialog. To let the user run commands from the context, it must be activated. After the context is active, voice commands in it become available, and Alan considers them for matching. When the part of the dialog presented as a context is no longer needed, the user can exit the context.

Follows

In contexts, you can use the follow() function to define the expected user input. The follow function is in many respects similar to the intent() function. However, unlike intents, follows are context-dependent. They can only be called when a context containing them is active or the parent intent has been matched.

You can use patterns and slots when defining a follow.

In the example below, follows are nested inside the intent. These follows can be called after the intent is matched.

intent("What('s|) (your|) name?", "Who are you?",
    reply("(My name is|It's) Alan! What is yours?",
        follow("(this is|it's|) $(NAME)", reply("Nice to meet you $(NAME)")),
        follow("I wont tell you", "it's a secret", "none of your business", reply("Ok (never mind|)"))))

Defining contexts

You can define a context in the following way:

let genericContext = context(() => {
    intent('hello', p => {
        p.play('Hi');
    });
    
    follow('how are you doing', p => {
        p.play('Fine, thank you');
    });
});

Activating contexts

Contexts can be activated in two ways:

Activating the context with a matching intent

A voice script can contain several contexts, and intents in these contexts can be matched at any moment of the script execution. When the intent in the context is matched, this context automatically becomes active.

let firstContext = context(() => {
    intent('activate first context', p => {
        p.play('First context is now active');
    });

    follow('what context is it', p => {
        p.play('It is the first context');
    });
});

let secondContext = context(() => {
    intent('activate second context', p => {
        p.play('Second context is now active');
    });

    follow('what context is it', p => {
        p.play('It is the second context');
    });
});

In this example, there are two commands that the user can give:

  • activate first context
  • activate second context

When the user gives the activate second context command, the second context is activated and the follow in the second context becomes available. The follow in the first context still cannot be matched as the first context is inactive.

Activating the context manually

You can use the then() function to activate the context manually.

let confirmOrder = context(() => {
    follow('yes', p => {
        p.play('Your order has been confirmed');
    });
    
    follow('no', p => {
        p.play('Your order has been cancelled');
    });
});

let chooseDish = context(() => {
    follow('get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Do you confirm?`);
        p.then(confirmOrder);
    })
});

intent('I want to order some food', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

In this example, we have two inactive contexts: chooseDish and confirmOrder. These contexts have only follows in them and so can't be activated with a matching intent. To activate them, you need to use the then() function.

When the user gives the first command I want to order some food, the chooseDish context is activated. After the user chooses the dish, the confirmOrder context is activated as well.

In the previous example with a matching intent, the user was able to activate only one context by giving a command. Here you can activate several contexts at once, and all follows in them will be available.

If you need to activate several contexts, it is recommended that you activate one context from another context as shown in this example. Technically, you can use the then() function in one intent several times to activate several contexts. However, this variant is strongly not recommended.

Exiting contexts

Alan builds a recursive context structure. Every newly activated context is bonded to the parent command from which it is activated and to the parent context containing this command.

This means that if you have activated several contexts one after another and then the user gives a command outside of an active context, the active contexts will be deactivated recursively. Let's use the previous example and add the cancel order intent to the global context.

let confirmOrder = context(() => {
    follow('yes', p => {
        p.play('Your order has been confirmed');
    });
    
    follow('no', p => {
        p.play('Your order has been cancelled');
    });
});

let chooseDish = context(() => {
    follow('get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Do you confirm?`);
        p.then(confirmOrder);
    })
});

intent('I want to order some food', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

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

After the user says I want to order some food -> get me a pizza, we will have 3 active contexts:

  • global
  • chooseDish
  • confirmOrder

If the user says cancel order, which is outside of the contexts scope and commands bonded to the I want to order some food intent, the chooseDish and confirmOrder contexts will be deactivated.

You can also exit a context manually by adding the resolve() function to the context. In the example below, after the user says yes, the confirmOrder context will be deactivated immediately.

let confirmOrder = context(() => {
    follow('yes', p => {
        p.play('Your order has been confirmed');
        p.resolve();
    });
    
    follow('no', p => {
        p.play('Your order has been cancelled');
    });
});

let chooseDish = context(() => {
    follow('get me a $(D pizza|burger)', p => {
        p.play(`You have ordered a ${p.D.value}. Do you confirm?`);
        p.then(confirmOrder);
    })
});

intent('I want to order some food', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

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

Using async contexts

Alan fully supports asynchronous JavaScript programming in scripts. For example, some parts of your voice script may require making API calls. If you need to get the result of the API call before you proceed further in the intent, you can use the await operator instead of callbacks. The await operator can also be used if you expect some 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:

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 a yes or no response from the user. Let's use one of our previous examples:

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

let chooseDish = context(() => {
    follow('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, choose another dish that you want');
        }
    })
});

intent('I want to order some food', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

Now when the user chooses a dish, we wait for the yes/no response in the confirmOrder context and, depending on his 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 voice 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 the Alan Studio logs. To do this, open the logs section and select a command belonging to this or that context. In the displayed command statistics, the Context field will show the ID assigned to the context.

If necessary, you can label contexts in the voice script with user-friendly names. The context names are displayed in the Alan 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.

// 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');
    
    follow('$(LOC)', p => {
        p.play({command: "address", address: p.LOC.value});
        p.play({command: "highlight", id: "address"});
    });
});

Locking contexts

Alan provides a user-friendly way to a lock the user in some context. This can be helpful when you expect a specific input from the user somewhere in the dialog flow and don't want this flow to be interrupted by other commands available in your script.

To lock the user in some context, you must use the predefined fallback() function. This function will be active only when its parent context is active and will match any input that is not matched by other functions in this context.

Check the chooseDish context in the example below:

let chooseDish = context(() => {
    follow('get me a $(D pizza|burger)', p => {
        p.play(`Here is your ${p.D.value}`);
        p.resolve();
    });
    
    follow('cancel (order|)', p => {
        p.play('Your order is cancelled');
        p.resolve();
    });
    
    fallback('We are expecting you to choose a dish. Say: get me a pizza or get me a burger. You can also cancel your order.');
});

let chooseDrink = context(() => {
    follow('get me a $(D soda|juice)', p => {
        p.play(`Here is your ${p.D.value}`);
        p.resolve();
    });
    
    follow('cancel (order|)', p => {
        p.play('Your order is cancelled');
        p.resolve();
    });
    
    fallback('We are expecting you to choose a drink. Say: get me a soda or get me a juice. You can also cancel your order.');
});

intent('I want to order some food', p => {
    p.play('I can offer you a pizza or a burger');
    p.then(chooseDish);
});

intent('I want to order some drinks', p => {
    p.play('I can offer you a soda or a juice');
    p.then(chooseDrink);
});

Now if the user enters the chooseDish context by saying I want to order some food, the user will need to finish this step either by ordering a dish or by cancelling his order. The I want to order some drinks command will be unavailable before the context is deactivated because of the fallback() function.

Sharing information between contexts

There are two ways in which you can share information between intent calls.

User Data

You can store the application state in a special userData variable which persists between voice 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.

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 have I ordered", p => {
    p.play(`You have ordered ${p.userData.coffee}`);
});

State

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).

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 have I ordered", 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.

let coffeeContext = context(() => {
    follow("what have I ordered", 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.

let coffeeContext = context(() => {
    follow("what have I ordered", 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});
});

Using onEnter() function

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

let countContext = context(() => {
    onEnter(p => {
        p.state.result = 0;
    });
    
    follow('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);
});