Prompting user for information

PromptDialog is a class that allows you to prompt users to answer questions. Each question corresponds to an entity. When the user answers the questions, entities are extracted from the answer. The PromptDialog uses the extracted entities to decide what to do next.

For example, you can use a PromptDialog when you want to:

Configure a car purchase:

const { PromptDialog } = require('botfuel-dialog');

class CarDialog extends PromptDialog {}

CarDialog.params = {
  namespace: 'car',
  entities: {
    color: {
      dim: 'color',
    },
    brand: {
      dim: 'brand', // use a corpus extractor
    },
    transmission: {
      dim: 'transmission', // use a corpus extractor
    },
    isNew: {
      dim: 'system:boolean',
    },
  },
};

Book a hotel:

const { PromptDialog } = require('botfuel-dialog');

class BookHotelDialog extends PromptDialog {}

BookHotelDialog.params = {
  namespace: 'book-hotel',
  entities: {
    city: {
      dim: 'city',
    },
    personNumber: {
      dim: 'number',
      isFulfilled: number => number && number <= 10, // max 10 persons
    },
    fromDate: {
      dim: 'time',
      isFulfilled: fromDate => fromDate && fromDate > Date.now(),
    },
    toDate: {
      dim: 'time',
      isFulfilled: (toDate, { dialogEntities }) =>
        toDate > dialogEntities.fromDate,
    },
  },
};

Implementation

In order to use the PromptDialog, you need to import the class and extend it to your dialog class.

The following example illustrates a travel dialog:

const { PromptDialog } = require('botfuel-dialog');

class TravelDialog extends PromptDialog {}

TravelDialog.params = {
  namespace: 'travel',
  entities: {
    departure: {
      dim: 'city',
      priority: 3,
      isFulfilled: (city, { dialogEntities }) =>
        city & (city !== dialogEntities.destination),
    },
    destination: {
      dim: 'city',
      priority: 2,
      isFulfilled: (city, { dialogEntities }) =>
        city & (city !== dialogEntities.departure),
    },
    date: {
      dim: 'time',
      isFulfilled: date => date && date > Date.now(),
    },
  },
};

Parameters

The PromptDialog has the following parameters:

  • The namespace is used to identify the dialog in the brain, where things are stored.
  • The entities are extracted from users answers by extractors and used to customize the flow of a conversation or to perform some actions.

For example:

<dialog-name>.params = {
  namespace: '<dialog-namespace>',
  entities: {
    <entity-name>: {
      dim: String,
      priority: Number,
      isFulfilled: Function(),
      reducer: Function(),
    },
    ...
  },
}

Namespace

The namespace is a key used by the brain to store the dialog-related data into the brain and access it. The namespace must be unique.

Entities

An entity is defined by the following properties: dim, priority, isFulfilled and reducer.

dim

The dimension defines the type of information that will be extracted from the user sentence. Dimensions can be pre-defined or user-defined.

dim is a required property.

The entity extractor based on Botfuel NLP entity extraction web service supports 31 dimensions. Corpus extractors use user-defined dimensions. There is also a built-in entity dimension system:boolean used to extract yes/no answers.

priority

The priority is a positive number, the greater the priority, the earlier the bot will ask for this entity.

priority is an optional property with a default value of 0.

For example, you can have the bot ask for the name of the user first, and then for his/her age:

age: {
  dim: 'number',
  priority: 1,
},
name: {
  dim: 'forename',
  priority: 2,
},

isFulfilled

The isFulfilled property is a function that returns a boolean value indicating if the entity value is matching the condition of done of the entity.

isFulfilled (entityName, { dialogEntities })

isFulfilled is an optional property. By default, an entity is fulfilled if the entity value is defined.

For example, if you want to store exactly 3 cities:

cities: {
  dim: 'city',
  isFulfilled: cities => cities && cities.length === 3,
}

Or if you want to retrieve a date later than today:

date: {
  dim: 'time',
  isFulfilled: date => date && date > Date.now(),
}

reducer

The reducer property is a function that defines how to deal with new values for the entity.

reducer (newValue, oldValue)

reducer is an optional property. By default the old value is replaced with the new one.

For example, if you want to replace the color only if the new one extracted is blue:

color: {
  dim: 'color',
  reducer: (oldColor, newColor) => newColor === 'blue' ? newColor : oldColor,
}

You want to replace the age only if is greater than the previous one:

number: {
  dim: 'number',
  reducer: (oldNumber, newNumber) => newNumber > oldNumber ? newNumber : oldNumber,
}

Erasing entities

By default, your bot will remember all the information (i.e., entities) the user provided. However, in certain use cases, remembering entities may not be desirable.

In the following conversation, the user wants to book a flight to Paris on September 10th, 2018 and a flight to London on September 20th, 2018.

  • User: I want to book a flight to Paris
  • Bot: When do you want to arrive?
  • User: September 10th 2018
  • Bot: Ok, your flight to Paris on September 10th 2018 is booked, thank you.
  • User: I want to book a flight to London
  • Bot: Ok, your flight to London on September 10th 2018 is booked, thank you.

In this example, the bot remembered the flight date from the first booking, but the user actually wants to book for the 20th. It may make more sense to forget all about the first flight once it's booked.

There are two ways to do that: starting a new conversation and deleting entries from the brain.

Starting a new conversation

The hook dialogWillComplete is fired at each dialog step and determines if the dialog is finished or not. The default dialogWillComplete implementation returns this.complete() once there are no missing entities left, otherwise it returns this.wait(), meaning the dialog still needs more information from the user.

Starting a new conversation is an easy way to forget all the information from the user. To do that, instead of returning this.complete, we return this.startNewConversation(). The difference between the two is that this.complete() will move the completed dialog from the current dialogs stack to the previous dialogs stack, while this.startNewConvesation() will empty both stacks.

const { PromptDialog } = require('botfuel-dialog');

class TravelDialog extends PromptDialog {
  dialogWillComplete() {
    if (data.missingEntities.size === 0) {
      return this.startNewConversation();
    }
    return this.wait();
  }
}

TravelDialog.params = {
  namespace: 'travel',
  entities: {
    city: {
      dim: 'city',
    },
    date: {
      dim: 'time',
      isFulfilled: date => date && date > Date.now(),
    },
  },
};

Keep in mind that using this approach, the bot will forget everything about this user. To have more granular control over what the bot forgets, you can delete specific entries from the brain.

Deleting entries from the brain

If there are entities or data from other dialogs you want to keep, you can delete only a specific key of the brain for a given user using the resetEntities method of the PromptDialog:

class TravelDialog extends PromptDialog {
  async dialogWillComplete(userMessage, data) {
    if (data.missingEntities.size === 0) {
      await this.resetEntities(userMessage.user);

      return this.complete();
    }
    return this.wait();
  }
}

TravelDialog.params = {
  namespace: 'travel',
  entities: {
    city: {
      dim: 'city',
    },
    date: {
      dim: 'time',
      isFulfilled: date => date && date > Date.now(),
    },
  },
};