Natural language understanding

FurhatOS provides a Natural language understanding (NLU) component that is well integrated with the flow. The NLU has two important concepts:

  • Intent: Each user utterance can be classified according to its Intent. Think of intent as a method in programming. For example, the intent of an utterance could be a Greeting ("hello there"), a RequestRepeat ("could you repeat that") or a BuyFruit ("I want to buy an apple"). Each intent can be expressed using many different combinations of words.
  • Entity: Utterances can also contain semantic entities, that is, parts of the utterance that represent concepts such as City, Color, Time or Date. Think of entities as parameters to the method (which corresponds to the intent). Thus, the BuyFruit intent may have an entity Fruit that specifies the fruit to be bought.

Intents

Intents are defined by extending the Intent class and providing examples. These examples do not have to match exactly to what the user says. Instead, the system uses machine learning to choose the intent that matches best, from a set of possible intents.

You define intents this way in Kotlin:

class Greeting : Intent() {
    override fun getExamples(lang: Language): List<String> {
        return listOf("hello there", "nice to meet you")
    }
}

As can be seen, the examples can be provided by overriding the getExamples() method.

Defining intent examples in a separate file

It is also possible to put them in a separate text file (separated by newline), such as a greeting intent. Give the file the name Greetings.en.exm ("en" for English ignoring the dialect, e.g. "en-GB" should be just "en") and put it in the resources folder in the same package as the intent class. See the example further down on this page for relative file placement.

The Greetings.en.exm could for example include:

Hello
Hi
Howdy
Hi there

When a separate file is used, the getExamples() method should not be implemented:

class Greetings : Intent()

One could also chose to make a seperate directory for every language. For example, we define the DontKnow intent by creating a directory en and placing a file called DontKnow.exm in there.

NOTE: Files with examples are case sensitive. For example, an intent called DontKnow will not find examples provided in a file labelled dontknow.exm


Example

If you do not have a resources folder set up, you will have to create it and mark it as the resource root folder in IntelliJ. The little yellow icon is an indicator that it has been marked as such.

In our nlu.kt which is located in src\main\kotlin\furhatos\app\testenv\nlu we defined two intents like this:

class Greetings : Intent()
class DontKnow : Intent()

We then provide two files:

  1. File is located in src\main\resources\furhatos\app\testenv\nlu called DontKnow.en.exm.
  2. File is located in src\main\resources\furhatos\app\testenv\nlu\en called Greetings.exm.

Note how IntelliJ will display the file path as furhatos.app.testenv.nlu, which is purely a way to compactly display nested folders. The created folder should not be named with periods, like shown in the screenshot. Unfortunately, IntelliJ will display it the same.

Now we can use our examples in the flow like this:

onResponse<Greetings> {}
onResponse<DontKnow> {}

Intents with Entities

Note: Scroll down to see the docs about Entities

To specify entities in an intent, you most easily define them in the default constructor:

class BuyFruit(val fruit : Fruit? = null) : Intent() {
    override fun getExamples(lang: Language): List<String> {
        return listOf("banana", "I want a banana", "I would like a banana")
    }
}

Important note: Entities need to be nullable and with null as a default value. This is a requirement for the NLU intent classification to work properly.

Note that the examples do not have to contain every variant of the fruit, and you do not have to point out the parameter in the example ("banana"), this is done automatically. However, you can use the name of the entity instead if you want (Using the format "I want a @fruit").

If your intent has several entities of the same type, you have to specify the field in the examples, since they cannot be automatically assigned:

class OrderTrip(
  val departure : City? = null,     
  val destination : City? = null,
  val date : Date? = null
) : Intent() {
    override fun getExamples(lang: Language): List<String> {
        return listOf(
                "i want to go from @departure to @destination on the first of august",
                "i would like to travel to @destination")
    }
}

Intent classification

