Documentation

Powered by Algolia

How-Tos

Here you can find answers to the most common questions that you may have when building a voice assistant with Alan.

How to send data to a client application

To send data to a client application, use the play() function. In the function, define the JSON object of the command to be sent:

intent("Change color to $(C red|green|blue)", p => {
    p.play({command: "setColor", color: p.C});
});

In the client application, you need handle commands received from the voice script. For details, see the Sending commands and receiving data from an app tutorial or refer to a platform-specific section in Client API.

How to capture the entire user input

If you need to get the entire utterance from the user input, you can use a greedy RegEx syntax in a slot, for example: "$(I* .+)".

Consider the following example: you need to get the user feedback and send this information to your app. You can add the following command to the voice script:

intent("I want to give feedback", async p => {
    p.play("Thank you! What do you think about our product?");
    const feedback = await p.then(userInput);
    p.play({command: "feedback", value: feedback});
    p.play(`Your feedback is: ${feedback}`);
});

const userInput = context(() => {
    intent("$(I* .+)", p => p.resolve(p.I));
})

Here, after the user says: I want to give feedback, Alan offers the user to share it, activates the userInput context and stops the script execution until the feedback is received. The user utterance (any type of it) is captured by "$(I* .+)" added to the intent in the context. Alan exits it the context, sends the command with the feedback information to the app and plays the feedback to the user.

How to create slots with item names from arrays

When designing a dialog for a voice assistant, you may want to let the user name items listed in an array when a voice command is given. To achieve this, you can create a slot with item names and use this slot in the command pattern. For example:

const menuItems = [
    {name: "Cheese Burger", price: 8.99},
    {name: "Double Cheese Burger", price: 12.00},
    {name: "Bacon Burger", price: 11.50},
    {name: "Hawaian Burger", price: 9.99},
    {name: "Mexican Spicy Burger", price: 12.50},
];

// Creates a list of items to be used in a slot: Cheese Burger~Cheese Burger|Double Cheese Burger~Double Cheese Burger|Bacon Burger~Bacon Burger|Hawaian Burger~Hawaian Burger|Mexican Spicy Burger~Mexican Spicy Burger
const menuItemsPattern = menuItems.map(item => `${item.name}~${item.name}`).join('|');

intent(
    `How much (is|does) (a|the|) $(MENU_ITEM~ ${menuItemsPattern}) (cost|)`,
    `What is the (price|cost) (on|for|of) (a|the|) $(MENU_ITEM~ ${menuItemsPattern})`,
    `How much (is|does) (a|the|) $(UNAVAILABLE_ITEM* .*) (cost|)`,
    `What is the (price|cost) (on|for|of) (a|the|) $(UNAVAILABLE_ITEM* .*)`,
    p => {
        if (p.UNAVAILABLE_ITEM) {
            p.play(`${p.UNAVAILABLE_ITEM.value} is unavailable`);
            return;
        } else {
            const requestedItem = p.MENU_ITEM.label.toLowerCase();
            const itemOnMenu = menuItems.find(m => m.name.toLowerCase().includes(requestedItem));
            p.play(`${requestedItem} costs $${itemOnMenu.price}`);
        }
    }
);

In the example above, we are using a slot with fuzzy matching to capture the item name. Each item name in the slot is accompanied with the label. Now, thanks to fuzzy matching, Alan can capture different variants of item names, even if they are close but not exact:

  • How much does Double Cheese cost?
  • How much does Double Cheese Burger cost?

When such a pattern is matched, the .value field of the MENU_ITEM slot will contain the user input, and the .label field will contain the item name provided in the label. In this voice command, p.MENU_ITEM.label is used to tell about the item price.

To capture any name of the item that is not on the menu, we are using RegEx for the UNAVAILABLE_ITEM slot.

How to run a query with words from the user input

Consider the situation: you need to make an API call to an external source, and a part of the user utterance must be sent as a query in this call. You can use a greedy RegEx syntax to capture the user input and then pass this data as a query value.

Here is an example of a simple translation script that translates the user input to German and sends a command with the translated string to the app:

