Slots

When writing patterns for voice commands, you can add slots to them. A slot is essentially a variable in the user’s utterance that allows Alan to identify and retrieve important facts from the user’s input. For example, in a weather voice assistant, you can use slots to capture the user’s location and forecast date and provide meaningful responses based on what the user has said.

Alan allows using the following slot types:

Note

A command pattern can have a maximum of 5 slots, both user-defined and predefined.

User-defined slots

In Alan, you can create custom, or user-defined slots. User-defined slots list expected possibilities (words or phrases) that the user can say when giving a command. To define a slot, use the $ character followed by brackets (). In brackets, define the name of the slot followed by its possible value.

In the example below, we define the D slot with the today value.

intent('What is the weather $(D today)', p => {
    p.play('It is great!');
});

When defining possible slot values, you can use the same rules as with patterns. You can:

intent('What (is|was|would be) the weather $(D yesterday|today|tomorrow|)', p => {
    p.play('It is great!');
});

Only user-defined slots can be optional. You cannot make predefined slots optional in patterns.

Note

User-defined slots are static. If you need to update the slot values during the dialog, for example, with the data obtained from an external source, use dynamic entities in patterns.

Slot values and labels

A slot is an object that has a set of predefined fields and methods. All slot types have the following fields:

  • .value

  • .label

The .value field is filled automatically when a command with the slot is matched. This field contains the captured user’s input.

In the example below, the user’s input is displayed in Alan logs and pronounced by Alan as a response.

intent('What (is|was|would be) the weather $(D yesterday|today|tomorrow|)?', p => {
    console.log(p.D.value);
    p.play(p.D.value);
    p.play('It is great!');
});

The .label field of the slot can be used to provide any description, identify or classify the slot values. Unlike slot values, labels do not necessarily need to be unique.

To use a label, you need to define it first. Add the ~ character followed by the label value to the slot value.

intent('What (is|was|would be) the weather $(D yesterday~past|today~now|tomorrow~future|)', p => {
    console.log('Value:', p.D.value);
    console.log('Label:', p.D.label);
    p.play('It is great!');
});

In the example below, labels are used to adjust the logic of intents and provide more accurate responses to the user. Now, if the user says: What was the weather yesterday?, the slot .value will be yesterday and the .label will be past. Alan will give a response defined for the past label.

intent('What (is|was|would be) the weather $(D yesterday~past|last day~past|today~now|now~now|tomorrow~future|next day~future|)', p => {
    console.log('Value:', p.D.value);
    console.log('Label:', p.D.label);
    switch (p.D.label) {
        case 'past':
            p.play('It was terrible!');
            break;
        case 'future':
            p.play('I hope it will be fine.');
            break;
        case 'now':
        default:
            p.play('It is great!');
    }
});

Slot values in responses

You can use slots in patterns for response functions: play() and reply(). Use the standard JavaScript syntax for template strings, that is, offset in backticks.

You can also add slot values to the string line.

intent('What (is|was|would be) the weather $(D yesterday~past|last day~past|today~now|now~now|tomorrow~future|next day~future|)', p => {
    console.log('Value:', p.D.value);
    console.log('Label:', p.D.label);
    switch (p.D.label) {
        case 'past':
            p.play(`The weather ${p.D.value} was terrible!`);
            break;
        case 'future':
            p.play(`I hope that the weather ${p.D.value} will be fine.`);
            break;
        case 'now':
        default:
            p.play('The weather ' + p.D.value + ' is great!');
    }
});

Regular expressions

You can use regular expressions in patterns. RegEx is helpful in capturing strings with different letter and digit combinations, like unknown names, IDs, flight or license numbers and so on. To use this slot type, you need to add the * character to the slot name. This will signal Alan this slot contains RegEx.

For example, if you want to match the I want $(FLAVOUR) ice cream command, but you don’t know all possible flavours in advance, you can use the following expression:

const regIce = "\\w+";

intent(`I want $(FLAVOUR* ${regIce}) ice cream`, p => {
    p.play(p.FLAVOUR.value);
});

Note

If you need to use special characters in RegEx, you should escape them with \. Such characters are: \, '.

You can use regular expressions of any complexity. In the example below, the intent will match all combinations of six letters and any number of digits after the incident word. This will help if you need your users to input some uncommon abbreviations.

let letters_reg = "([A-Za-z]{1}\\s?){6}";
let num_reg = "(\\d+\\s?)+";
let reg = letters_reg+num_reg;