When user speech is detected (see Listening), all intents that are relevant in the current dialog state are considered, and the user's utterance is classified as belonging to one of these intents (or none of them, if none of them is close enough). All candidates are assigned a confidence score and the one with the highest score is selected, given that it passes the classification threshold. The default threshold is 0.6, but this can be increased generally, or for individual intents:

// This is how we raise the general threshold:
furhat.param.intentConfidenceThreshold = 0.7

// This is how we raise it for a specific intent:
class MyIntent() : Intent() {
    override fun getConfidenceThreshold() {
        return 0.7
    }
}

The intent classifier uses the examples in the intent to make the classification. The examples do not have to match exactly what the user says, and different examples might cover different parts of the user's utterance. However, the more examples you add, the higher is the chance of a good match. Also consider adding negative examples (as described above) to avoid false positives. Each intent will be compared to the other active intent candidates, so the result will depend on what those are. The classifier will give different weights to different words, depending on how common they are across the different candidates: Words that can only be found in one of the intents are given a greater weight than words that are found in many different intents. Also, word order is of importance. Note that per default, stemming is applied to words, so "dogs" and "dog" will be regarded as the same thing.

If an entitity in found in all examples in an intent, it is has to be found in the utterance for the intent to match. If you want to mark other words in the example to be required, you can give them a + prefix (it is enough that a word is marked in one place):

class OrderPizzaIntent() : Intent() {
     override fun getExamples(lang: Language): List<String> {
        // We mark "pizza" here as required, since it is not enough with any of the other words to trigger this intent
        return listOf("I want to order a +pizza", "Could you order a +pizza for me")
    }
}

In addition to the confidence score, the classifier also calculates these scores, that you can use if you want:

  • Recall: Given the closest matching example in the intent, how much of that example was found in the utterance?
  • Precision: How much of the user's utterance was found in any of the examples.

Alternative classification

Note: Alternative classifier available since 1.19.0

Since 1.19.0, we have implemented an alternative intent classifier, which should give better accuracy. However, it is not guaranteed to give the same results as the old classifier and is not used by default. In addition, there can be a significant delay when initializing the classifier with a large amount of intents. If you would like to test the new classifier in your skill, you can use the following command:

LogisticMultiIntentClassifier.setAsDefault()

It is possible to use negative examples with the alternative classification, if your intent tend to match utterances that it shouldn't:

class Greeting : Intent() {
    override fun getExamples(lang: Language): List<String> {
        return listOf("hello there", "nice to meet you")
    }
    override fun getNegativeExamples(lang: Language): List<String> {
        return listOf("It is not nice to meet you")
    }
}

Dynamic Intents

The methods described above are very useful when a set of intents can be pre-defined in Kotlin. Defining intents as classes has the advantage that Kotlin understands the types of the entities, and thereby provides code completion for them in the flow.

However, sometimes it is not possible to define all intents as separate classes, but you would rather want to define them as instances of a common class. This could for example be the case if you want to read a set of intents from an external resource, and generate them on-the-fly.

There are two classes that support this behavior: SimpleIntent, in case you do not need the intent to have any entities, and DynamicIntent, if you need it to support entities.

Built-in Intents

FurhatOS comes with common intents already pre-defined, in the furhatos.nlu.common package:

  • Yes (e.g. "yes")
  • No (e.g. "no")
  • DontKnow (e.g. "I don't know")
  • Maybe (e.g. "Perhaps")
  • Greeting (e.g. "Hi there")
  • Goodbye (e.g. "Goodbye")
  • Thanks (e.g. "Thank you")
  • Wait (e.g. "Please wait")
  • RequestRepeat (e.g. "Could you repeat")
  • AskName (e.g. "What is your name?")
  • TellName (e.g. "My name is Peter")

Note: currently these intents exist for English, Swedish, German, Spanish, French, Italian, Arabic, and Mandarin.

SimpleIntent - dynamic intents without entities

You can easily define a SimpleIntent like this:

val Compliment = SimpleIntent("I love you", "you look fantastic" )

It can then be caught in the flow like this:

onResponse(Compliment) {
    furhat.say("That was very nice of you!")
}

Notice the difference from the previous examples, where the intent class is specified inside angle brackets (<>). Here, we instead use the actual intent object as an argument.

You can also provide a list of SimpleIntents to catch:

val NiceThingsToSay = listOf(Compliment,Praise,Gratitude)

onResponse(NiceThingsToSay) {
    furhat.say("That was very nice of you!")
}

You can also define a simple intent on-the-fly in the flow (with examples in brackets):

onResponse("what is your favorite film", "what do you like to watch") {
    furhat.say("I really like Star Wars")
}

It is also possible to extend the SimpleIntent:

class QAIntent(
        val question : String,
        val answer : String
) : SimpleIntent(listOf(question))

val Questions =
    listOf(
        QAIntent("how old are you", "I am five years old"),
        QAIntent("what is your name", "my name is Furhat"),
        QAIntent("what is your favorite food", "I love meatballs"))

As you can see below, it is now possible to access the actual intent that was matched through the it.intent variable. Kotlin knows the type of the intent (since Questions is a list of intents of type QAIntent), and you can therefore directly access the "answer" field:

onResponse(Questions) {
    val castIntent = it.intent as QAIntent
    furhat.say(castIntent.answer)
}

DynamicIntent - dynamic intents with entities

If you need to support entities in you dynamic intents, you should use the DynamicIntent class:

val OfferFruit = DynamicIntent(
    listOf("do you want a banana", "would you like an apple"),
    mapOf("fruit" to Fruit::class.java),
    Language.ENGLISH_US)

As can be seen, the constructor needs to be provided with example utterances, a map with the entities, and the language of the intent. The DynamicIntent can be used in the flow in exactly the same way as SimpleIntent, but the "it.intent" variable now has two properties: "dynamicIntent", which points to the dynamic intent object, and "entities" which points to a Record of entities found when matching the intent.

Entities

An entity (or Semantic entity) is defined as a Java class that extends the Entity class. As we will see, there are already a number of common entities implemented. For example, the entity Date corresponds to "tomorrow" or "the 3rd of July". There are also a number of abstract entity classes that can be extended, in order to make it convenient to implement them using different algorithms.

Entities have three important properties:

  1. Entities are dependent on each other. For example, Time entity ("three o'clock") is dependent on the Number entity ("three").

  2. Entities can identify themselves in a text. This means that the semantic entity encapsulates the method for identifying itself in a text in a certain language. Different entities can use different methods for the natural language processing (NLP), such as a context-free grammar, or machine learning. Since entities are identified in different processing steps, but dependent on each other, different NLP techniques can be mixed in the same interpretation chain.

  3. Entities are rich Java objects. This means that they can encapsulate powerful methods and fields. For example, the Date entity has the method asLocalDate(), which returns a LocalDate object (introduced in Java 8), which has very powerful methods for date arithmetics. The City entity contains information about the country, population, latitude and longitude.

EnumEntity

FurhatOS provides a set of base classes for easily defining different types of entities, using different NLU algorithms.

The EnumEntity is based on a simple enumeration. Such entities can easily be defined in Kotlin like this:

class Fruit : EnumEntity() {
    override fun getEnum(lang: Language): List<String> {
        return listOf("banana", "orange", "apple", "pineapple", "pear")
    }
}

Using EnumEntities

In this basic example, the language is ignored, and a simple list is returned. Note that the items are not case sensitive.

When the EnumEntity has been identified, the value of the entity (e.g. "banana") is stored in the "value" property of the EnumEntity. The text used to identify the entity is stored in the "text" property. In the simple case described above, these will be the same thing: Each alternative only has one surface form, and the value of the entity is the same as this form.

However, sometimes you want different surface forms to mean the same thing (i.e. synonyms), and the value to have a special computational meaning that is different from the surface form. For example, you typically want the value to be the same across different languages. In that case, you can specify the enum like this, where the value is followed by a colon and a list of synonyms, separated by commas (note that the value is not treated as a synonym and will not be used for matching):

