Documentation

Powered by Algolia

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:

  • User-defined slots to capture custom values the user can name when giving a voice command.
  • Predefined slots to capture standard data types like user's location, time, numbers and so on.

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 name when giving a command.

To define a slot, use the $ sign followed by brackets (). In brackets, define the name of the slot followed by its possible value(s). 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 principles 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.

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, Alan displays the user's input captured with the D slot in logs and plays it back to the user:

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 ~ sign 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, slots 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

The captured slot values can be used in patterns for response functions:

Use the standard JavaScript syntax for template strings: 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

Alan supports slots with 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 * sign 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);
});

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 going 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 values work best when they can be uniquely matched. When writing a pattern with a RegEx, add words preceding or following the RegEx whenever possible: 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 open 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, 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. That is, Alan relies on the semantic meaning, even if it is not close to what the user has said.

To define a slot with fuzzy matching, you need to add the ~ sign after the slot name and provide a label for each value. For slots with fuzzy matching, the .value field will contain the user's input exactly as user has said it, not the value you have defiined in the script. The .label field can be used to give a proper response to the user.

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

For example, let's assume you want to receive feedback on the following question: How likely are you to use our company products again? To the intent, you add a slot with four values, each of which gets a rating from 1 to 4 in the label field. There are many different variants how users can say very likely, for example:

  • very likely
  • super likely
  • extremely likely
  • massively likely

To make sure such intent is matched, you need to either cover all possible variants using alternatives with the same label (as in the end you are interested in the numerical grouped value for statistics) or you can use a slot with fuzzy matching.

intent('$(RATING~ very unlikely~1|unlikely~2|likely~3|very likely~4)', p => {
    p.play(`Your input is ${p.RATING.value}`);
    p.play(`The rating that you are giving is ${p.RATING.label}`);
});

In the example above, all listed inputs (super likely, extremely likely and so on) and other phrases similar to them will be matched as semantically they are close to very likely.

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's 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(`Its ${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's 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 went 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}`);
});

Accessing data in 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 the number of tickets captured with the NUMBER slot, you can use the p.NUMBER.value variable:

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

Alternatively, you can specify a custom name for the predefined NUMBER slot and access the captured number using this custom name:

intent('I need $(TICKETNUM NUMBER) tickets', p => {
    p.play(`You have ordered ${p.TICKETNUM.value} tickets. Is it correct?`);
});

Capturing several values with slots

If you need to capture several 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.

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.