intent(`Incident $(INCIDENT* ${reg})`, p => {
    p.play(`${p.INCIDENT.value}`);
});

RegEx work best when they can be uniquely matched. You should try to use words surrounding RegEx in the intent; this will make matching more precise.

Getting any user input

To match any user’s input, you can use a greedy RegEx in slots. This functionality can be helpful, for example, if you need to capture the user’s comments or feedback.

In the example below, the user’s message is captured with the COMPLAINT slot and then played back to the user. The (.+) RegEx pattern here matches any string with one or more characters (a non-empty string).

intent('I want to submit a complaint. $(COMPLAINT* (.+))', p => {
    p.play(`Your complaint has been registered. You've said: ${p.COMPLAINT.value}`);
});

Slots with fuzzy matching

By default, to match an intent with a slot with the highest match score, Alan requires that the user’s input contain the exact slot value. However, you sometimes cannot specify all possible variants in the slot. For example, a slot may contain groups of words in which the word order may differ, some words in the input phrase may be replaced with synonyms or there may be some values that are hard to pronounce.

To allow Alan to match intents with slots whose values may be less than perfect, you can use slots with fuzzy matching. In such slots, Alan matches the intent based on the most similar alternative. Alan relies on the semantic meaning, even if it does not coincide exactly with what the user has said.

Warning

Slots with fuzzy matching should be used with caution, as they can lead to overly general matching.

To define a slot with fuzzy matching, you need to add the ~ character after the slot name. As well as with other user-defined slots, you can provide a label for each value. In this case:

  • The .value field of the slot will contain the user’s input exactly as user has said it, not the value you have defined in the script.

  • The .label field of the slot can have any value and can be used, for example, to give a proper response to the user.

In the example below, the intent will be matched even if the user skips a part of the item name or names a close variant instead. And to ensure proper verbiage, the slot label is played instead of the slot value in the confirmation phrase.

intent('I want a $(ORDER~ Double Cheese Burger~Cheese Burger|Spicy Mexican Burger~Mexican Burger|Grilled Hawaiian Burger~Hawaiian Burger)', p => {
    console.log(`Your choice is ${p.ORDER.value}`);
    p.play(`You have ordered ${p.ORDER.label}. Is it correct?`);
});

Predefined slots

You can use predefined slots to let Alan identify and retrieve some common types of data from the user’s input. Alan offers the following types of predefined slots:

Slot type

Short description

DATE

Matches a correct date in the user’s input, for example, today, March 3rd or two days ago

TIME

Matches time information in the user’s input, for example, 10:00 AM

NUMBER

Matches cardinal numbers in the user’s input, for example, 3 or 25

ORDINAL

Matches ordinal numbers in the user’s input, for example, first or 22nd

LOC

Matches a location in the user’s input, for example, a city, district or address

NAME

Matches a name, surname or both in the user’s input, for example, John Doe

DATE

The DATE slot matches the user’s input when it can be represented as a correct date. For example, the DATE slot can match user inputs like today, last friday, tomorrow, yesterday, March 3rd, 2 days ago and so on.

intent('What is $(DATE)', p => {
    console.log(p.DATE.moment);
    p.play(`${p.DATE.value} is a date`);
    p.play(`It is ${p.DATE.moment.format("dddd, MMMM Do YYYY")}`);
});

Each DATE slot contains two special fields:

  • moment: date object from the moment.js library

  • luxon: the luxon date object

You can call all available methods for date formatting or any kind of calculations on dates. These two objects already use the date in the user timezone which is also always available in p.timeZone.

intent('What is (my|) timezone', p => {
    p.play(`Your current timezone is ${p.timeZone}`);
});

TIME

The TIME slot matches time information in the user’s input. If the user inputs time, the corresponding TIME slot value will be interpreted as time, and the slot is filled with time data.

intent('I will come at $(TIME)', p => {
    p.play(`Ok, we are waiting for you at ${p.TIME.value}`);
});

You can use the special .time field of the TIME object to get the number of seconds from midnight.

intent('How many seconds passed till $(TIME)', p => {
    p.play(`${p.TIME.time} seconds passed from midnight till ${p.TIME.value}`);
});

NUMBER

The NUMBER slot matches any cardinal number, for example 5, three, 256, one million. The .value field will contain the user’s input and the .number field will contain a numerical representation of this input.

intent('I want to order $(NUMBER) items', p => {
    console.log('User input:', p.NUMBER.value);
    console.log('Parsed number:', p.NUMBER.number);
    p.play(`You have ordered ${p.NUMBER.number} items`);
});