coca_cola:cola,coca cola,coke
pepsi:pepsi,pepsi cola
sprite

Per default, stemming is applied to the words, which means that it assumes that for example "apple" and "apples" are the same words. If you do not want this, you can specify this in the EnumEntity constructor (EnumEntity(stemming=false)):

Note: Stemming is currently only available in English, Swedish, and German. For languages that currently don't support stemming we recommend using synonyms, as described above.

Defining EnumEntities in separate files

Enumerations can also be defined in a separate file. The system assumes the files to be given the name of the entity, plus the language, and the .enu extension. For the Fruit entity, this would be for example Fruit.en.enu. The file should be placed in the resource folder of same package folder as the entity class. This is very similar to dealing with intent examples in a separate file.

Example content of Fruit.en.enu would be:

banana
orange:orange,lemon
apple
pineapple
pear

If such an enumeration file is used, the class should not implement the getEnum() method:

class Fruit : EnumEntity()

GenericEnumEntity

In the examples above, we have assumed that the EnumEntity only has one value field, which has the name value and is of the type String. For more complex use cases, where we might want to support more complex types, we can instead extend the more generic class GenericEnumEntity.

class CountryEntity(val name: String? = null, val population: Int? = null) : GenericEnumEntity(stemming = false) {
    // We override getEnumItems instead of getEnum
    override fun getEnumItems(lang: Language): List<EnumItem> {
        return listOf(
                EnumItem(CountryEntity("Sweden", 9995000), "sweden"),
                EnumItem(CountryEntity("Norway", 5258000), "norway"),
                EnumItem(CountryEntity("Denmark", 577000), "demark")
        )
    }
    // We should also override toText, so that the entity can be used in speech output (using string interpolation)
    override fun toText(lang: Language?): String {
        return "$name";
    }
}

ComplexEnumEntity

If you want your enum entity to refer to other entities, you need to use the ComplexEnumEntity. For example, you could define an entity that matches things like "five apples", which uses both the Fruit entity defined above and the built-in entity Number:

class FruitCount(
    val count : Number? = Number(1),
    val fruit : Fruit? = null) : ComplexEnumEntity() {

    override fun getEnum(lang: Language): List<String> {
        return listOf("@count @fruit", "@fruit")
    }
}

In the enum, you can use a mix of words and references to entities, which starts with the @-symbol. The referred entities are defined as variables in the class and will be instantiated when extracting the entity. In this example, we also allow just "@fruit" (e.g. "banana"), in which case the "count" field will be assigned the default value Number(1).

ComplexEnumEntity Wildcards

ComplexEnumEntity also supports wildcards, i.e., fields that can match arbitrary strings. To do this, you simply use the String class for a field. The following example would catch all strings like "remind me to water the flowers", where the field "who" would be bound to "me", and "what" would be bound to "water the flowers". Note that the matching of wildcard elements is greedy, so it will match as many words as possible, and has to match one of the examples exactly.

class FruitCount(
    val who : String? = null,
    val what : String? = null) : ComplexEnumEntity() {

    override fun getEnum(lang: Language): List<String> {
        return listOf("remind @who to @what")
    }
}

Of course, it is also possible to mix wildcard elements with entities (e.g., such as the built-in entity PersonName for "who", or Color in a clothes store scenario).

ListEntity

If you want to represent a list of entities as one entity (such as "banana, orange and apple"), you can define it as a ListEntity. This is very useful for intents that allow such enumarations:

class ListOfFruit : ListEntity<Fruit>()

class BuyFruit(val fruits : ListOfFruit? = null) : Intent() {
    override fun getExamples(lang: Language): List<String> {
        return listOf("banana", "I want banana apple and orange", "I would like a banana")
    }
}

If you instead of Fruit use the FruitCount entity defined above, you could match phrases like "one banana, two apples and three oranges".

WildcardEntity

