Use a list entity to increase entity detection

This tutorial demonstrates the use of a list entity to increase entity detection. List entities do not need to be labeled as they are an exact match of terms.

In this tutorial, you learn how to:

  • Create a list entity
  • Add normalized values and synonyms
  • Validate improved entity identification

Prerequisites

Tip

If you do not already have a subscription, you can register for a free account.

All of the code in this tutorial is available on the LUIS-Samples GitHub repository.

Use HomeAutomation app

The HomeAutomation app gives you control of devices such as lights, entertainment systems, and environment controls such as heating and cooling. These systems have several different names that can include Manufacturer names, nicknames, acronyms, and slang.

One system that has many names across different cultures and demographics is the thermostat. A thermostat can control both cooling and heating systems for a house or building.

Ideally the following utterances should resolve to the Prebuilt entity HomeAutomation.Device:

# utterance entity identified score
1 turn on the ac HomeAutomation.Device - "ac" 0.8748562
2 turn up the heat HomeAutomation.Device - "heat" 0.784990132
3 make it colder

The first two utterances map to different devices. The third utterance, "make it colder", doesn't map to a device but instead requests a result. LUIS doesn't know that the term, "colder", means the thermostat is the requested device. Ideally, LUIS should resolve all of these utterances to the same device.

Use a list entity

The HomeAutomation.Device entity is great for a small number of devices or with few variations of the names. For an office building or campus, the device names grow beyond the usefulness of the HomeAutomation.Device entity.

A list entity is a good choice for this scenario because the set of terms for a device in a building or campus is a known set, even if it is a huge set. By using a list entity, LUIS can receive any possible value in the set for the thermostat, and resolve it down to just the single device "thermostat".

This tutorial is going to create an entity list with the thermostat. The alternative names for a thermostat in this tutorial are:

alternative names for thermostat
ac
a/c
a-c
heater
hot
hotter
cold
colder

If LUIS needs to determine a new alternative often, then a phrase list is a better answer.

Create a list entity

Create a Node.js file and copy the following code into it. Change the authoringKey, appId, versionId, and region values.

/*-----------------------------------------------------------------------------
This template demonstrates how to use list entities.
. 
For a complete walkthrough, see the article at
https://aka.ms/luis-tutorial-list-entity
-----------------------------------------------------------------------------*/

// NPM Dependencies
var request = require('request-promise');

// To run this sample, change these constants.

// Authoring/starter key, available in www.luis.ai under Account Settings
const authoringKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// ID of your LUIS app to which you want to add an utterance
const appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

// Version number of your LUIS app
const versionId = "0.1";

// Region of app
const region = "westus";

// Construct HTTP uri
const uri= `https://${region}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${appId}/versions/${versionId}/closedlists`;

// create list entity
var addListEntity = async () => {

    try {

        // LUIS model definition for list entity
        var body = {
            "name": "DevicesList",
            "sublists": 
            [
                {
                    "canonicalForm": "Thermostat",
                    "list": [ "ac", "a/c", "a-c","heater","hot","hotter","cold","colder" ]
                }
            ]
        }

        // HTTP request
        var myHTTPrequest = request({
            uri: uri,
            method: 'POST',
            headers: {
                'Ocp-Apim-Subscription-Key': authoringKey
            },
            json: true,
            body: body
        });

        return await myHTTPrequest;

    } catch (err) {
        throw err;
    }
}
// MAIN
addListEntity().then( (response) => {
    // return GUID of list entity model
    console.log(response);
}).catch(err => {
    console.log(`Error adding list entity:  ${err.message} `);
});

Use the following command to install the NPM dependencies and run the code to create the list entity:

npm install && node add-entity-list.js

The output of the run is the ID of the list entity:

026e92b3-4834-484f-8608-6114a83b03a6

Train the model

Train LUIS in order for the new list to affect the query results. Training is a two-part process of training, then checking status if the training is done. An app with many models may take a few moments to train. The following code trains the app then waits until the training is successful. The code uses a wait-and-retry strategy to avoid the 429 "Too many requests" error.

Create a Node.js file and copy the following code into it. Change the authoringKey, appId, versionId, and region values.

/*-----------------------------------------------------------------------------
This template demonstrates how to use list entities.
. 
For a complete walkthrough, see the article at
https://aka.ms/luis-tutorial-list-entity

-----------------------------------------------------------------------------*/