const SERVICE_URL = "https://api.mymemory.translated.net/get?q=";
intent(`Translate $(SOURCE* (.*)) (to German|)`, p => {
    const request_url = `${SERVICE_URL}?q=${p.SOURCE}&langpair=en|de`;
    api.axios.get(request_url)
        .then((response) => {
            const translation = response.data.matches[0].translation.replace(/^[^-]+ = /, '');
            p.play({
                command: "translated",
                value: translation,
            });
            p.play('$(SOURCE) translated to German would be');
            p.play(voice('de'), translation);
            console.log('** Input:', p.SOURCE.value);
            console.log('** Translation:', translation);
        })
        .catch((error) => {
            console.log(error);
            p.play(`Could not get translation`);
        });
});

We are using the $(SOURCE* (.*)) slot with the greedy RegEx syntax to capture a part of the user utterance. The captured phrase is then passed as a query in the API call to the external translation service. When the response is received, the script does the following:

  1. Retrieves the value of the translation key for the best match in the response.
  2. Sends the translated string in a command to the app.
  3. Plays the translation to the user. For playback, the voice('de') parameter is used. This parameter defines the language-specific voice. As a result, the translation is played naturally, with the German accent.
  4. Writes the user input and translation to Alan Studio logs.

How to use predefined slots

If you need to capture specific values from the user input, for example, dates or numbers, you can add predefined slots to voice commands:

intent(`I have a meeting with $(NAME) from $(ORG) $(DATE) (at|) $(TIME) (in|at|near|) $(LOC)`, p => {
    p.play(`Adding to your schedule: Meeting ${p.NAME} from ${p.ORG} ${p.DATE} at ${p.TIME} in the ${p.LOC}`)
})

How to get the right answer from the user

In some cases, the user's answer may be not precise. As a result, you will not be able to get from the user input the data you need for the subsequent dialog flow. To get the necessary information, you can use an async context in the voice script.

Consider the following example: you need to get the coffee type from the user input, but instead the user just says: I want a coffee. You can add the following command to the voice script:

intent("I want a coffee", async p => {
    p.play("What coffee do you prefer?");
    const coffee = await p.then(whatCoffee);
    p.play(`Your ${coffee} coffee will be ready in a moment`);
});

const whatCoffee = context(() => {
    intent("$(COFFEE Black|Americano|Latte|Cappuccino|Expresso|Macchiato)", p => p.resolve(p.COFFEE));
})

Here, once you receive an answer that should be more specific, Alan plays: What coffee do you prefer?, activates the whatCoffee context and stops the script execution until the user gives the necessary answer. If the user answer matches one of the values listed in the COFFEE slot, Alan exits the context, and the coffee type is returned. Alan then plays: Your ${coffee} coffee will be ready in a moment with the coffee type obtained.

How to handle unexpected voice commands from the user

The user may give an answer of the type inappropriate for you. In this case, you can lock the user in the context with the fallback() function until the necessary user input is received.

Consider the following example: a voice script for a simple math checker offers the user to add two random numbers.

intent("Check my math", async p => {
    const a = Math.floor(Math.random()*10)
    const b = Math.floor(Math.random()*10)
    p.play(`How much ${a} plus ${b}?`)
    const sum = await p.then(getNumber)
    p.play(`Your answer ${sum} is ${a+b==sum? 'correct': 'incorrect'}`)
});

const getNumber = context(() => {
    intent("$(NUMBER)", p => p.resolve(p.NUMBER.number))

    fallback("You have to say a number")
})

Here, after Alan asks a question, it activates the getNumber context and stops the script execution until the answer of the appropriate type is received. The answer must be captured by the NUMBER predefined slot: in this case, Alan exits it the context, and the user answer is returned. If the answer is of any other type, the user remains in the context. To instruct the user on the rules, Alan plays: You have to say a number.

How to allow adding comments with voice

If you want to let users add comments with voice, you can use the following command:

let commentContext = context(() => {
    intent(`$(I* (.+))`, async p => p.resolve(p.I))
});