WildcardEntity can be used to match arbitrary strings, as part of an intent. The examples in the intent will be used to identify the strings. The preceding and following words in the example are used to identify the string, so it is important that these match.

Also note that you can not reliably use two wildcard entities right after one another, for example "I would like to buy a @color @garment.", since multiple words can not be accurately assigned to the two entities in a greedy manner. For example, if the user were to say "I would like to buy a lime green knitted sweater", it is difficult to determine if @color is supposed to match "lime", "lime green", or even "lime green knitted". For such a use case, a ComplexEnumEntity might be better suited, with an enum for the color and a wildcard for the garment. Neighboring entities that contain multiple words are a tough nut to get correct every time, so take care when designing the conversational flow.

Note: Do not confuse WildcardEntity with the Wildcard functionality in ComplexEnumEntity mentioned above. The examples in ComplexEnumEntity have to match exactly, whereas the examples in WildcardEntity do not.

class WhoEntity: WildcardEntity("who", SendMessageIntent())
class MessageEntity: WildcardEntity("message", SendMessageIntent())

class SendMessageIntent(): Intent() {

    var who: WhoEntity? = null
    var message: MessageEntity? = null

    override fun getExamples(lang: Language): List<String> {
        return listOf(
            "Tell @who to @message",
            "Send @who a message saying @message"
        );
    }

}

GrammarEntity

If you need an entity to identify more complex syntactic structures, you can specify them using a grammar (technically a context-free grammar), using the GrammarEntity.

class Burger(
  val type : String? = null,
  val size : String? = null
) : GrammarEntity() {

    override fun getGrammar(lang : Language) : Grammar {
        return when (lang.main) {
            "en" -> BurgerGrammarEn
            else -> throw InterpreterException("Language $lang not supported for ${javaClass.name}")
        }
    }
}

val BurgerGrammarEn =
    grammar {
        rule(public = true) {
            group {
                - {"a" / "an" / "the")
                ruleref("size")
                ruleref("type")
                tag { Burger(size=ref["size"] as String,
                        type=ref["type"] as String) }
            }
        }
        rule("size") {
            +"small" tag {"small"}
            +"medium" tag {"medium"}
            +"large" tag {"large"}
            +"big" tag {"large"}
        }
        rule("type") {
            +"burger" tag {"regular"}
            +"hamburger" tag {"regular"}
            +"cheeseburger" tag {"cheese"}
            +"fishburger" tag {"fish"}
        }
    }
  • grammar: Defines a grammar consisting of a set of rules
  • rule: Defines a rule in the grammar. The rules marked as "public" are the ones that will be used to identify the entity. The other rules are sub-rules (given names), used to identify parts of the entity. The rule contains a list of tokens, of which one has to match.
  • ruleref: Matches another rule, specified by the name.
  • entity: Matches an entity of type T
  • group: Defines a sequence of tokens that has to match.
  • choice: Defines a list of tokens or groups, of which one has to match.

Matching of words can be defined in different ways:

// Matches the word "burger" (and returns the string "burger")
+"burger"
// Optionally matches the word "the"
-"the"
// Matches the word "burger" and returns the string "regular"
+"burger" tag {"regular"}
// Matches "burger" or "hamburger", and returns the string "regular"
+("burger" / "hamburger") tag {"regular"}
// Optionally matches "a", "an", or "the"
-("a" / "an" / "the")
// NB: Remember to use the "+" or "-". If you ommit these, Kotlin will not complain, but the token will not be added to the grammar.

Built-in entities

The Furhat system comes with common Entities already pre-defined, in the furhatos.nlu.common package:

  • Time (e.g. "half past six", "5pm")
  • Date (e.g. "on the first of august", "tomorrow")
  • Number (e.g. "three hundred", "300")
  • Ordinal (e.g. "third", "3rd")
  • Color (e.g. "red")
  • PersonName (e.g. "Peter")

Note: currently some of these entities are defined for English, Swedish and Mandarin only.