// NPM Dependencies
var request = require('request-promise');
const Promise = require("bluebird");
const promiseRetry = require('promise-retry');
const promiseDelay = require('sleep-promise');

// To run this sample, change these constants.

// Authoring/starter key, available in www.luis.ai under Account Settings
const authoringKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// ID of your LUIS app to which you want to add an utterance
const appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

// Version number of your LUIS app
const versionId = "0.1";

// Region of app
const region = "westus";

// Construct HTTP uri
const uri= `https://${region}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${appId}/versions/${versionId}/train`;

// times to request training status
const retryCount = 10;

// wait time between training status requests
const retryInterval = 2000;

// current count of retries
let count = 0;

// determine if complete model, all entities, is trained
var isTrained = (trainingStatus) => {

    var untrainedModels = trainingStatus.filter(model => {
        return model.details.status == 'Fail' || model.details.status == 'InProgress';
    });
    return (untrainedModels.length===0);
}

// request training (POST)
var train = async (config) => {

    try {

        var body = undefined;

        // Add an utterance
        var myHTTPrequest = request({
            uri: uri,
            method: 'POST',
            headers: {
                'Ocp-Apim-Subscription-Key': authoringKey
            },
            json: true,
            body: body
        });

        return await myHTTPrequest;

    } catch (err) {
        throw err;
    }
}
// request training status (GET)
var getTrainStatus = async (config) => {

    try {

        var body = undefined;

        var myHTTPrequest = request({
            uri: uri,
            method: 'GET',
            headers: {
                'Ocp-Apim-Subscription-Key': authoringKey
            }
        });

        return await myHTTPrequest;

    } catch (err) {
        throw err;
    }
}
// retry if not trained
var manageRetries = (client) => {

    count += 1;

    return promiseRetry((retry, number) => {

        return promiseDelay(retryInterval)
        .then( () => {
            return getTrainStatus();
        }).then(response => {

            // response is a string
            // convert it to an array of JSON
            response = JSON.parse(response);

            // 2xx http response 
            let trained = isTrained(response);

            console.log(number + " trained = " + trained);

            if (count < retryCount && !trained) retry("not trained");
            
            return response;
        })
        .catch((err) => {
            throw err;
        });
    });  
}
// wait until model is trained -- may take a few moments
var waitUntilTrained = async () => {
    let setTraining = await train();
    let trained = await manageRetries();
    return trained;
}

// MAIN
waitUntilTrained().then( (response) => {
    console.log(response);
}).catch(err => {
    console.log(`Error adding list entity:  ${err.message} `);
});

Use the following command to run the code to train the app:

node train.js

The output of the run is the status of each iteration of the training of the LUIS models. The following execution required only one check of training:

1 trained = true
[ { modelId: '2c549f95-867a-4189-9c35-44b95c78b70f',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } },
  { modelId: '5530e900-571d-40ec-9c78-63e66b50c7d4',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } },
  { modelId: '519faa39-ae1a-4d98-965c-abff6f743fe6',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } },
  { modelId: '9671a485-36a9-46d5-aacd-b16d05115415',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } },
  { modelId: '9ef7d891-54ab-48bf-8112-c34dcd75d5e2',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } },
  { modelId: '8e16a660-8781-4abf-bf3d-f296ebe1bf2d',
    details: { statusId: 2, status: 'UpToDate', exampleCount: 45 } } ]

Publish the model

Publish so the list entity is available from the endpoint.

Create a Node.js file and copy the following code into it. Change the endpointKey, appId, and region values. You can use your authoringKey if you do not plan to call this file beyond your quota limit.

/*-----------------------------------------------------------------------------
This template demonstrates how to use list entities.
. 
For a complete walkthrough, see the article at
https://aka.ms/luis-tutorial-list-entity
-----------------------------------------------------------------------------*/

// NPM Dependencies
var request = require('request-promise');

// To run this sample, change these constants.

// Authoring/starter key, available in www.luis.ai under Account Settings
const authoringKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

// ID of your LUIS app to which you want to add an utterance
const appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";

// Version number of your LUIS app
const versionId = "0.1";

// Region of app
const region = "westus";

