Provides natural language understanding/processing to enable easy implementation of chat bots and voice services. High performance run time in only 2 lines of code - 'require' to include it, and the call to process the text. These can run anywhere Node.js
npm install vui-ad-hoc-alexa-recognizer






Provides natural language understanding/processing capability to enable easy implementation of chat bots and voice services.
High performance run time in only 2 lines of code - 'require' to include it, and the call to process the text.
These can run anywhere Node.js is running - backend, browser, mobile apps, etc with or without internet connection.
Has a rich set of built in intents and extensible slots (equivalent to Alexa's),
custom slots (both list based and regular expression based), synonyms, slot flags, parametrized flags, transformation functions,
SoundEx matching, wild card matching, option lists, text equivalents sets, mix-in post processing, sentiment analysis,
unlimited sets of recognizers to build large
segmented apps, domains with state specific processing, builtin and custom chainable responders, sub-domains (trusted and
non-trusted), etc.
I have finally gotten to a point where the major features are at a good spot and I can spare some time for
tutorials, examples, and documentation.
To that end I have set up a web site on GitHub pages and included the first set of tutorials, with more on the way.
Here is the website:
https://rationalanimal.github.io/vui-ad-hoc-alexa-recognizer/
The first set of tutorials deals with the lower level, i.e. recognizer functionality:
* "Hello World" and "Using Hello World" - these two tutorials together comprise the usual "Hello World" example -
one shows how to configure, generate, and test a recognizer.json file.
The other tutorial shows how to write your own code to use the generated file.
* "You Have Options... Option Lists, That Is" - this tutorial shows how to avoid having to manually enter and maintain a
large number of related utterances.
* "Great Intrinsic Value" - this one shows how to get information back from the user via BUILT IN slot types.
* "Let's Talk" - here the user is shown how to build an actual chat bot - a small app that takes input from the user,
parses it, stores some values in the state for future use, and responds to the user based on the parsed
information and state.
* "Count on it" - introduces the use of what is probably the singularly most useful built in slot type - NUMBER.
* "He Spoke Bespoke" - deals with how to define the simplest type of a custom slot - list based custom slot.
* "Known Intentions" - deals with another built in feature: shows how to use, configure, and turn off built in intents.
* "It's All the Same to Me" - demonstrates custom slots that use synonyms to simplify the code that has to deal with
multiple values that map to a smaller subset.
* "Wild (Card) Thing" - explains how to get values from the user that are not part of the "accepted" set, i.e. using
wildcard matches.
* "Express Yourself Regularly" - explains how to use regular expressions based custom slots to get user input that
matches a particular pattern rather than specific list of values.
* "That Sounds Like You" - explains how to use SOUNDEX matching to process words that sound similar. This helps with
commonly substituted words in chat bots and words that sound the same but aren't spelled the same way in voice services.
* "Six of One, Half a Dozen of the Other" - covers text equivalents sets and their use. Text equivalents feature lets you
define words and phrases that are equivalent to each other so that you need only to specify a simple utterance and
have vui-ad-hoc-alexa-recognizer match on any variation of that utterance that may result from using text equivalents.
This allows solving many different issues - from typos, to homophones, to special vocabularies, etc...
* "Would You Like Some Fries with That?" - introduces the concept of "mix in" (aka "add on") processing.
Shows how you can use mix ins to cleanly separate matching and business logic, change matching through configuration and
mix in application, add logging, and more.
Finally, here is the first domain tutorial:
* "Hello Domain" - this is a very simple, introductory example of a domain that doesn't use the state nor any of the
advanced features, but does respond to every handled intent without the need for any conversation specific code.
In addition to tutorials I will from time to time publish articles related to either chatbots/voice services in general
or vui-ad-hoc-alexa-recognizer in particular (or some of both). They are located on the same website as the tutorials.
Here is the first of these:
* "Better Way of Building Better Chatbots" at https://rationalanimal.github.io/vui-ad-hoc-alexa-recognizer/Articles/BetterWayOfBuildingBetterChatbots/
``shell`
npm install vui-ad-hoc-alexa-recognizer --save
This module provides the ability to match user's text input (possibly from speech to text source) to an intent with
slot values as well as the ability to configure return values and state updates right within it.
There are two levels of functionality - a lower level allowing a match of a string against a single recognizer, returning
an intent match with parsed out slot values and a higher level "domain" functionality that allows configuring an
entire app, returning not just a match but results and even updating the current state.
Keep in mind that many text parsing tasks can be trivially configured as intents/utterances even if you have no intention
of building a chat bot or a voice service. For example, if you wanted
to parse spelled out numbers, or even combinations of spelled out numbers and numerals, you can easily setup to do it like this:
utterances:
`text`
NumberIntent {NumberSlot}
intents:
`json`
{
"intents": [
{
"intent": "NumberIntent",
"slots": [
{
"name": "NumberSlot",
"type": "AMAZON.NUMBER"
}
]
}
]
}
Now, you can call the match() function, pass it any combination of spelled out and numerals, and it will return
a match on NumberIntent with the value of the NumberSlot being set to the parsed number:
`shell`
node matcher.js "51 thousand 2 hundred sixty 3"`json`
{
"name": "NumberIntent",
"slots": {
"NumberSlot": {
"name": "NumberSlot",
"value": "51263"
}
}
}
Note that the Number slot is coded to be able to accept both "normal" numbers and numbers that people spell out digit by
digit or groups of digits, such as zip codes or phone numbers. So "one two three four five" will parse as "12345", etc.
This does mean that occasionally there may come up a way to parse the same expression in more than one way and the attempt
is made to make the most likely match.
Similarly, you can parse dates, etc. even if that's the only thing you want to do (e.g. you have an app where the user
can type in a date - simply use vui-ad-hoc-alexa-recognizer to parse it and return a date). Dates will match not only the
exact date specification, but strings such as "today", etc.
Domains are a higher level of parsing than recognizers. Domains do use "recognizer" parsing, but add the follow abilities:
* define a list of recognizers to be used
* define application state-based conditions for using a particular match (e.g. only test against a particular recognizer if you are in a specific state)
* allow returning of results in addition to simply matching on an intent (e.g. if the user says "How are you doing?", not only will it match on a greeting intent, but also will return "Good, and you?")
* allow updating of the application state right within the matching code rather than having to write the extra code to do it (e.g. if the user says "My name is Bob" then some portion of the state will be set to "Bob" by the domain handling code)
* allow nesting of the domains. This is particularly useful as whole types of interactions can be encapsulated as domains and then reused. It also allows breaking large apps into smaller chunks, i.e. domains.
It has two pieces of functionality:
* run it offline to generate a recognizer.json file that will be used in matching/parsing the text
* add two lines of code to your app/skill to use it to match the raw text at run time using the generated recognizer.json file.
Imagine you already have an Alexa skill and you would like to port it to Cortana
or Google Assistant (or even if you don't but want to create a chat bot/service from scratch).
Here are examples of files that you will have for your
Alexa skill or will need to create if you don't have any yet
(these are NOT complete files, you can find the complete sample
files in the test directory):
`shell`
cat test/utterances.txt``
TestIntent test
TestIntent test me
TestIntent test please
TestIntent test pretty please
TestIntent test pleeeeeeease
MinionIntent One of the minions is {MinionSlot}
MinionIntent {MinionSlot}
StateIntent {StateSlot}
StateIntent New England includes {StateSlot} as one of its states
BlahIntent here is my number {BlahSlot}, use it wisely. And here is another one {BlehSlot}, don't squander it
BlahIntent here is {BlahSlot} and {BlehSlot}
AnotherIntent First is {SomeSlot} and then there is {SomeOtherSlot}`shell`
cat test/intents.json`json`
{
"intents": [
{
"intent": "TestIntent",
"slots": []
},
{
"intent": "BlahIntent",
"slots": [
{
"name": "BlahSlot",
"type": "AMAZON.NUMBER"
},
{
"name": "BlehSlot",
"type": "AMAZON.NUMBER"
}
]
},
{
"intent": "AnotherIntent",
"slots": [
{
"name": "SomeSlot",
"type": "SOME"
},
{
"name": "SomeOtherSlot",
"type": "SOMEOTHER"
}
]
},
{
"intent": "MinionIntent",
"slots": [
{
"name": "MinionSlot",
"type": "MINIONS"
}
]
},
{
"intent": "StateIntent",
"slots": [
{
"name": "StateSlot",
"type": "AMAZON.US_STATE"
}
]
}
]
}
and also here is an example of a custom slot type file:
`shell`
cat test/minions.txt``
Bob
Steve
Stewart
Currently, Amazon is beta testing a new GUI tools for editing skills' interaction models. This produces a single json
file that includes ALL the information for the skill - intents, utterances, custom slot types, and some "new" bits:
prompts/dialogs/confirmations. The support for this is being added to various parts of vui-ad-hoc-alexa-recognizer
(including the generator and alexifyer) and is functional. However, until Amazon finishes the beta testing, this will not be finalized and
may change.
* NOTE: AT THIS TIME I DO NOT RECOMMEND USING INTERACTION MODEL FILES - WHILE I HAVE FINISHED THE SUPPORT, I HAVE NOT TESTED IT FULLY
The first step is to generate a run time file - recognizer.json. This file has
all the information that is needed to parse user text later. To create it, run
the generator, e.g.:
`shell`
node generator.js --intents test/intents.json --utterances test/utterances.txt --config test/config.json
(the example intents.json, utterances.txt, and config.json files are included in the test directory)
This will produce a recognizer.json in the current directory.
Additionally, there is beta support for the beta interaction model builder by Amazon. To use it specify --interactionmodel
parameter instead of --intents and --utterances:
`shell`
node generator.js --interactionmodel test/interactionmodel.json --config test/config.json
* NOTE: AT THIS TIME I DO NOT RECOMMEND USING INTERACTION MODEL FILES - WHILE I HAVE FINISHED THE SUPPORT, I HAVE NOT TESTED IT FULLY
Note that you can use the extra features in the interactionmodel.json file just as you could with intents.json and
utterances.txt (e.g. options lists, slot flags, TRANSCEND specific slot types). Simply use alexifyutterances.js (see later) to prepare interactionmodel.json for import into Alexa
developer console.
For usage, simply run the generator without any arguments:
`shell`
node generator.js
and the generator command will list the needed arguments, e.g.:
`shell`
Usage: node /Users/ilya/AlexaProjects/vui-ad-hoc-alexa-recognizer/vui-ad-hoc-alexa-recognizer/generator.js:
--sourcebase BaseSourceDirectory that is the base for the other file references on the command line or in the config file. This will be used for both build and run time source base unless overridden by other command line arguments.
--buildtimesourcebase BuildTimeBaseSourceDirectory that is the base for the other file references on the command line or in the config file at build time. Will override --sourcebase value for build time directory, if both are supplied
--runtimesourcebase RunTimeBaseSourceDirectory that is the base for the other file references (e.g. in the config file) at run time. Will override --sourcebase value for run time directory, if both are supplied
--vuibase BaseVuiDirectory that is the location of vui-ad-hoc-alexa-recognizer. This will be used for both build and run time vui base unless overridden by other command line arguments. Defaults to ./node_modules/vui-ad-hoc-alexa-recognizer
--buildtimevuibase BuildTimeBaseVuiDirectory that is the location of vui-ad-hoc-alexa-recornizer executable files at build time. Will override --vuibase value for build time directory, if both are supplied
--runtimevuibase RunTimeBaseVuiDirectory that is the location of vui-ad-hoc-alexa-recognizer executable files at run time. Will override --vuibase value for run time directory, if both are supplied
--runtimeexebase RunTimeBaseExeDirectory that is the location of javascript executable files at run time.
--config ConfigFileName specify configuration file name, optional. If not specified default values are used.
--intents IntentsFileName specify intents file name, required. There is no point in using this without specifying this file.
--utterances UtterancesFileName specify utterances file name, optional. This is "optional" only in the sense that it CAN be omitted, but in practice it is required. There only time you would invoke this function without an utterance file argument is if your skill generates only build in intents, which would make it rather useless.
--optimizations [SINGLE-STAGE] optional. SINGLE-STAGE means no pre-matches using wildcards. Depending on the recognizer, this may be slower or faster
--suppressRecognizerDisplay does not send recognizer.json to console
Note here that you should already have the intents.json and utterances.txt files
as these files are used to configure the Alexa skill.
Also, you can specify how to parse built in intents in the config.json.
For example:
`json`
{
"builtInIntents":[
{
"name": "AMAZON.RepeatIntent",
"enabled": false
}
]
}
will turn off parsing of the AMAZON.RepeatIntent.
You can also specify additional utterances for built in intents, either directly
in the config file or in an external file:
`json`
{
"builtInIntents":[
{
"name": "AMAZON.StopIntent",
"enabled": true,
"extendedUtterances": ["enough already", "quit now"],
"extendedUtterancesFilename": "test/stopIntentExtendedUtterances.txt"
}
]
}
Similarly you can affect built in slot types using config:
`json`
{
"builtInSlots": [
{
"name": "AMAZON.US_FIRST_NAME",
"extendedValues": [
"Prince Abubu"
],
"extendedValuesFilename": "test/usFirstNameExtendedValues.txt"
}
]
}
This will add "Prince Abubu" and whatever names are found in test/usFirstNameExtendedValues.txt
file to the list of first names recognized by the AMAZON.US_FIRST_NAME slot.
The second step is to use recognizer.json file at run time to parse the user
text and produce the output json that can be used to set the intent portion of
the request json.
You only need 2 lines of code to be added to your app to use it:
`js`
let recognizer = require("vui-ad-hoc-alexa-recognizer");
let parsedResult = recognizer.Recognizer.matchText("Some text to match to intent");
(If this is not working, check to make sure you have generated your recognizer.json
first and that it's located in the same directory where your code is.)
Note that there are additional arguments to matchText(). You can specify sorting
order, excluded intents, and a different recognizer file.
You can also use it from the command line to test your configuration and to see
the matches. For an example of how to use it (assuming you cloned the code from
GitHub and ran "npm test" to have it configured with the test samples), try:
`shell`
node matcher.js "Bob"
which will produce:
`json`
{
"name": "MinionIntent",
"slots": {
"MinionSlot": {
"name": "MinionSlot",
"value": "Bob"
}
}
}
or you could specify a particular recognizer file to use, e.g.:
`shell`
node matcher.js "Bob" "./recognizer.json"
or try
`shell`
node matcher.js "here is four hundred eighty eight million three hundred fifty two thousand five hundred twelve and also six oh three five five five one two one two"
which will produce:
`json`
{
"name": "BlahIntent",
"slots": {
"BlahSlot": {
"name": "BlahSlot",
"value": "488352512"
},
"BlehSlot": {
"name": "BlehSlot",
"value": "6035551212"
}
}
}
`shell`
node matcher.js "thirty five fifty one"
which will produce:
`json`
{
"name": "FourDigitIntent",
"slots": {
"FooSlot": {
"name": "FooSlot",
"value": "3551"
}
}
}
`shell`
node matcher.js "sure"
which will produce:
`json`
{
"name": "AMAZON.YesIntent",
"slots": {}
}
`shell`
node matcher.js "New England includes New Hampshire as one of its states"
which will produce:
`json`
{
"name": "StateIntent",
"slots": {
"StateSlot": {
"name": "StateSlot",
"value": "New Hampshire"
}
}
}
`shell`
node matcher.js "My first name is Jim"
which will produce:
`json
{
"name": "FirstNameIntent",
"slots": {
"FirstNameSlot": {
"name": "FirstNameSlot",
"value": "Jim"
}
}
}
`
`shell`
node matcher.js "December thirty first nineteen ninety nine"
which will produce:
`json`
{
"name": "DateIntent",
"slots": {
"DateSlot": {
"name": "DateSlot",
"value": "1999-12-31"
}
}
}
`shell`
node matcher.js "lets do it on tuesday"
which will produce:
`json`
{
"name": "DayOfWeekIntent",
"slots": {
"DayOfWeekSlot": {
"name": "DayOfWeekSlot",
"value": "tuesday"
}
}
}
Please note that matcher.js is just a convenience and also serves as an example.
You will NOT be using it at run time (most likely, though some might find the use
for it).
If you are porting an existing Alexa skill (to, for example, Cortana),
you will probably deploy your code (that uses the parser) to some middleware layer, like this:
``
Alexa --------------> Alexa <-- middleware <---- Cortana
Skill <-------------- AWS Lambda --> AWS Lambda ----> Skill
Where in the middleware AWS Lambda (exposed via API Gateway) you will be able to
see the raw user text from Cortana (passed as the "message" field in the request),
then call it same way matcher.js does, get the resulting json and update the
intent in the request from "None" to the resulting json. The backend Lambda can
then process it further.
If you are using vui-ad-hoc-alexa-recognizer for new development you have two main options:
* use it just for NLP to map utterances to intents, which you will then use for branching code
* use higher level domain functionality to define much of your app/skill
There are differences between what kind of values different services may send.
Alexa appears to respect capitalization of the custom slot values (or it sends
lower case versions) while Cortana capitalizes the first letter under some
circumstances, but not always, and also adds a period at the end of utterances
or even other punctuation signs (I've seen entering a zip code changed from
"12345" to "Is 12345 ?"). To keep Cortana (as well as other "misbehaving" services)
behaving consistently, the returned matches use the capitalization of the custom
slot values supplied in the config file rather than what Cortana will send.
Thus, if your custom slot value (in the config file) is "petunia" then "petunia"
will be returned even if Cortana will send you "Petunia".
In some cases you would like to match on a particular slot differently from the
standard algorithm. For example, if you are trying to get the user's first name
you may way to match on ANYTHING the user says so that unusual names are matched.
In this case you can modify your utterances file to include special flags, e.g.:
``
FirstNameIntent My first name is {FirstNameSlot: INCLUDE_WILDCARD_MATCH, EXCLUDE_VALUES_MATCH }
These flags will be used in parsing. Here are the different currently available
flags:
1. "INCLUDE_VALUES_MATCH", "EXCLUDE_VALUES_MATCH" - to include/exclude custom
slot values in the matching pattern.
2. "INCLUDE_WILDCARD_MATCH", "EXCLUDE_WILDCARD_MATCH" - to include/exclude
a wildcard in the matching pattern.
3. "SOUNDEX_MATCH" - to use SoundEx for matching (will match on values that sound like desired values, but are not necessarily spelled the same)
4. "INCLUDE_SYNONYMS_MATCH", "EXCLUDE_SYNONYMS_MATCH" - to include/exclude
synonym values in the matching pattern. This is only relevant for custom slot types that actually use synonyms.
5. "EXCLUDE_YEAR_ONLY_DATES" - this flag is only applied to the AMAZON.DATE type
slot and turns off parsing of a single number as a year. This is useful when
there are otherwise identical utterances that may match on a number or on a date. If the year only
match is allowed then there is no way to differentiate between the two.
6. "EXCLUDE_NON_STATES" - this flag is only applied to the AMAZON.US_STATE type
slot and turns off parsing of US territories and D.C.
7. "STATE" - this is a parametrized flag (see below). currently
it only applies to the AMAZON.Airport slot type and it restricts the matches to
the specified states.
8. "COUNTRY" - this is a parametrized flag (see below). currently
it only applies to the AMAZON.Airline and AMAZON.Airport slot types and it restricts the matches to
the specified countries.
9. "CONTINENT", "TYPE" - these are parametrized flags (see below). currently
they only apply to the AMAZON.Airline slot type and they restrict the matches to
the specified types and continents.
10. "SPORT", "LEAGUE" - these are parametrized flags (see below). currently
they only apply to the AMAZON.SportsTeam slot type and they restrict the matches to
the specified sports and leagues.
11. "INCLUDE_PRIOR_NAMES", "EXCLUDE_PRIOR_NAMES" - Currently these only apply to the AMAZON.SportsTeam and AMAZON.Corporation
slot type and they include/exclude the prior team (or corporation) names in the search. Default is EXCLUDE_PRIOR_NAMES.
If you don't specify any of these, then
INCLUDE_VALUES_MATCH and EXCLUDE_WILDCARD_MATCH will be used as the default. Also,
if you include by mistake both INCLUDE... and EXCLUDE... for the same flag, the
default value is (silently) going to be used. If you are concerned you can look
at the generated recognizer.json to see how the flags were parsed.
Also note that SOUNDEX_MATCH will automatically imply EXCLUDE_VALUES_MATCH and
EXCLUDE_WILDCARD_MATCH flags; however SOUNDEX_MATCH is only available for the
custom slot types at this time.
It would typically not be useful (at this time with only these sets of flags)
to specify INCLUDE... for both or EXCLUDE... for both wildcard and value matches
unless you are specify SOUNDEX_MATCH. If you are going to include
wildcard then there is no reason to include values as well - it will only slow
down the parsing. If you exclude both then it will be the same as if you had removed that slot
from the utterance completely. For this reason, parsing ignores these combinations.
If you specify INCLUDE_WILDCARD_MATCH then only the wild card will be used.
If you specify both EXCLUDE_VALUES_MATCH and EXCLUDE_WILDCARD_MATCH then only
EXCLUDE_WILDCARD_MATCH is used.
Also note that you have to be very careful when using wildcards. For example,
imagine this utterance instead of the above example:
``
FirstNameIntent {FirstNameSlot:INCLUDE_WILDCARD_MATCH,EXCLUDE_VALUES_MATCH}
This will match on ANYTHING the user says. So, DON'T use wildcards in "naked"
utterances (ones that use nothing but slots) unless you are absolutely SURE that
that is what you want. This is why these flags exist at the utterance level rather
than intent level.
Also, you should probably NOT specify wildcard matches on slots of many of the
built in intents, such as date or number - this will likely not end well and
it doesn't make sense. For this reason, parsing ignores these flags at this
time on most of these slot types. Parsing will also ignore SOUNDEX_MATCH on non-custom
slot types (though this may be added in the future for some built in types).
In the future there will be other flags added, possibly specific to
particular built in slot types (e.g. I may add a flag to return only the female
first names from the AMAZON.US_FIRST_NAME type slot or numeric values within
a certain range from the AMAZON.NUMBER type slot).
`shell`
AirlineIntent {AirlineSlot:COUNTRY(["canada"])} is a canadian airline
AirlineIntent {AirlineSlot:CONTINENT(["north america"])} is a north american airline
then only Canadian airlines will match the first one, and only north american
airlines will match the second one.
You can create a very simple custom slot type based on a list of simple values, loaded either from config.json or a separate
text file. But you can also load "values" which are objects. These objects must themselves contain a "value" field
that replaces the simple field. In addition, these objects can also contain a field "synonyms" which must be an array
of strings. For example, here is a custom slot type defined in a config.json:
`json`
{
"name": "KITCHENSTUFF",
"values": [
"Spoon",
{
"value": "Pan",
"synonyms": [
"Skillet"
]
}
]
}
This will match on "spoon", "pan", and "skillet". Furthermore, and this is the real value of the synonyms, when
matching on the "skillet", the actual returned value will be "Pan" (otherwise you could have simply added more values
instead of using synonyms).
Couple of important points:
* You can mix strings and objects within config.json
* If you want to specify json objects in a separate file then you MUST use a file with a .json extension and it must
contain valid json
* Whatever you specify in a file that does NOT have .json extension will be loaded as plain strings (one per line) even
if it contains valid json
#### Synonyms and SOUNDEX
Custom slot type that has synonyms will work with SOUNDEX flag just like one without synonyms.
In addition to the normal custom type slots - one based on a list of values - you can also define a custom slot type
based on a regular expression. This might be useful if you are looking for some value that has a particular format.
For example, a serial number for a product might have a specific format and you may be looking for it in user input.
It would be impractical to specify all the serial numbers even if you had the up to date list. Instead, you can define
a custom slot that will match the regular expression for the serial number and return it.
E.g. given config.json:
`text`
...
{
"name": "CUSTOMREGEXP",
"customRegExpString": "(ABC123|XYZ789)"
}
...
and otherwise standard intents and utterances files, when
`shell`
node matcher.js "here is XYZ789 if you see it"
will produce:
`json`
{
"name": "CustomRegExpIntent",
"slots": {
"CustomRegExpSlot": {
"name": "CustomRegExpSlot",
"value": "XYZ789"
}
}
}
You can also load the reg ex for a custom slot from a file. This can be useful for sharing the same reg ex between many
different recognizers. To do this, use customRegExpFile member instead of customRegExpString:
`text`
...
{
"name": "CUSTOMREGEXP",
"customRegExpFile": "customRegExpFile.txt"
}
...
`text`
DateIntent I {want to|wish to|like to|would like to|can} meet {you|with you} {DateSlot}
is equivalent to these:
`text`
DateIntent I want to meet you {DateSlot}
DateIntent I want to meet with you {DateSlot}
DateIntent I wish to meet you {DateSlot}
DateIntent I wish to meet with you {DateSlot}
DateIntent I like to meet you {DateSlot}
DateIntent I like to meet with you {DateSlot}
DateIntent I would like to meet you {DateSlot}
DateIntent I would like to meet with you {DateSlot}
DateIntent I can meet you {DateSlot}
DateIntent I can meet with you {DateSlot}
Note that you can specify an options list that's optional by omitting one value:
`text`
DateIntent I {want to|wish to|like to|would like to|can} meet {|you|with you} {DateSlot}
will match on (for instance):
`text`
I want to meet tomorrow$3
Similarly to the options list, text equivalents allow variations in the text to be matched. Unlike the options list
you don't have to manually add all of the possibilities. Instead, simply enclose the text that should match using
equivalents and this module will do it for you, e.g.:
`text`
HiIntent {~Hi what time is it}
is equivalent to these:
`text`
HiIntent Hi what time is it
HiIntent Hello what time is it
HiIntent Hey what time is it
HiIntent How are you what time is it
HiIntent Good morning what time is it
HiIntent Good day what time is it
HiIntent Good night what time is it
HiIntent Hi there what time is it
...etc..
At this time the following implementation is in place: it uses two data sets - a very small default data set and a
common misspellings data set. It will match both single
word substitutions AND phrase substitutions. This will soon be expanded to include
an independent npm module containing additional values (so that they can be updated independently of this module) as well
as the ability to add your own modules to support special "domain" equivalents. For example, you can add "slang" data
set or "medical jargon" data set.
So, an example similar to the above that substitutes both the phrases and the individual words and uses multiple data
sets (e.g. correcting for typos, skipping optional words like please, etc) could be:
`text`
HiTimeIntent {~How are you can you tell me please what is the acceptable time to come to work}
is equivalent to these:
`text`
HiTimeIntent how are you can you tell me please what is the acceptable time to come to work
HiTimeIntent how are you doing can you tell me please what is the acceptable time to come to work
HiTimeIntent how are you can you tell me what is the acceptable time to come to work
HiTimeIntent how are you doing can you tell me what is the acceptable time to come to work
HiTimeIntent how are you can you tell me please what is the acceptible time to come to work
HiTimeIntent how are you doing can you tell me please what is the acceptible time to come to work
HiTimeIntent how are you can you tell me what is the acceptible time to come to work
HiTimeIntent how are you doing can you tell me what is the acceptible time to come to work
HiTimeIntent hi can you tell me please what is the acceptable time to come to work
HiTimeIntent hello can you tell me please what is the acceptable time to come to work
HiTimeIntent good morning can you tell me please what is the acceptable time to come to work
HiTimeIntent good day can you tell me please what is the acceptable time to come to work
HiTimeIntent good evening can you tell me please what is the acceptable time to come to work
HiTimeIntent good night can you tell me please what is the acceptable time to come to work
HiTimeIntent whats up can you tell me please what is the acceptable time to come to work
HiTimeIntent hey can you tell me please what is the acceptable time to come to work
HiTimeIntent hi can you tell me what is the acceptable time to come to work
HiTimeIntent hello can you tell me what is the acceptable time to come to work
HiTimeIntent good morning can you tell me what is the acceptable time to come to work
HiTimeIntent good day can you tell me what is the acceptable time to come to work
HiTimeIntent good evening can you tell me what is the acceptable time to come to work
HiTimeIntent good night can you tell me what is the acceptable time to come to work
HiTimeIntent whats up can you tell me what is the acceptable time to come to work
HiTimeIntent hey can you tell me what is the acceptable time to come to work
HiTimeIntent hi can you tell me please what is the acceptible time to come to work
HiTimeIntent hello can you tell me please what is the acceptible time to come to work
HiTimeIntent good morning can you tell me please what is the acceptible time to come to work
HiTimeIntent good day can you tell me please what is the acceptible time to come to work
HiTimeIntent good evening can you tell me please what is the acceptible time to come to work
HiTimeIntent good night can you tell me please what is the acceptible time to come to work
HiTimeIntent whats up can you tell me please what is the acceptible time to come to work
HiTimeIntent hey can you tell me please what is the acceptible time to come to work
HiTimeIntent hi can you tell me what is the acceptible time to come to work
HiTimeIntent hello can you tell me what is the acceptible time to come to work
HiTimeIntent good morning can you tell me what is the acceptible time to come to work
HiTimeIntent good day can you tell me what is the acceptible time to come to work
HiTimeIntent good evening can you tell me what is the acceptible time to come to work
HiTimeIntent good night can you tell me what is the acceptible time to come to work
HiTimeIntent whats up can you tell me what is the acceptible time to come to work
HiTimeIntent hey can you tell me what is the acceptible time to come to work
Note that the matching algorithm is pretty efficient and does NOT actually try to match on these utterances, but instead
uses a single regular expression.
`shell`
node alexifyutterances.js --utterances test/utterances.txt --intents test/intents.json --output testutterances.txt --noconfig
Result was saved to testutterances.txt
You can now import testutterances.txt into the Alexa developer console.
Note that not only will alexifyutterances.js remove flags, it will also "unfold"
options lists into multiple utterances as well as "unfold" any text equivalents so that you can use them with Alexa.
This feature would be useful even if you only want to use this module to
reduce the tedium of entering multiple lines into Alexa and don't even
intent to create your own chat bot or convert your Alexa skill.
There is also support for the BETA Amazon interaction model editor. You can edit the files it generates to add
features supported by this module. E.g. you can options lists or slot flags or even TRANSCEND native slot types.
Then run it through the alexifyutterances.js and the result will be importable back into Alexa console:
`shell`
node alexifyutterances.js --interactionmodel test/interactionmodel.json --output alexifiedmodel.json --noconfig
Result was saved to alexifiedmodel.json
Many of the list slots (e.g. AMAZON.Actor) have very large value lists. These
are often not needed in a typical vui skill. Thus, a compromise support is
provided for them. They are there and can be used, but they only have a few
values. If you actually do have a need for them, you have two options:
1. You can provide your own expansion list of values in the config.json file
2. You can use wildcard slot matching to match on any value the user can provide
#### Custom transform functions
You can transform matched values before returning them. You do this by specifying
transform functions in the config file, here are examples for the built in and
custom slot types:
`json`
{
"customSlotTypes":[
{
"name": "SOME",
"values": [
"apple",
"star fruit",
"pear",
"orange"
],
"transformSrcFilename": "./test/transformSome.js"
}
],
"builtInSlots": [
{
"name": "AMAZON.US_STATE",
"transformSrcFilename": "./test/transformUsState.js"
},
{
"name": "AMAZON.Month",
"transformSrcFilename": "./test/transformFirstWordTitleCase.js.js"
}
]
}
You then put into the specified "transformSrcFilename" file the source code for
the function to do the transformation.
Then, when you type:
`shell`
node matcher.js 'january is the best month'
you will get (note the capitalized first letter of the month):
`json`
{
"name": "MonthIntent",
"slots": {
"MonthSlot": {
"name": "MonthSlot",
"value": "January"
}
}
}
See the test directory for more examples.
There are many reasons you may want to do this: transforming states into postal
code or fixing issues with speech recognition, etc.
For example, a particular service may not understand some spoken phrases well.
One that I've ran into is the word "deductible" is understood to be "the duck tibble".
This will never match. Well, you could add this to your list of acceptable values.
This will only solve half a problem. Once you match it and send it to your Alexa
backend, it will choke on this. So, you can add a transform function to map
"the duck tibble" to "deductible" before sending it off to Alexa backend.
When you write a custom transform function be aware that it has this signature:
`javascript`
function someTransformFunction(value, intentName, slotName, slotType){/ do something and return transformed value/};
And that it returns a transformed value (or undefined if the input value is undefined or null).
You can use the other arguments to change how your function may transform the matched value.
For example, you may specify a particular transform function of a slot type, but you may check within your function
that the slot name equals a particular slot name and change the transformation.
#### Built in transform functions
In addition to being able to write your own custom transform functions you can also use some built in ones.
The current list is:
`text`
addAngleBrackets - surrounds the matched value with <>
addCurlyBrackets - surrounds the matched value with {}
addParentheses - surrounds the matched value with ()
addSquareBrackets - surrounds the matched value with []
codeToState - converts passed in US state postal code to the corresponding state name. Does NOT convert territories.
formatAsUsPhoneNumber1 - formats TRANSCEND.US_PHONE_NUMBER matched value as (111) 111-1111
formatAsUsPhoneNumber2 - formats TRANSCEND.US_PHONE_NUMBER matched value as 111.111.1111
formatAsUsPhoneNumber3 - formats TRANSCEND.US_PHONE_NUMBER matched value as 111 111 1111
formatAsUsPhoneNumber4 - formats TRANSCEND.US_PHONE_NUMBER matched value as 111-111-1111
removeDigits - removes all digits from the matched value
removeDollar - removes all occurrences of $s from the matched value
removeNonDigits - removes all non-digits from the matched value
removeNonAlphanumericCharacters - removes all non alphanumeric characters from the matched value
removeNonWordCharacters - same as removeNonAlphanumericCharacters, but allows underscore. Removes anything that's not a number, letter, or underscore from the matched value.
removePeriod - removes all occurrences of . from the matched value
removePoundSign - removes all occurrences of #s from the matched value
removeWhiteSpaces - removes all continuous sequences of any white space characters from the matched value
replaceWhiteSpacesWithSpace - replaces all continuous sequences of any white space characters in the matched value with a single space
stateToCode - converts passed in US state name to the corresponding postal code. Does NOT convert territories.
toLowerCase - converts the matched value to lower case
toUpperCase - converts the matched value to upper case
You can also
see the currently available ones in the builtintransforms directory.
To use them, specify "transformBuiltInName" member instead of the "transformSrcFilename":
`json`
{
"name": "MEANINGLESS",
"values": [
"foo",
"bar"
],
"transformBuiltInName": "toUpperCase"
}
#### Chaining transform functions
Both custom and built in transform functions can be chained simply by specifying an array instead of a single value in
the configuration file, for example:
`json`
{
"name": "MEANINGLESS",
"values": [
"foo",
"bar"
],
"transformBuiltInName": ["toUpperCase", "addParentheses", "addSquareBrackets"]
}
will apply all the specified transforms.
Sometimes you may want to do some additional processing of the result before returning it. It could be almost anything,
for example:
* add logging to all matches
* compute sentiment score and add it to the result
* adjust/update/replace matched slot values
and many other possible examples.
Mix-in (or add on) processing allows you to do that and you can do it mostly through configuration (some coding may be
required)
#### Built in mix-ins
Currently there are only about seven built in mix ins. Here is the list with a short description for each:
* adddefaultslots - can be used to inject slot(s) with hard coded values
* changeintent - can be used to change the matched intent to another one
* charactercount - counts the characters in the matched utterance and attaches this count to the result
* countregexp - counts the occurence of the specified reg exp and attaches this count to the result
* noop - a simple logging mix in. Does not modify the result in any way, simply logs it to console
* removeslots - removes all matched slots from the result
* wordcount counts the words in the matched utterance and attaches this count to the result
Imagine that you update you config.json file to add the mixIns section like this:
`text`
"mixIns": {
"bundles": [
{
"bundleName": "loggingMixIn",
"mixInCode": [
{
"mixInBuiltInName": "noop",
"arguments": {
"log": true
}
}
]
}
],
"appliesTo": [
{
"bundleName": "loggingMixIn",
"intentMatchRegExString": "(.*)"
}
]
}
What this does is defines a mix in "bundle" (i.e. bundle of the code - noop - and argument) and give it a name "loggingMixIn".
Then it specifies that this "bundle" applies to every intent (i.e. "appliesTo" field has a pairing of this bundle with
the "intentMatchRegExString" which matches on every intent: (.*)). As a result, the "noop" mix in will run after every
match and log the results. You can modify which intents it applies to by chaining the matching reg exp. The code that
will actually be run is noop.js located in the builtinmixins directory:
`javascript`
"use strict";
module.exports = function(standardArgs, customArgs){ // eslint-disable-line no-unused-vars
let intentName;
let utterance;
let priorResult;
if(typeof standardArgs !== "undefined"){
intentName = standardArgs.intentName;
utterance = standardArgs.utterance;
priorResult = standardArgs.priorResult;
}
if(typeof customArgs !== "undefined" && customArgs.log === true){
console.log("noop built in mix in called");
if(typeof standardArgs !== "undefined"){
console.log("noop standardArgs: ", JSON.stringify(standardArgs));
}
else {
console.log("noop standardArgs: undefined");
}
if(typeof customArgs !== "undefined"){
console.log("noop customArgs: ", JSON.stringify(customArgs));
}
else {
console.log("noop customArgs: undefined");
}
}
};
Note the signature - two arguments are passed in, both are objects.
The first one is passed to your mix in by vui-ad-hoc-alexa-recognizer automatically. It contains intent name,
utterance that matched, and the result to be returned to the user.
The second one contains the arguments specified in the config.json: {"log": true} passed to this function on your
behalf by vui-ad-hoc-alexa-recognizer.
You might be wondering why some of these exist. After all, why have code that removes parts of the result produced by
the matching process. Here is a simple "for instance": You are encountering issues with some intent(s). You don't want
to delete the code nor do you want to change skill definitions. You just want to temporarily disable these intents.
So, you may remove all the slot values, then change the intent name to something like "RemovedIntent" which will handle
any such cases and will respond to the user with "I am sorry, I didn't get that" essentially disabling the intents.
Or you may have decided that you want to experiment with changing the conversation flow and remap an intent to a closely
related, but different one.
#### Custom mix-ins
In addition to the built in functionality you can define your own code to run for some intents.
Imagine you have an intent on which you want to do some post processing. For example, you may have an intent that
collects some numerical input from the user. You might ask the user: "How many television sets do you have". And you
may define multiple utterances to recognize - some contain just the number, some might be a full sentence containing a
number: "I have 2 television sets". But... the user might say something like "I have a television set" or "I have a couple
of television sets". Now, these two last utterances do NOT contain an explicit number, but they DO implicitly specify
the count. You could construct several intents (NumberOfTvSetIntent, OneTvSetIntent, TwoTvSetsIntent) and then map
corresponding utterances to their intents and the "handler" code would know about the implied counts in the 1 and 2 TV sets
intents. However, that requires a complication of the code and potentially mixing parsing and business logic together.
Wouldn't it be nice if we simply could somehow "extract" the counts (1 and 2 respectively) and add them to the result
as slot values so that the business logic would simply use them? Well, that's what a custom mix-in would let you do.
`text`
"mixIns": {
"bundles": [
{
"bundleName": "tvCountMixIn",
"mixInCode": [
{
"mixInSrcFileName": "./injecttvcountslotvalue.js",
"arguments": {}
}
]
}
],
"appliesTo": [
{
"bundleName": "tvCountMixIn",
"intentMatchRegExString": "(TvCountIntent)"
}
]
}
Now, after a successful match on TvCountIntent, ./injecttvcountslotvalue.js will run and add the corresponding slot
and value to the result. What would this code look like? Something like this:
`javascript`
"use strict";
module.exports = function(standardArgs, customArgs){ // eslint-disable-line no-unused-vars
let intentName;
let utterance;
let priorResult;
if(typeof standardArgs !== "undefined"){
intentName = standardArgs.intentName;
utterance = standardArgs.utterance;
priorResult = standardArgs.priorResult;
}
if(typeof priorResult !== "undefined" && priorResult !== null && typeof priorResult.slots !== "undefined" && priorResult.slots !== null && typeof priorResult.slots.CountSlot === "undefined"){
if(utterance.endsWith("a television set")){
priorResult.slots["CountSlot"] = {
"name": "CountSlot",
"value": "1"
};
}
else if(utterance.endsWith("a couple of television sets")){
priorResult.slots["CountSlot"] = {
"name": "CountSlot",
"value": "2"
};
}
}
};
Note the signature - just as with the built in mix ins two arguments are passed in,
both are objects with multiple (potentially) fields.
The first one is passed to your mix in by vui-ad-hoc-alexa-recognizer automatically. It contains intent name,
utterance that matched, and the result to be returned to the user.
The second one contains the arguments specified in the config.json (nothing in this case).
Here this code checks to see if the result already has a CountSlot value. If not - it will attempt to determine whether
it's 1 or 2 by looking at the utterance and updating the result with "injected" CountSlot.
#### Applying mix ins when there is no match
Sometimes you may want to apply a particular mix in NOT when there IS an intent match, but when there ISN'T one.
A typical common example is replacing all non-matches with a default intent, e.g. "UnknownIntent".
You can easily do this by specifying "unmatched": true in your config.json:
`json`
{
"bundleName": "SetIntentMixIn",
"unmatched": true
}
You can even combine both matched intent and unmatched specifications:
`json`
{
"bundleName": "loggingMixIn",
"intentMatchRegExString": "(.*)",
"unmatched": true
}
the above will execute on EVERY match attempt, whether it successfully matches or not.
#### AFINN
You can use afinn built in mix in for sentiment analysis. Currently it supports AFINN96 and AFINN111 data sets.
Also, there is an AFINN96 misspelled words data set that includes misspelled versions of the words in AFINN96.
More data sets are on the way. Some of them will be "alternative base sets" - AFINN165. Others are additional data sets
that can be added to the base set, such as scored misspelled words or emoji data set.
This mix in takes a single argument and returns a single
score. For example, given this config snippet:
`json`
{
"bundleName": "afinnSentimentBundle",
"mixInCode": [
{
"mixInBuiltInName": "afinn",
"arguments": {"ratingDataSetFiles": ["./afinn96.json", "./afinn96misspelled.json"]}
}
]
}
might attach this "sentiment.AFINN.score" to the result:
`json`
{
"name": "AfinnIntent",
"slots": {},
"sentiment": {
"AFINN": {
"score": -3
}
}
}
#### Precompiled sentiment data sets
When you specify the data sets for sentiment analysis you should be aware that it may have some performance implications.
For your convenience you can specify the data set(s) individually as many as you'd like as part of the "ratingDataSetFiles"
array. However, this would then mean that the sentiment analysis code would have to do extra work at run time. Namely,
merge the data sets, sort, remove duplicates, etc. You can eliminate these steps if you use "precompiled" data sets.
These include one or more data sets already merged, sorted, etc. You can specify only one such file since the intent
is to create a single precompiled file that does not need to be processed further.
So, instead of the "ratingDataSetFiles" array, please use "precomputedDataSet" field:
`json`
{
"bundleName": "afinnSentimentBundle",
"mixInCode": [
{
"mixInBuiltInName": "afinn",
"arguments": {"precomputedDataSet": "./afinn96withmisspelledwords_precompiled.json"}
}
]
}
Currently there is only one precomputed data set - afinn96withmisspelledwords_precompiled.json - but you can make a set
yourself it you need it.
#### Making your own precompiled sets
If you want to create a custom precompiled set you can use provided afinndatasetcombiner.js utility to do so. You can
run it without arguments to get the usage info, but it's really simple:
`shell`
node afinndatasetcombined.js -i ... -o
e.g.
`shell`
node afinndatasetcombiner.js -i builtinmixins/afinn96.json builtinmixins/afinn96misspelled.json builtinmixins/afinnemoticon-8.json -o builtinmixins/afinn96withmisspelledwordsandemoticons_precompiled.json
If a service like Cortana passes a dollar value, e.g. $1000, it will be mapped
to "1000 dollars" as would be expected by an Alexa skill. (Note that if you
want to test it with matcher.js you have to either escape the $ character or
enclose the whole string in '' rather than "" to avoid command line handling
of $)
`shell`
node matcher.js 'the first price is $1000 and the second price is $525000'
which will produce:
`json`
{
"name": "PriceIntent",
"slots": {
"PriceOneSlot": {
"name": "PriceOneSlot",
"value": "1000"
},
"PriceTwoSlot": {
"name": "PriceTwoSlot",
"value": "525000"
}
}
}
Note that this is identical to:
`shell`
node matcher.js 'the first price is 1000 dollars and the second price is 525000 dollars'
which will produce:
`json`
{
"name": "PriceIntent",
"slots": {
"PriceOneSlot": {
"name": "PriceOneSlot",
"value": "1000"
},
"PriceTwoSlot": {
"name": "PriceTwoSlot",
"value": "525000"
}
}
}
Trailing periods (.), exclamation signs (!), and question marks (?) are ignored
during parsing.
Any commas within numeric input, e.g. "20,000", are ignored during parsing.
Note: now that multi-stage matching has been enabled, the performance should be
a lot better for many previously slow scenarios. However, you can still make it
faster by arranging for the parsing order and excluding some intents from parsing.
In some interesting cases multi-stage matching is actually slower (sometimes by a large factor)
than single-stage matching. To accommodate such cases, you can generate recognizer files
without multi-stage matching. To do so, simply add --optimizations SINGLE-STAGE to the generator command line:
`shell`
node generator.js --intents test/intents.json --utterances test/utterances.txt --config test/config.json --optimizations SINGLE-STAGE
You can pass to the matching call the name(s) of the intents that you want to
try to match first. (Currently it only supports custom intents, but that's not
a problem since built in intents are very fast). Then this call
will likely execute much faster. Since most of the time you know what the next
likely answers (i.e. utterances) are going to be, you can provide them to the
matching call. For example, the following call will try to match CountryIntent first:
`javascript`
let result = recognizer.Recognizer.matchText("have you been to France", ["CountryIntent"]);
In addition to the intent parsing order you can also pass a list of intents to
be excluded from the matching process. This is useful if you have intents that
have very large sets of custom values and you are pretty sure you don't want to
parse then in a particular place in your skill (i.e. if you are in a flow that
does not include some intents then you should be able to exclude them from
parsing). The following call will try to match CountryIntent first and will not even try to match FirstNameIntent:
`javascript`
let result = recognizer.Recognizer.matchText("have you been to France", ["CountryIntent"], ["FirstNameIntent"]);
In addition to the intent parsing order and intent exclusion lists you can pass
an alternate recognizer file to use in the matching call. (Note that the "normal" behavior is to assume a recognizer
named "recognizer.json". Explicitly specifying the recognizer simply overrides the default.)
`javascript`
let result = recognizer.Recognizer.matchText("have you been to France", ["CountryIntent"], ["FirstNameIntent"], alternativeRecognizer);
This can be used both
for performance optimization as well as for breaking up large skills/apps into
smaller chucks, typically by functionality (though now that domain functionality has been added, you should probably
use domains for modularizing your app unless there is a reason not to). Let's say you have a large skill
that has several logical flows in it. For example, you can have a travel skill
that lets you book a hotel and/or a car, check for special events, reserve a
table at a restaurant, etc. Each of these may have its own logic - its "flow".
So, you may define a separate set of intents and utterances and custom slot
values for each. Then use a session variable to keep track of the current flow.
For each flow you can generate its own recognizer file. Then, at run time, use
the recognizer for the flow that the user is in.
This has multiple advantages: performance will increase; the skill/app can be
developed separately by different teams, each having its own skill/app portion
that they are working on and they will update only the recognizer for their
portion.
Using options lists instead of multiple similar utterances may
improve performance. Testing with a simple one slot date example and an utterance
that unfolds to about 3800 utterances reduces the time from 330 ms to 74 ms on
a desktop computer. Considering that if you run it on AWS Lambda (which is MUCH slower
than a typical higher end desktop computer) you may be shaving off seconds off of
your time, which for voice interactions is quite important.
SoundEx support has been added at the utterance level for custom slot types.
You can now specify a SOUNDEX_MATCH flag for a custom slot type and SoundEx match
will be used. This allows matching of expressions that are not exact matches, but
approximate matches.
`shell`
cat utterances.txt
where utterances.txt includes this line:
``
MinionIntent Another minion is {MinionSlot:SOUNDEX_MATCH}`
thenshell`
node matcher.js "another minion is steward"`
will returnjson`
{
"name": "MinionIntent",
"slots": {
"MinionSlot": {
"name": "MinionSlot",
"value": "Stewart"
}
}
}
Note that "Stewart" matched on "steward"
More documentation for domains is coming. Meanwhile you can test your domain files using domainrunner.js utility.
To see the usage, simply run it:
`shell`
node domainrunner.js
will return
`text`
Usage: node domainrunner.js --domain
To exit type "EXIT"
If you specify the path to the domain file and to the state json object, then you will see a prompt once you run it:
`shell``
Please type user text:
If you do, you will see the results being returned. The domainrunner.js will continue running and accepting user
input (and possible updating the state object) until you kill the process or type EXIT
You can create a domain rather easily. For the simplest setup all you need is an existing recognizer JSON file and you are in business.
Imagine you already have a recognizer file (lets call it myrecognizer.json) that's located in your current directory.
You would also need a "state" json file. For now, you can simply create a file named "mystate.json" in the current
directory that contains an e