ORDINAL

You can use the ORDINAL slot to get ordinal numbers like fist, second, 21st, 3rd. Its .value field will contain the user’s input and .number field will contain a numerical representation of this input.

const LIST = ["Anna", "Michael", "Lisa", "Alex"]

intent ('Who is the $(ORDINAL) in the list?', p => {
    let member = LIST[p.ORDINAL.number - 1];
    p.play(`${member} is the ${p.ORDINAL.value} in the list`);
})

LOC

The LOC slot interprets the user’s input as a location. It can be a city, district, or full address string. For example, Washington, 36 West Bedford and so on.

intent('Navigate me to $(LOC)', p => {
    p.play(`OK! Calculating a route to ${p.LOC.value}.`);
});

NAME

The NAME slot interprets the user’s input as a person. It can be a name, surname, or both. So Alan, Turing, and Alan Turing all can be matched using this slot.

intent('Show contact details for $(NAME)', p => {
    p.play(`Getting contact details for ${p.NAME.value}`);
});

Note

When testing voice commands with $(NAME) and $(LOC) slots in patterns in the Debugging Chat, type the street and name values with the initial capitals. Do not use abbreviations like ave. or dr.

Custom names for predefined slots

To access data captured by predefined slots, you can use system names of these slots or specify custom names. For example, to get a location, you can use the LOC slot:

intent('I need a ticket to $(LOC)', p => {
    p.play(`You have ordered a ticket to ${p.LOC.value}. Is it correct?`);
});

Alternatively, you can specify custom names for the predefined LOC slot and access the captured locations using these custom names:

intent('I need a ticket to $(toDirection LOC) and a ticket back to $(backDirection LOC)', p => {
    p.play(`You have ordered a ticket to ${p.toDirection.value} and a ticket to ${p.backDirection.value}`);
        p.play('Is it correct?');
});

Multiple values in slots

If you need to capture multiple slot values at once, you can add a slot to the pattern several times. When such an intent is matched, all pieces of data captured with the slot are collected to an array of values.

In such a case, p.SLOTNAME.value would contain only the value captured with the first slot instance in the pattern. To access the list of all values in the array, use the slot name followed by the underscore character, for example: p.ITEM_. To access a specific element in the array, add the element index, for example: p.ITEM_[0].value.

Note

In case of predefined slots, you can access slot values by addressing a specific element in the array or define custom slot names. For details, see Custom names for predefined slots.

Let’s assume we want to provide the user with the ability to ask about the price of different pizza sizes with one voice command. We can add the following intent:

const SHOW_REQ = 'How much is a $(D small|medium|large) pizza';
const ALL_REQ = [
    `${SHOW_REQ}`,
    `${SHOW_REQ} and ${SHOW_REQ}`,
    `${SHOW_REQ} and ${SHOW_REQ} and ${SHOW_REQ}`,
]

intent(ALL_REQ, p => {
    console.log(JSON.stringify(p.D));
    console.log(JSON.stringify(p.D_));
    let resultMessage = "";
    if(p.D_[0]) {
        resultMessage = playSizeMessage(p.D_[0].value);
        p.play(resultMessage);
    }
    if(p.D_[1]) {
        resultMessage = playSizeMessage(p.D_[1].value);
        p.play(resultMessage);
    }
    if(p.D_[2]) {
        resultMessage = playSizeMessage(p.D_[2].value);
        p.play(resultMessage);
    }
})

function playSizeMessage(theSize)  {
    if (theSize == "small") {
        return "A small pizza is 5 dollars";
    }
    if (theSize == "medium") {
        return "A medium pizza is 10 dollars"
    }
    if (theSize == "large") {
        return "A large pizza is 15 dollars"
    }
}

Here, we use SHOW_REQ to define a question the user will ask: How much is a $(D small|medium|large) pizza? In ALL_REQ, we provide all possible combinations for this question. For example, the user can ask:

  • How much is a small pizza?

  • How much is a small pizza and how much is a large pizza?

  • How much is a small pizza and how much is a medium pizza and how much is a large pizza?

When the user asks one of the questions above, we collect all captured values for the D slot to an array and then play the pizza price for each element in the array with the playSizeMessage() function.

Each element in the array has its own .value and .label fields:

  • The value field is populated with the value obtained from the user’s input: p.SLOTNAME_[index].value.

  • The label field can contain a custom value and can be used to give more accurate responses to the user: p.SLOTNAME_[index].label.