// Construct HTTP uri
const uri= `https://${region}.api.cognitive.microsoft.com/luis/api/v2.0/apps/${appId}/publish`;

// publish
var publish = async () => {

    try {

        // LUIS publish definition
        var body = {
            "versionId": "0.1",
            "isStaging": false,
            "region": "westus"
         };

        // HTTP request
        var myHTTPrequest = request({
            uri: uri,
            method: 'POST',
            headers: {
                'Ocp-Apim-Subscription-Key': authoringKey
            },
            json: true,
            body: body
        });

        return await myHTTPrequest;

    } catch (err) {
        throw err;
    }
}
// MAIN
publish().then( (response) => {
    // return GUID of list entity model
    console.log(response);
}).catch(err => {
    console.log(`Error adding list entity:  ${err.message} `);
});

Use the following command to run the code to query the app:

node publish.js

The following output includes the endpoint url for any queries. Real JSON results would include the real appID.

{ 
  versionId: null,
  isStaging: false,
  endpointUrl: 'https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/<appID>',
  region: null,
  assignedEndpointKey: null,
  endpointRegion: 'westus',
  publishedDateTime: '2018-01-29T22:17:38Z' }
}

Query the app

Query the app from the endpoint to prove that the list entity helps LUIS determine the device type.

Create a Node.js file and copy the following code into it. Change the endpointKey, appId, and region values. You can use your authoringKey if you do not plan to call this file beyond your quota limit.

/*-----------------------------------------------------------------------------
This template demonstrates how to use list entities.
. 
For a complete walkthrough, see the article at
https://aka.ms/luis-tutorial-list-entity
-----------------------------------------------------------------------------*/

// NPM Dependencies
var argv = require('yargs').argv;
var request = require('request-promise');

// To run this sample, change these constants.

// endpointKey key - if using a few times - use authoring key, otherwise use endpoint key
const endpointKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" || argv.endpointKey;

// ID of your LUIS app to which you want to add an utterance
const appId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" || argv.appId;

// Region of app
const region = "westus" || argv.region;

// Get query from command line
// you do NOT need to add quotes around query phrase
// example: node query.js turn on the lights
// q: "turn on the lights"
// verbose: true means results return all intents, not just top intent
const q = argv._.join(" ");
const uri= `https://${region}.api.cognitive.microsoft.com/luis/v2.0/apps/${appId}?q=${q}&verbose=true`;

// query endpoint with endpoint key
var query = async () => {

    try {

        var myHTTPrequest = request({
            uri: uri,
            method: 'GET',
            headers: {
                'Ocp-Apim-Subscription-Key': endpointKey
            }
        });

        return await myHTTPrequest;

    } catch (err) {
        throw err;
    }
}

// MAIN
query().then( (response) => {
    // return verbose results (all intents, all entities)
    console.log(response);
}).catch(err => {
    console.log(`Error querying:  ${err.message} `);
});

Use the following command to run the code and query the app:

node train.js

The output is the query results. Because the code added the verbose name/value pair to the query string, the output includes all intents and their scores:

{
  "query": "turn up the heat",
  "topScoringIntent": {
    "intent": "HomeAutomation.TurnOn",
    "score": 0.139018849
  },
  "intents": [
    {
      "intent": "HomeAutomation.TurnOn",
      "score": 0.139018849
    },
    {
      "intent": "None",
      "score": 0.120624863
    },
    {
      "intent": "HomeAutomation.TurnOff",
      "score": 0.06746891
    }
  ],
  "entities": [
    {
      "entity": "heat",
      "type": "HomeAutomation.Device",
      "startIndex": 12,
      "endIndex": 15,
      "score": 0.784990132
    },
    {
      "entity": "heat",
      "type": "DevicesList",
      "startIndex": 12,
      "endIndex": 15,
      "resolution": {
        "values": [
          "Thermostat"
        ]
      }
    }
  ]
}

The specific device of Thermostat is identified with a result-oriented query of "turn up the heat". Since the original HomeAutomation.Device entity is still in the app, you can see its results as well.

Try the other two utterances to see that they are also returned as a thermostat.

# utterance entity type value
1 turn on the ac ac DevicesList Thermostat
2 turn up the heat heat DevicesList Thermostat
3 make it colder colder DevicesList Thermostat

Next steps

You can create another List entity to expand device locations to rooms, floors, or buildings.