Using NLU in flow

The NLU entities are deeply coupled with the flow, allowing a typed experience when defining your response handlers. For

Using Intents

You can use the intents directly in the flow in the onResponse event handler with onResponse<Intent> { ... }, example as follows:

val AskFruit = state {
    onEntry {
        furhat.ask("What fruit do you want?")
    }

   onResponse<Greeting> {
        furhat.say("Hello there")
        reentry()
    }

    onResponse<BuyFruit> {
        furhat.say(it.intent.fruit + ", what a lovely choice")
            reentry()
    }

}

As you can see, the entity of the intent can be accessed through the "it" variable.

It is possible to have onResponse handlers with intents on different levels in the state hierarchy. The system will collect all intents from all ancestors to the current state, to choose from.

Using Entities as Intents

If you want an intent to just cover a single entity, and the utterance corresponds to that entity (e.g. "banana" as a ReplyFruit intent, to answer the question "which fruit do you want?"), you can use the entity directly in the flow as you would an intent. However, be aware that the entities must be included fully in the utterance to match. If your entity has the defintion "lord darth vader" and you try to match it as an intent, utterances like "I like lord darth vader very much" may match but "I am lord vader" will not. Solve this by providing several definitions for each entity entry.

val AskFruit = state {
    onEntry {
        furhat.ask("What fruit do you want?")
    }

    onResponse<Fruit> {
        furhat.say(it.intent + ", what a lovely choice")
        reentry()
    }

}

When entities are used as intents like this, the it.intent field will hold the entity (Fruit in this case).

Lists of intents

Sometimes, you might have several intents that you want to handle the same way. For example, in some contexts you might want a "maybe" to be handled the same way as a "no" (because consent is important!) but in others not. There are several ways of accomplishing this, lists of events is the first.

If you have the handlers in the same state, you can use listOf(FirstIntent(), SecondIntent()) together with onResponse. Example:

  onResponse(listOf(No(), Maybe())) {
    furhat.say("Okay, no problem!")
  }

You can get the entities from the intent above with it.intent.getEntities(), returning a map of variable name and the Entity instance.

If you want, you can also use a function that returns a list of responses in a similar fashion:

// As a lambda
onResponse({ listOf(Yes(), No()) }) {
    // Handle
}

// As named function
fun getIntentList() : List<IntentCandidate> {
    return listOf(Yes(), No())
}

onResponse(getIntentList()) {
    // Handle
}


Raising a response with a new Intent

You can also raise a response with a new response, where you create a new intent. This allows you to use an already defined response handler, perhaps in a parent state. It doesn't matter where in order the second response handler is defined.

onResponse<No> {
    furhat.say("Yes or no? I'm confused")
}

onResponse<Yes> {
    raise(it, No())
}

While this gives you more flexibility in terms of what you can do with the response, when you manually raise a response with a new intent you have to manually construct the second response and intent. This means that you also have to construct/attach any entities that your new intent might need.

If you don't need to keep any information from the response, such as the text of the user's speech, you can raise an intent with raise(intent).

Generating text

Sometimes you need to generate a text back from an intent or an entity (referred to as Natural Language Generation, or NLG), for example if you want to confirm something that the user said.

The base class Entity implements an interface called TextGenerator, which provides a method toText(lang: Language), which generates a text string for the entity in a specific language. There is also a toText() method that calls toText(lang: Language) with the current default language. The Entity class also implements toString() by calling toText(). This way, you can directly use the entity as part of a string in a response, as we have seen before:

onResponse<BuyFruit> {
    furhat.say("${it.intent.fruit}, what a lovely choice")
    reentry()
}

The base class Entity has a property "text", which stores the text string used to identify the entity the user's utterance. If it is filled in (i.e., if the entity was extracted from the user's utterance), this is used per default to generate the text for the entity. If not, different entities have different ways of generating text. You can also override the toText(lang: Language) method in your entity if you want to generate text with some specific method.

