Documentation

Powered by Algolia

Using contexts in the voice script

If you want to create a multi-step dialog where some parts of the conversation can occur only in specific circumstances, you can add contexts to the script. In this tutorial, you will learn how to use contexts, context tools and functions to make the dialog with your voice assistant structured and robust.

We will create a simple True or False game. Alan will say a set of statements, and the user will tell whether they are true or false. For each correct answer, the user will get one point. When the game ends, Alan will report the user score.

What you will learn

  • How to use contexts in the voice script
  • How to create multi-step or branched dialogs for voice assistants with Alan
  • How to use the following context tools and functions:

    • Contexts activated by a matching intent and manually
    • Locking the user in the context
    • Sharing data between contexts
    • Working with the flowchart
    • Debugging contexts

What you will need

To go through this tutorial, make sure the following prerequisites are met:

Step 1: Add intents and follows to the script

First of all, we will add a set of questions to the user. Open the project in the Alan Studio and to the code editor, add the project.data object:

project.data = [{
        question: 'Sandwich is named after a person',
        answer: "true"
    },
    {
        question: 'A baby has more bones than an adult person',
        answer: "true"
    },
    {
        question: 'Gorillas blood type is always B',
        answer: "true"
    },
    {
        question: 'Olympic gold medal is made of silver',
        answer: "true"
    },
    {
        question: 'Pigs can look up in the sky',
        answer: "false"
    }
]

Now let's add simple voice commands that will greet the user and explain the rules. To the code editor, add the commands:

intent('(Hi|), what can I do here?', p => {
    p.play('Hello, this is the True or False game.');
	p.play('To start the game, say: Let\'s play. To hear the rules, say: Tell me the rules.');
});

follow('Tell me the rules', p => {
    p.play('I say a statement, and you say whether it is true or false.');
	p.play('For each correct answer, you get one point.');
	p.play('To start the game, say: Let\'s play.');
});

Here, we are already working with our first context — the global one. The global context is created automatically by Alan. At present, the global context contains two commands defined with different functions:

  • intent: a regular way to define voice commands
  • follow: a function to define context-dependent commands

Let's have a look at our commands. The first one is used for greeting and introducing the user to the game. The second one tells about the game rules. In the real world, you cannot start a conversation with Tell me the rules. This phrase makes sense only if you are within the context — you are about to play the game.

This is why we are using follow for the second command. Alan can match a follow only if the context to which it belongs is active. And to activate the global context, the user must say the first voice command defined with the intent: Hi, what can I do here?

You can test it. In the Debugging Chat, type or say: Tell me the rules. Alan will not be able to match the command. Try the following sequence: Hi, what can I do here? and then: Tell me the rules. Alan will play responses provided in the commands.

Step 2: Add a context activated by a matching intent

Let's create a context for a new step in our dialog. To the script, add the following code:

let startGame = context(() => {
    intent('Let\'s play', p => {
        for (let i = 0; i < project.data.length; i++) {
            let query = project.data[i].question;
            p.play(`${query}. Is it true or false?`);
        }
        p.play(`I've run out of questions!`)
    });
});

This is how we can create a new user-defined context. At this step, Alan plays statements listed in project.data and asks whether they are true or false. After all statements are played, Alan says: I've run out of questions!

This context is activated by a matching intent when the user says: Let's play. In Alan, you can also activate a context manually, with the then() function. Move on to the next step to see how to do it.

Step 3: Add a context activated manually

Our script now simply plays a set of statements to the user. We need to add a new step — let the user say whether the statement is true or false and proceed to the next statement only after the answer is received. To accomplish this, we will add a new context that will capture the user's answer.

  1. Add the following context to the script:

    let getAnswer = context(() => {
    	follow("$(ANS True|False)", p => p.resolve(p.ANS.value.toLowerCase()));
    });

    In this context, we are getting the user answer — True or False, and then exiting the context with the resolve() function. The obtained answer is returned in the p.ANS.value variable.

  2. Let's update the startGame context:

    a. To the intent in the startGame context, add async to tell Alan the context is asynchronous.

    let startGame = context(() => {
    	intent('Let\'s play', async p => {
        ...
    	});
    });

    b. After Alan plays a statement, add the following code:

    let startGame = context(() => {
    	intent('Let\'s play', async p => {
    		for (let i = 0; i < project.data.length; i++) {
    			let query = project.data[i].question;
    			p.play(`${query}. Is it true or false?`);
    
    			// Activating the getAnswer context
    			let result = await p.then(getAnswer);
    
    			// Processing the answer returned by the getAnswer context
    			if (result == project.data[i].answer) {
    				p.play(`You say it is ${result}. Correct.`)
    			} else {
    				p.play(`You say it is ${result}. Incorrect.`)
    			}
    		}
    		p.play(`I've run out of questions!`)
    	});
    });

    Here, we are activating the getAnswer context with the then() function and storing the user's answer to the answer variable. We are then comparing the answer with the answer in project.data and playing a response to the user.

Now, when the user says: Let's play, the startGame context in the script is activated by a matching intent. Alan plays the first statement, asks whether it is true or false and then automatically activates the getAnswer context. This context captures the user input and returns it to the startGame context. Alan tells the result and then passes to the next statement.

You can test it. In the Debugging Chat, say or type: Let's play. When Alan says the first statement, give the answer: True or False. Then go on with other statements.

Step 4: Lock the user in the context