intent(`(Add a comment|Comment|Add comment)`, async p=> {
         p.play("Okay, what comment would you like to add?");
         let comment = await p.then(commentContext);
         p.play(`Your comment is: ${comment}`);
});

Here, after the user says: Add a comment, Alan plays: Okay, what comment would you like to add?, activates the commentContext context and stops the script execution until a comment is received. The comment is captured with the $(I* (.+)) RegEx expression added to the intent in the context. The comment is then played to the user.

How to iterate over a list of objects (back and forward)

You may need to add a voice command that will allow the user to iterate over a list of objects. This can be the case, for example, if you are building a voice assistant for a shopping or ordering app and want the user to pick something from the products list.

Consider the following example: you want your voice assistant to name all available fruits, allow the user to pick some and save them to the user's favorites list.

let fruits = ["apple", "orange", "lemon", "mango", "banana", "avocado", "kiwi"]

intent("(I want to|) (select|choose) fruits", async p => {
    p.play("Iterate over the list of fruits and select your favorite one. To select fruit say 'select', say 'forward' or 'back' to traverse over the list");
    p.play("Say 'finish' to complete your choice")
    p.userData.favorite = []
    p.play(`We have ${fruits.length} fruit in our list. First is ${fruits[0]}`)
    await p.then(selectFruits, {state: { index : 0}})
    if (p.userData.favorite.length > 0) {
        p.play(`Your favorite fruits are: ${p.userData.favorite.join(", ")}`)
    } else {
        p.play(`You have not chosen any fruit`)
    }
});

const selectFruits = context(() => {
    intent("(select|choice)",  p => {
        let selected = false
        if(!p.userData.favorite.includes(fruits[p.state.index])) {
            p.userData.favorite.push(fruits[p.state.index])
        }
        p.play(fruits[p.state.index] + " is selected")
        p.play(p.userData.favorite)
    })

    intent("(repeat|say again)", p => playFruit(p, p.state.index))

    intent("(forward|next)", p => {
        if(p.state.index < fruits.length-1) {
            p.state.index++;
            playFruit(p, p.state.index)
        }
        else {
            p.play("It's the last fruit in the list")
        }
    })

    intent("(back|previous)", p => {
        if(p.state.index > 0) {
            p.state.index--;
            playFruit(p, p.state.index)
        }
        else {
            p.play("It's the first fruit in the list")
        }
    })

    intent("(finish|stop|it's all)", p => p.resolve())

    fallback("Say select to choose fruit, say forward or back to navigate through the list or say 'finish' to return")
})

const playFruit = (p, index) => {
    p.play(`Fruit number ${p.state.index+1} is ${fruits[p.state.index]}`)
}

Here, the list of fruits is first saved to the fruits array. When the user says: I want fruits, Alan gives some instructions on how to pick fruits, names the first fruit in the list and activates the selectFruit context. The voice script execution stops until the user makes his or her choice and exits the selectFruit context by saying: Finish|Stop|It's all.

In the selectFruit context, the user can select the named fruit, go forward or back with voice commands and tell Alan to stop iterating over the list. In the latter case, Alan exits the context, and we get back to executing the script in the initial voice command.

The context also has the fallback() function to lock the user if the necessary answer is not given.

To iterate over the list of fruits, the following Alan objects and variables are added to the script:

  • userData: this variable can be used to store data that can be accessed between contexts. In this example, the user choice is saved to the p.userData.favorites array in the selectFruit context. The resulting list is played to the user after the user exits the context.
  • state: this is a special object that can be used as a 'dictionary'. In this example, the state object stores the index of the current element in the fruits array. When the context is activated, the index is set to 0, and the state object is passed to the context. With each new step forward or backward in the context, the index is incremented or decremented.

How to sort items by price, name or category

If you are working with the item list, it is always a good idea provide the user with the ability to sort items by price, name or category. You can do it in the following way:

const menuItems = [
    {name: "Cheese Burger", price: 8.99, category: 'burger'},
    {name: "Double Cheese Burger", price: 12.00, category: 'burger'},
    {name: "Bacon Burger", price: 11.50, category: 'burger'},
    {name: "Hawaian Burger", price: 9.99, category: 'burger'},
    {name: "Mexican Spicy Burger", price: 12.50, category: 'burger'},
    {name: "Ceaser Salad", price: 12.50, category: 'salad'},
    {name: "Garden Salad", price: 9.99, category: 'salad'}
];

intent(
    '(order|sort) by $(ORDER_BY name|price|category)',
    p => {
        const orderBy = p.ORDER_BY.value;
        let orderedMenuItems = menuItems;
        switch(orderBy){
            case 'name':
                orderedMenuItems = menuItems.sort((p1, p2) => p1.name.localeCompare(p2.name));
                break;
            case 'price':
                orderedMenuItems = menuItems.sort((p1, p2) => p1.price - p2.price);
                break;
            case 'category':
                orderedMenuItems = menuItems.sort((p1, p2) => p1.category.localeCompare(p2.category));
                break;
        }
        p.play({command: 'getMenu', data: orderedMenuItems});
        p.play(`Here are the items ordered by ${orderBy}`);
        console.log(orderedMenuItems);
    }
);

How to pass information to the context

If you need to pass some information to the context, you can use the state object. state is a kind of 'dictionary' where you can store any data you want.

intent("I have friends", async p => {
    p.play("How many friends do you have?");
    let number = await p.then(numberContext)
    p.play("What is your friend's name number one?");
    let index = 0, friends = [];
    let list = await p.then(friendsContext, {state: {number, index, friends}})
    p.play({command: "friends", list})
})

const friendsContext = context(() => {
    intent("$(NAME)", p => {
        let {number, friends} = p.state;
        friends[p.state.index++] = p.NAME;
        if (p.state.index == number) {
            p.resolve(friends)
        }else {
            p.play(`What is your friend's name number ${p.state.index+1}?`);
        }
    })
})

const numberContext = context(() => {
    intent("$(NUMBER)", p => {
        if (p.NUMBER.number > 0) {
            p.resolve(p.NUMBER.number)
        } else {
            p.play("The number of friends must be one or more")
        }
    })
})

In the example above, Alan gets the number of friends the user has and the friends' names. Here we have a voice command: I have friends, and two contexts that are activated from it:

  • numberContext to get the number of friends from the user input
  • friendsContext to create a list of friends the user has

When we activate the friendsContext, we are passing the following state object to it: {state: {number, index, friends}}. number stands for the number of friends retrieved from the user input, index is used for iterating over the friends' array and friends contains the list of friends. Once the number of friends in the array is equal to the number of friends defined in the user input, Alan exits the context, and a command with the list of friends is sent to the app.

How to use voice commands with fuzzy parameters

If you want Alan to recognize users' commands that are close to those added to the voice script but are not 100% matching, you can use slots with fuzzy matching. Add ~ after the slot name and define labels for each slot value.

const array = ["small", "medium", "large"];
intent(`$(ORD~ one~1|first~1|two~2|second~2|middle~2|three~3|third~3|last~3)`, p => {
    // one is value, 1 is label
    let index = parseInt(p.ORD.label);
    p.play(`You've selected ${array[index-1]}`);
})

In the example above, the ORD slot labels are used as indices for values in array. In the Debugging Chat, try typing one, two or last. Alan will reply with a corresponding value from array. Then try making a slight mistake, for example, thrird. Alan will still be able to match the intent.

How to use several slots of the same type in one intent

You can add a slot of the same type to one intent several times. In this case, all slot values will be collected in an array with the following name: SLOTNAME+s, for example, ITEMs.

For example, if you have several slots named ITEM, you can access the ITEMs object with an array of size 2 and you can reference the first item as ITEMs[0].

const items = ["burger", "cola", "taco"].join('|');
intent(`I want $(NUMBER) $(ITEM ${items}) and $(NUMBER) $(ITEM ${items})`, p => {
    let order = _.reduce(_.range(_.size(p.NUMBERs)), (acc, i) => {acc[p.ITEMs[i]] = p.NUMBERs[i].number; return acc}, {});
    p.play({command: "createOrder", order });
})