Intents do not implement the TextGenerator interface, so they cannot be used directly in this way (toString() doesn't return anything readable). However, you can implement the TextGenerator interface in your intent:

class BurgerOrder(    
    val main : MainCourse? = null,
    val drink : Drink? = null,
    val side : SideOrder? = null,
    val payment : Payment? = null) : Intent(), TextGenerator {

    override fun getExamples(lang: Language): List<String> {
        return listOf(
                "a @drink please",
                "I want a @main with @drink",
                "I would like a @main",
                "I want a @main with @side")
    }

    override fun toText(lang : Language) : String {
        return generate(
                "($main with $side and $drink | $main with $side | $main with $drink | $main)[, paying with $payment]");
    }

    override fun toString(): String {
        return toText()
    }
}

Note that you should also implement toString() (and call the toText() method) in order to be able to use the intent directly as part of a string.

In the toText(lang: Language) method, you can take advantage of the generate() method provided in the TextGenerator interface. It parses the provided string and applies some useful logic:

  • It will not generate strings that contain the "null" word. So, if a variable returns "null", the generation will normally fail.
  • You can provide several possible strings to generate, separated by a pipe ("|"). It will then choose the first string that does not fail.
  • You can group parts of the string with parentheses
  • If you group a part of the string with brackets, the generation will not fail if the brackets contain the "null" word, instead the brackets will just generate an empty string.

In the example above, this means that it will first try to generate "$main with $side and $drink". If any of these variables returns null, it will try to generate "$main with $side", and so on. After this, it will append the string ", paying with $payment" if payment is not null, otherwise it will not append anything.

Pre-loading and reloading intents and entities

Intents and entities are normally loaded/initialized the first time they are used, on state entry. If you have some dynamic property in these intents, this means that there might be a short delay if this initialization takes time - for example if you do an API call do fetch examples or if you simply have a very long list of examples.

Pre-loading of intents and entities

To cope with the above mentioned cases, you might want to preload/pre-initialize your intents. A good time to do this may be on skill startup or at some other time that makes sense for your use-case.

To do this, you use YourIntent().preload(language), for example in your first state's init method as shown below:

// Entity with enums fetched from an API
class MyEntity : EnumEntity() {
    override fun getEnum(lang: Language): List<String> {
        return mySlowAPI.fetchEntityEnums(this.javaClass.simpleName, lang)
    }
}

// Intent with examples fetched from an API. Note that the entity needs to be nullable
class MyIntent(entity: MyEntity? = null) : Intent() {
    override fun getExamples(lang: Language): List<String> {
       return mySlowAPI.fetchIntentExamples(this.javaClass.simpleName, lang)
   }
}

// Initial state that preloads the above intent and entity for English US
val myFirstState : State = state {
    init {
      MyIntent().preload(Language.ENGLISH_US)
    }

    onEntry {
      goto(InteractiveState)
    }
}

If you want to manually pre-load/initialize entities without them being part of intents as above, you can use Interpreter.preload(MyEntity.class, language) (we will update this method shortly to a more friendly API similar to that of intents).

Reload intents and entities

If your intents and entities needs to be refreshed at some point, for example if you dynamically update the examples of an intent, or the enums of an entity, you can use the methods forget() and forgetAll() available for Intents and Entities and then either let the system automatically reload them the next time they are used, or explicitly do it at some time of your choosing.

// Some state where we for some reason want to forget our intents. They will then be re-initialized the next time they are used, or when they are initialized manually

val refreshedContent : State = state {
    onEntry {
      // You can forgot individual intents and entities ...
      MyIntent.forget()
      MyEntity.forget()

      // If you want, you can reload them manually as shown above
      MyIntent.preload(Language.ENGLISH_US)

      // ... or forget all intents and entities
      Intent().forgetAll()
      Entity().forgetAll()
    }
}

Note that you explicitly have to forget entities even if they are loaded/initialized through an intent. The reason is that you might use the entities elsewhere and you may not want to forget them automatically.