Our getAnswer context is resolved if the user says: True or False. However, the user can say something else, for example: I don't know. In this case, we need to 'lock' the user in the context: that is, stay in the context until the user gives the necessary answer. We can do it with the fallback() function.

To the getAnswer context, add the following line:

let getAnswer = context(() => {
	follow("$(ANS True|False)", p => p.resolve(p.ANS.value));
	
	// Locking the user in the context
	fallback("You have to say True or False");
});

You can test it. In the Debugging Chat, say or type: Let's play. When Alan says the first statement, say: I don't know. Alan will give you the instructions to say True or False.

Step 5: Share data between the contexts

Sometimes you need to share information between contexts. To do this, you can store the necessary data in the following ways:

In this tutorial, we will use the p.userData variable to store the user score. The user will be able to get the final score after the game ends.

  1. In the startGame context, define the p.userData.score variable and then increment it every time the user gives the correct answer:

    let startGame = context(() => {
    	intent('Let\'s play', async p => {
    	
    	// Set the user score
    	p.userData.score = 0;
    	
    		for (let i = 0; i < project.data.length; i++) {
    			let query = project.data[i].question;
    			p.play(`${query}. Is it true or false?`);
    			
    			let result = await p.then(getAnswer);
    			if (result == project.data[i].answer) {
    				p.play(`You say it is ${result}. Correct.`);
    			
    				// Increment the user score
    				p.userData.score++;
    				
    			} else {
    				p.play(`You say it is ${result}. Incorrect.`)
    			}
    		}
    		p.play(`I've run out of questions!`)
    	});
    });
  2. To the global context of the voice script, add the following command:

    intent('Tell me the final score', p => {
    	p.play(`Your score is ${p.userData.score}`);
    });

Now test it: in the Debugging Chat, play the game. When you answer all the questions, say: Tell me the final score.

When you say a command outside of all active user-defined contexts, these contexts are deactivated recursively. However, you still can hear the final score stored in the p.userData.score variable.

Step 6: Exit the game

One more tweak: we need to give the user a possibility to exit the game while it is running.

  1. Let's update our startGame context: let Alan ask if the user wants to hear the next statement and then activate a new context — continueGame:

    let startGame = context(() => {
    	intent('Let\'s play', async p => {
    		p.userData.score = 0;
    		for (let i = 0; i < project.data.length; i++) {
    			let query = project.data[i].question;
    			p.play(`${query}. Is it true or false?`);
    
    			let result = await p.then(getAnswer);
    			if (result == project.data[i].answer) {
    				p.play(`You say it is ${result}. Correct.`);
    				p.userData.score++;
    			} else {
    				p.play(`You say it is ${result}. Incorrect.`)
    			}
    			
    			// Ask if the user wants to proceed
    			p.play(`Do you want to hear the next question?`);
    			// Activate the continueGame context
    			let nextQuest = await p.then(continueGame);
    			if (nextQuest) {
    				p.play(`Here is one more question:`)
    			} else {
    				p.play(`OK, your score is ${p.userData.score}. See you next time.`);
    				return;
    			}
    		}
    		p.play(`I've run out of questions!`);
    	});
    });
  2. And add the continueGame context itself:

    let continueGame = context(() => {
    	follow("(Yes|Of course|I want to go on)", p => p.resolve(true));
    	follow("(No|Stop the game|Finish the game)", p => p.resolve(false));		
    });

Now, after each user's answer, Alan asks if the user wants to proceed. The continueGame context is activated, and the user must give an answer. If the user agrees to continue the game, the next statement is played. If the user wants to stop the game, Alan tells the current score and exits the game.

You can test it: in the Debugging chat, play the game and try exiting after the first statement.

Step 7: View contexts in the flowchart

When we work with contexts, one of the most powerful and helpful tools is the flowchart. The flowchart visualizes the voice script and presents it as a diagram. It allows us to see all the connections and dependencies in the script quickly and easily, and troubleshoot problems, if any.

The flowchart is located at the bottom right of the Alan Studio. Click the expand button to expand the flowchart pane:

As we can see, in our script, there are four contexts: one is global, others are user-defined. We can also see all commands and fallbacks in these contexts and from where this or that context is activated.

Step 8: Troubleshoot contexts with logs

Every context in the voice script is labeled with the global name (the global context) or with numeric IDs (user-defined contexts). To check to which context a voice command belongs, use the Alan Studio logs:

  1. At the bottom of the code editor, expand the logs section.
  2. Make sure the Input filter is on.
  3. Click a user utterance in the logs and check the Context field. Then do the same for other commands.

For better debugging, Alan allows you to label contexts with user-friendly names. If a context is labeled, its name is displayed in the Alan Studio logs instead of the context ID.

To label a context:

  1. Above the commands in the voice script, add the title() function with the context name:

    ...
    title('The global context')
    
    intent('(Hi|), what can I do here?', p => {
    	p.play('Hello, this is the True or False game.');
    	p.play('To start the game, say: Let\'s play. To hear the rules, say: Tell me the rules.');
    });
    ...
  2. To every user-defined context, add a label in the following way:

    let getAnswer = context(() => {
    
    	title('Get answer context')
    	
    	follow("$(ANS True|False)", p => p.resolve(p.ANS.value.toLowerCase()));
    	fallback("You have to say True or False");
    });
  3. Test the game in the Debugging Chat.
  4. Open the logs section and check the Context field for each command.

See also

Contexts