How to make Alan use the plural form for nouns

To use the pluralizer in Alan, add the underscore character (_) to the necessary noun in the voice command.

This syntax is not supported for irregular plural nouns.

  intent("I want $(NUMBER) $(P cake_)", p => {
      p.play(`Here you are, ${p.NUMBER} ${p.P}`);
  })

How to allow users to say synonyms for the same thing

It is often necessary to give the user an opportunity to say the same thing in several ways. For example, a product in the price list can be expressed differently, but we still must recognize which product the user is talking about. For these purposes, you can compile a synonym dictionary for each element and use fuzzy parameters in the voice command. Fuzzy parameters make it possible to associate a key with the value that is recognized as the user input.

Consider the following example: we are constructing a list of slot values out of a JSON object. aliases become slot values and the id becomes the label.

const products = [
    {id: "cola",  aliases: ["cola","coca-cola", "soda", "coke"]},
    {id: "juice", aliases: ["juice", "fresh", "orange juice", "orange fresh"]}
]
// We have to construct following fuzzy parameters with all value~label items concatenated with |
// cola~cola|cola-cola~cola|soda~cola|coke~cola|juice~juice|fresh~juice|orange juice~juice|orange fresh~juice

const PROD_INTENTS = _.flatten(products.map(p => p.aliases.map(a => `${a}~${p.id}`))).join('|')

intent(`(I want|give me) $(ITEM ${PROD_INTENTS})`, p => {
    p.play(`We added ${p.ITEM.label} to your order`)
})

Here is another example of how to construct a list of slot values out of the JSON object passed with visualState. In this example, we are using dynamic entities to get the user feedback. The list of slot values is constructed out of the values passed in the synonyms key, and the values in the category key are used as the label.

//Passed visualState
{
    "answers": [
        {
            "category": "positive",
            "synonyms": [
                "yes",
                "of course",
                "definitely"
            ]
        },
        {
            "category": "negative",
            "synonyms": [
                "no",
                "in no case",
                "I don't think so"
            ]
        },
        {
            "category": "neutral",
            "synonyms": [
                "maybe",
                "I'll think about it",
                "I don't know"
            ]
        }
    ]
}

// Voice script
onVisualState((p, s) => {
    if (s.answers) {
        p.visual.answersEntity = s.answers.filter(ans => !ans.unique).map(ans => ans.synonyms.map(syn => syn + '~' + ans.category).join('|')).join('|');
    } else {
        p.visual.answersEntity = "";
    }
});

intent(`(My answer is|) $(ANS v:answersEntity)`, p => {
        switch(p.ANS.label){
            case "positive":
                p.play(`That's great!`);
                break;
			case "negative":
                p.play(`Sorry to hear that`);
                break;
            case "neutral":
                p.play(`Thanks for sharing`);
                break;
        }
    });

How to respond differently based on the visual context

If your app has several views or screens, you may need Alan to respond differently to the same voice command when this or that view or screen is active. To do this, you can set the visual state object on the client side, and it will be available in p.visual object in the voice script when the user input matches the intent.

Check our tutorials to see how to send the visual state from apps on different platforms.

intent('What can I do here?', p => {
    switch (p.visual.screen) {
       case "main":
           p.play("Here is the main screen. Choose a category.");
           break;
       case "checkout":
           p.play("You are in the cart. You can update your order or checkout.");
           break;
       case "product":
           p.play(`It's the product details page for ${p.visual.product.name}`);
           break;    
       default:
           p.play("Say open menu to start");
   }
})

You can also filter intents matching based on the visual state value. For example, this intent will never match if the cart is empty:

intent(visual(v => !_.isEmpty(v.order)), `(What are|) my order details`, p => {
    p.play("You have ordered:");
    for (let product in p.visual.order) {
        p.play(p.visual.order[product] + " " + product);
    }
});