Variables, logic and conditionals
To build a user-friendly application that allows users to navigate freely, or create a personalized experience you will likely need to add some logic to your app. This page covers that, in addition to how to set custom variables.
#
Variables_Variables are essential parts of any type of applications, and spoken apps are no different. Narratory comes with two types of variables, system set variables and custom variables.
#
System variablesNarratory comes with a few handy variables set by the system that help you create personalized and engaging dialogs. These can be used as normal variables in your conditions (see Conditionals below) and passed with API-requests (see Dynamic BotTurns).
Variable | Type | Description |
---|---|---|
sessionId | An unique string | An unique identifier generated by the Narratory system for the current session |
platform | "google", "facebook", "slack", "kommunicate", "voximplant", unknown" and others | The platform that the user is using to interact with your app. If you test from terminal, it will be set to "unknown" |
user_text | string | The last utterance said by the user |
bot_text | string | The last utterance said by the bot |
lastTurnType | "narrative", "userInitiative", "botInitiative", "bridge" | One of the three values to the left depending on if the previously executed BotTurn was part of the Narrative, the response to a user initiative or a Bot initiative |
turnCount | A positive integer, starting at 1 | The amount of a times a turn is executed |
retryCount | A positive integer, starting at 0 | The amount of fallbacks in a row. The retryCount resets whenever a non-fallback intent is matched |
inputType | "voice", "keyboard", "unknown" | The input type of the last user input. For Google assistant, "voice" and "keyboard" will be set. If using the Google Assistant Simulator, "voice" will always be set even if you enter the text with a keyboard. For other clients including terminal, it will be set to "unknown". |
user_firstName | String | A user's first name when account-linked through Google Assistant |
user_lastName | String | A user's last name when account-linked through Google Assistant |
user_fullName | String | A user's last name when account-linked through Google Assistant |
user_email | String | A user's email when account-linked through Google Assistant |
user_guest | Boolean | True if user is not authenticated on Google Assistant. Undefined if not on Google Assistant |
user_returning | Boolean | True if user is a returning user on Google Assistant. Undefined if not on Google Assistant_ |
#
Custom variablesIn addition to the above mentioned system variables, and variables set by captured Entities, as described in the NLU docs, you can also set custom variables that you can use for conditionals and API-requests.
#
Setting custom variablesTo set custom variables, you do as follows:
// Setting a variableconst booking: BotTurn = { say: "I got all I need. Now processing a booking", // A booking would be done here with an API-call set: { booked: true }}
Your variables can be of any type, i.e. complex JSON structures are supported, but be aware that certain limitations exist for variables in says (for example say: "This is my _myVarWithComplexValue"
) and when used in conditionals (for example cond: { myVar: "banana" }
}). For the latter, check the supported conditional values below on Supported Conditional values.
Note: make sure your variables (entity parameter names and custom variables) have unique names, otherwise they will overwrite each other. This can of course be intentionally used as well, by manually overwriting values captures by entities and vice verse.
You can also set variables to the value of other already set variables, for example:
const booking: BotTurn = { say: "Great. I saved your booking", set: { savedBookingNumber: "_bookingNumber" }}
This allows us to reuse the bookingNumber variable for something else.
Note, if
bookingNumber
does not exist above, the savedBookingNumber variable will instead be set to the string value "_bookingNumber" which probably is not what you want, so be careful here.
#
Lists of variablesSometimes a variable with a single value is not sufficient, for example you might want to allow users to add several items to a grocery list across several dialog turns. This is done by using Javascript arrays, which should be a familar concept at this point.
#
Creating a list of variablesTo create an empty list of variables, you can use an empty array []
:
const greeting: BotTurn = { say: ["Welcome to the grocery store. I'll help you create a grocery list!"] set: { groceryList: [] }}
You can also provide starting values:
const cinnamonBun: BotTurn = { say: ["Hi. Since it's the cinnamon bun days, we will give you a free cinnamon bun and a package of coffee." set: { groceryList: ["Cinnamon bun", "Coffee"] }}
#
Modifying a list of variablesSimilar to non-list variables, you can always overwrite them with a new value. But, usually you might want to add or remove items from your list. This is done using a special syntax in variable strings, +
or -
as follows:
const addMilk: BotTurn = { say: "Did you say you wanna make cappuchino? Then maybe you want to add oatmilk to your order?", user: [ { intent: Yes, bot: { say: "Great. That's what I thought", set: { groceryList: "+Oat milk" } } } ]}
const removeCoffee: BotTurn = { say: "It sounds like you had enough coffee for today. Should I remove it from your list?", user: [ { intent: Yes, bot: { say: "Alright, consider your list caffeine-free", set: { groceryList: "-Coffee" } } } ]}
Naturally, you might want to use variables instead of hardcoded values like "Coffee" and "Oat milk". These variables would likely be set by speech input using Entities but could also be set manually or by your API (see DynamicBotTurn docs).
This is done as follows:
const modifyList: BotTurn = { say: "What do you want to add to the list?", user: [ { intent: intents.addProductsToList, bot: { say: [ "Absolutely. _product.", "Okay, adding _product to the list.", "Great, _product. List is now _groceryList." ], set: { groceryList: "+_product" } } }, { intent: intents.removeProductsFromList, bot: { say: "Okay, removing _product. List is now _groceryList.", set: { groceryList: "-_product" } } },
Important note: If you add or remove single items to a list, they will simply be added or removed from the list. If you add a list of items to a list, for example as in the above example where
_product
is a Listentity, the new list will be concatenated, i.e. merged, with the existing list. For example, if you had["apple", "milk"]
and you added["egg", "banana"]
, the resulting list would be["apple", "milk", "egg", "banana"]
.
#
Resetting variablesYou can reset any variable (set by an Entity or set manually) by giving them a null value as follows:
const bookAnotherFlight : BotTurn = { say: "Do you want to book another flight?", user: [ { intent: ["yes", "I do"], bot: { say: "Okay", set: { toCity: null, fromCity: null, numPeople: null } }} ]}
You can reset variables by giving them values
null
,undefined
or an empty string""
. For consistency, we recommend usingnull
.
#
Using variables in bot outputVery commonly, you want to use your variables (whether they are set using entities or manually) in your bot output, i.e. what the bot says. This is done using a special syntax that you likely remember from defining intents with entities, _myVariable
. Narratory will try to populate this "handle" with the current value of the myVariable^ variable. If not available, the _myVariable
handle will remain.
#
Lists of variablesFor lists of variables, you typically want to choose what conjunction you want to use - "and", "or", or "and or". You can tell Narratory which one you like for every utterance by using the special characters &
, |
, &|
respectively.
Example: If your list groceryList
would have the value ["apple", "pear", "banana"]
you can have the bot say:
const listSpeech: BotTurn = { set: [ groceryList: ["apple", "pear", "banana"] ], say: [ "Your list contains _&groceryList", // "Your list contains apple, pear and banana" "Do you want to remove _|groceryList?", // "Do you want to remove apple, pear or banana?" "Wanna remove _&|groceryList?" // "Wanna remove apple, pear and or banana?" ]}
If you don't provide these characters, i.e. you use _groceryList
it will default to &
, i.e. "and".
Note, Narratory uses localized versions of "and" and "or" based on your agent's selected language.
#
Variables that are objectsIf the value of myVariable is complex (likely an object), Narratory will try to populate it by joining the values of each keys in the object.
For example, the built-in entity unitCurrency
returns values such as {"amount":5,"currency":"USD"}
. If you have a variable with this value and use it in a Say string like "the total payment is _myVariable"
, Narratory will populate it to "the total payment is 5 USD". The order of the two parameters will be alphabetically based on the key names, i.e. "amount" is before "currency" hence "5 USD" and not "USD 5".
#
ConditionalsConditionals is essential for most applications, where you want to change some behavior based on user input or other factors. A common example is skipping certain BotTurns in your narrative based on some criteria. For example, you probably only want to ask a user what color they want on the shirt they are buying if they indeed said they want to buy a shirt. Similarly, you only want to ask the user what their final destination in a flight booking is if they haven't provided this information yet.
You set a cond by adding a cond
parameter with an Object:
cond: { key: value}
Conditionals are set using the cond
parameter on either BotTurns or RichSays as introduced below.
#
Conditional Bot initiatives - top level BotTurnsConditionals on BotTurns are set on the BotTurns (or other types of BotTurns - DynamicBotTurn and BridgeTurn) like this:
const conditionalBotTurn: BotTurn = { cond: { finalDestination: null }, say: "What is your final destination?", user: [{ intent: finalDestination, bot: "I see, _finalDestination" }]}
The above means that when this BotTurn is the next turn in the narrative, the Narratory system will check if the condition is true - which in this case means that the finalDestination variable is not set - and only if so, the turn will be executed. If the condition fails, Narratory will instead attempt the next turn.
#
Conditional followup BotTurnsConditions can also be used on followup turns, for example you might want to respond differently depending on a captured entity value. An example of this below.
When conditionals are used in followup turns, the first turn with a truthful condition (or non-existant condition) will be executed. Thus, the order of the turns is important to mind.
const conditionalBotTurn: BotTurn = { cond: { finalDestination: false }, say: "What is your final destination?", user: [ { intent: finalDestination, bot: [ { cond: { finalDestination: ["Paris", "Berlin"] }, say: "Oh, _finalDestination is a personal favorite!" }, { cond: { finalDestination: "Stockholm" }, say: "Oh, I was born there!" }, { say: "Oh, _finalDestination sounds lovely!" } ] } ]}
#
Conditional bot utterances using RichSayConditionals can also be set on bot utterances (i.e. the say
parameter in BotTurns), allowing you to alter the bot speech based on some variables set.
Similarly to conditional followup BotTurns (as described above), conditional says are always executing the first say with a condition that is true or doesn't have a conditional.
Note: if you don't have any say with a conditional, or all conditionals are empty, i.e.
cond: {}
, Narratory will randomize among all provided says, i.e. as if you had written a simple array of strings:say: ["one", "two", "three"]
. This is similar to the default behavior described in building blocks.
const commentOnColor: BotTurn = { say: [ { cond: { userFavoriteColor: "red" }, text: "Oh wow. My favorite color is red as well, we must be soul-mates!" }, { text: "Oh, _userFavoriteColor is nice. My favorite color is red, however." }, "This will never be said since the (directly) above utterance always will be truthful, since it has no condition" ]}
The above is implicitly using the Narratory interface RichSay
which has a mandatory text parameter taking a string or an array of strings and an optional cond parameter with your conditions object.
Note that you can mix and match RichSays and strings as shown above.
The RichSay can also be used explicitly if declared as variables:
import { RichSay, BotTurn } from "narratory"
const redComment: RichSay = { cond: { userFavoriteColor: "red" }, text: "Oh wow. My favorite color is red as well, we must be soul-mates!"}
const blueComment: RichSay = { cond: { userFavoriteColor: "blue" }, text: [ "Oh, blue, really? Like my eyes, if I had had eyes. My favorite color is red, however!", "Blue blue blue, how nice. I'm mostly into red though" ]}
const commentOnColor: BotTurn = { say: [ redComment, blueComment, "Oh, _userFavoriteColor is nice. My favorite color is red, however." ]}
#
Supported Conditional valuesThe supported conditional values are:
true: value of variable is truthful. Truthful in Narratory means NOT
false
,null
,undefined
,""
or[]
. In other words, the conditioncond: { myVar: true }
will be true if myVar is set and is not an empty string, null, false or an empty array.false: value of variable is falsy, i.e not truthful. Falsy in Narratory means the opposite of truthful as defined above, i.e.
false
,null
,undefined
,""
or[]
.null: has the same meaning as false defined above. It can feel more intuitive to use null as a conditional if you want to check for a non-filled slot, hence we decided to have null and false means the same.
string: value of variable is set to a matching string value (case insensitive) or an array containing the string value (again, case insensitive). For example,
cond: { myVar: "carrot" }
will be truthful if myVar is set to"carrot"
,"CaRRoT"
or["potato", "tomato", "carrot"]
.variable: value of variable is truthful if it matches that of another variable, note - the variables does not have to be identical. For example, the cond
cond: { myVar: "_myOtherVar" }
will be truthful if myVar is set tofoo
and myOtherVar is set to["foo", "bar"]
since the above logic for string conditionals apply.string array: truthful if the value of the variable is included in the supplied string array. For example,
cond: { myVar: ["carrot", "tomato"] }
will be truthful will execute if myVar is either"carrot"
,"tomato"
or["carrot", "tomato", "pineapple"]
.number: (
cond: { myVar: 1 }
) will be executed if myVar is set to 1. Note that"1"
will be parsed as a string whereas1
is parsed as a number, so be mindful!
#
Combining conditionals with boolean logic - OR, NOT & ANDIf you supply more than one conditional statements, for example cond: { foo: "bar", baz: "qux" }
, both statements need to be truthful for the whole conditional to be true. In other words, the default boolean operator for conditional statements is AND.
Sometimes you want to instead use OR or NOT and possibly combine all of these operands. This is possible as follows.
An OR conditional can be used like this:
// OR conditionalconst withOR : BotTurn = { cond: { OR: { foo: "bar", baz: "qux" } }, say: "This will be said if foo is \"bar\" OR if baz is \"qux\""}
A NOT conditional can be used like this:
// NOT conditional - this time in a ConditionalSayconst withNot : BotTurn = { set: { foo: "qux" }, say: [ { cond: { NOT: { foo: ["bar", "baz"] } } text: "This is said since foo is NOT \"bar\" or \"baz\"" }, "This will not be said" ]}
Finally, you can also explicitly use the AND operator (this really only is necessary inside an OR conditional since AND is the default operand):
const withOR : BotTurn = { cond: { OR: { foo: "bar", AND: { baz: "qux", foo: "baz" } } }, say: "This will be said if foo is \"bar\" OR if baz is \"qux\" AND foo is \"baz\""}
#
Jumping in narrativeSometimes you want to jump ahead in the dialog, one common example is that the user wants to abort whatever you are doing by saying "exit". It could also be that you want to support branching in your narrative, i.e run different parts of the narrative depending on something. Of course, conditionals can be used for this aswell, so it is up to you do decide when to use each functionality.
Every BotTurn can have a goto parameter, which means that it will go to a different part in the narrative after executing the current turn. In order to know where to go, you supply a label. This label needs to set on the turn you want to move to.
A common example is that you are in the middle of a narrative but the user says that they want to exit (caught by a UserTurn part of the app's user initiatives), in which case you want to go to a final goodbye turn.
// A user turn that is part of our question baseconst exit: UserTurn = { intent: ["exit", "stop"], bot: { say: "Okay", goto: "END" }}
const end: BotTurn = { label: "END", say: "goodbye"}
const narrative = [start, middle, end] // Start and middle would be defined elsewhere.const questions = [exit]
Above, the start and middle BotTurns would be defined elsewhere. A good practise, to avoid errors, is to use variables for the labels as well, as shown below:
// A user turn that is part of our question baseconst exit: UserTurn = { intent: ["exit", "stop"], bot: { say: "Okay", goto: END }}
const END = "END" // label, only used in the followup turn above and in the BotTurn below
const end: BotTurn = { label: END, say: "goodbye"}
Note, the above example would give an error since you are using the END variable before it is defined. Typically you would have the exit UserTurn in a different file however, which would eliminate this problem. It is showed in this order here for pedagogical reasons.
#
Exiting conversationsNarratory will automatically exit the ongoing conversations once you reach the end of your narrative. If you want to prematurely exit a conversation you can do that in two ways, either you do a goto: YOUR-END-STATE
(see Jumping in narrative above) or you do a goto: EXIT
. Examples follow.
If end below is your last BotTurn in the narrative, you can go there using goto
:
const guest: BotTurn = { cond: { user_guest: true }, say: "It seems you aren't logged in with a Google account, unfortunately we can't help you without this" goto: "END"}
const end: BotTurn = { label: "END", say: "Goodbye!"}
const narrative = [/* Other turns */, guest, /* Other turns */, end]
You can also trigger an Exit right away using the built-in EXIT label (remember to import it!):
import { EXIT } from "narratory"
const exitIntent: UserTurn = { intent: ["exit", "stop", "I want to quit"], bot: { say: "Okay, bye bye!", goto: EXIT }}
#
Looped turnsSometimes you want to remain in the same turn until the user says something that would move the dialog on. One example is the end of a dialog where you might want to let users any remaining questions before exiting. For example, you might want to have this type of dialog:
...Bot: Now, do you have any questions for me?User: Yes, when is the expected delivery?Bot: Your delivery is estimated to arrive in 4 week days, so that is thursday!Bot: Do you have any other questions?User: What time of the day?Bot: The deliveries come between 12 and 16<Chat ended>
To do this, you can have a state in your narrative where you ask if the user has questions, and then end the dialog (or move on) if the user doesn't. An example follows (the questions themselves are in this example user initiative UserTurns, see Basic turns).
const end: BotTurn = { say: [ { cond: { turnCount: 0 }, text: "Now, do you have any questions for me? If not, feel free to browse around the site" }, { cond: { turnCount: 1 }, text: ["Any other questions for me?"] }, { text: [ "I like that you're hungry! Shoot any other questions you might have", "You're interested, that makes me happy! Any other question?", "Anything else you wonder?", "Do you have any more questions?" ] } ], user: [ { intent: ANYTHING, bot: [ { cond: { retryCount: 0 }, say: "I didn't get that. Can you rephrase?", repair: true }, { say: "Sorry, I can't answer that yet. Please ask me something else?", repair: true } ] }, { intent: No, bot: "Okay, bye bye" } ]}