Using a bot’s memory
A bot has 2 types of memory at it’s disposal: short and long term memory, but with some tricks, a mid term memory function can also be simulated.
Short term memory
Or also called ‘volatile’ as it’s content is lost in time, can be accessed through the use of variables in the patterns. Input patterns support 2 types of variables: regular variables, which can collect any type of content and thesaurus variables, which can only collect words that are equal to or are children of the thesaurus item referenced by the variable (in other words, they are filters). Asset variables, a third type of variable, is theoretically also possible, but not yet implemented. These are also filters, like thesaurus variables, except that they filter on concrete (asset) data instead of abstract (thesaurus) data.
The basic usage of this short term memory is simple: to provide a mechanism for collecting values of variable parts in the input patterns so that these values can later be used in the output and do-patterns for providing a response and feeding the long term memory.
Regular variables
As already mentioned, regular variables can’t filter on the values that they collect, but they are optionally able to limit the number of words that they collect, either as a specific number or a range. Here are some short input-pattern examples:
I’m called $var[.]
I’m called $var:1[.]
I’m called $var:1-3[.]
I’m called $var:4:CollectSpaces[.]
Copy $from:collectSpaces to $to:collectspaces
The $var constitutes the variable (‘$’ is the variable operator, followed by the name). Every word (except spaces, if ‘CollectSpaces’ is not specified) is collected by this variable until the pattern matcher finds a word in the input that follows the variable in the pattern or until the range is fully used. This variable can then be used in the output and do-patterns of the same rule (and also from other rules, if you are certain that the input-pattern is part of the result set, which can be checked upon, more on that later).
Thesaurus variables
Thesaurus variables are a special type of input variable: they provide a mechanism for filtering possible input to a sub-branch of the thesaurus. If the input can’t be found in that branch, the pattern wont be activated. So, this is a filtering mechanism. The actual value that was found, can be accessed like any regular variable, through it’s name. There is no mechanism for providing length or range values though since they have no meaning here: it’s either an exact match with a thesaurus node or it isn’t. Here are some examples:
I’m called ^var:noun
I’m called ^var:noun.name
I’m called ^var:noun.(first name)
I’m ^var:number years old
A thesaurus variable always starts with a ‘^’ followed by it’s name. The ‘:’ indicates the start of the thesaurus path and should always be followed with a POS (part of speech). You can use the ‘any’ value to indicate that any type of pos is allowed, as long as the word is stored in the thesaurus. These are the other supported POS values:
noun, verb, adjective (or adj), adverb (or adv), article (or art), pronoun (or pron), conjunction (or conj), interjection (or inter), preposition (or prep), number, integer (or int), double, any.
You can stop there, which would indicate that you want any word of the specified part of speech. You can also continue the path with a ‘.’ followed by a text value (put into brackets if it’s multiple words). This allows you to further refine the thesaurus path. Note that you don’t need to start at the root of the thesaurus that you are using, just as long as you are comfortable that it will point to a unique word within the tree (otherwise you can have multiple matches,… which might also be desirable). Note that the last 3 POS values (number, int and double) can’t have any further path specifiers, they have to stop at the POS value.
Different relationship types
The thesaurus is able to store different types of recursive relationships. If you don’t specify one in the thesaurus path, the ‘is a’ will be used. But you can specify other paths as well, like so:
^letter->(member holonym):noun.(roman alphabet)
This declares a thesaurus variable called ‘letter’ which will collects nouns that are children of ‘roman alphabet’ under the relationship ‘member holonym’. In other words, this variable can collect all the letters of the roman alphabet (a, b, c,…).
Collecting multiple values
It’s possible to use the same variable name multiple times in the same input pattern. This allows you to collect a list of values for the same variable. Thesaurus and regular variables can be intermixed. Here are some examples:
{$name ,} and ^name:noun.name are here //catches something like: Tom, Flint and Warner are here
Note that there is a difference when a regular variable collects multiple words at a single location compared to when it collects single words at multiple locations in the pattern. When a single location collects multiple words, this group of words is combined into a compound word (as in ‘baby gear’), but when words are collected at multiple locations, a list is created. This list can later-on (in the long term memory) be labeled as AND, OR or LIST (unspecified).
Using short term memory
Up until now, we’ve only been talking about how to collect the values for the short term memory. Of course, there’s no point in doing that unless you can actually do something with these values. That’s done in the output and do-patterns. As already mentioned, you access the content through the variable names. Here are some output-pattern examples:
Ok, I see, your name is $var\.
So you are $var, nice to meet you!
So, you can $verb:Infinitive, can you?
I see, $name:interleave(‘, ‘, ‘ and ‘)
At it’s most basic form you specify the ‘$’ operator followed by the name of the variable that you want to render. Note that you should always use the ‘$’ operator while rendering, even if the value was collected using a thesaurus variable ( ^ ). This is because for output, the ^ operator is used to access the long-term, abstract memory (the thesaurus data itself).
Rendering the value as it was collected, is useful but often we want to do a little more, sometimes we need to do some kind of change or transformation to the values, like conjugating a verb, get the plural of a noun or find the attribute for the value (see later),… This is done through functions that you define in the path. A function starts with a ‘:’ followed by the name of the function (a list of all the available functions will come shortly) and optionally a list of arguments for the function, specified between brackets and separated by a ‘,’.
Long term memory
The second major type of memory that’s available to the bot is used to store and retrieve values so that they can cross the boundary of the single-shot input/response system, in other words: long term memory. Currently, there are 2 types: a thesaurus structure for storing abstract information and assets which maintain concrete knowledge. Typically, you use this data to compare against short-term variables, render previously stored data or store newly acquired knowledge.
The thesaurus
As already mentioned, thesaurus variables are used in the input-patterns so that the valid content for a variable can be filtered. When the ‘^’ operator is used in output, conditional or do patterns however, it behaves a little bit different: it becomes a value generator instead of collector. Consider the following output patterns:
We are in ^noun.month[$time:month-1]
I like ^noun.food.(Italian food):random
I ^verb.be:conjugate(#bot) trying something complicated //render: I am trying something complicate
As you can see, a thesaurus output-path contains a mix of statics and functions which eventually result into 0, 1 or 2 values. Because they render values and don’t collect it, no name is required. You can use the [] operator to select a child at a specific index position, like in the first example, which is used the generate the name of the month instead of a number. Note that the index is 0 based. In case that a static path item contains multiple words (like ‘Italian food’), use () brackets to group them. Also, if there are no values found for the path, any spaces that follow it in the output are stripped.
You can also store new data in the thesaurus. This is done in the calculation or do-sections. There are basically 2 operations that you can do at 2 different levels: you can add or remove values either as thesaurus children or as conjugations/references. To explain the difference between children and conjugations or references, take the following examples and how they are stored:
| A house is a building | ^noun.building += ‘house’; |
| The plural of bird is birds | ^noun.bird->plural = ‘birds’; |
| The opposite of good is bad | ^adj.good->opposite = ‘bad’; |
| seagulls are a type of the singular of birds | ^noun.birds->singular += ‘seagull’; |
| The superlative of the opposite of good is worst | ^adj.good->opposite->superlative = ‘worst’; |
In the first example, we are declaring a child relationship: house is a building. If you have done any coding before, the syntax might be vaguely familiar: the left part of the statement contains the thesaurus path, the ‘+=’ operator to indicate that we want to create an ‘is child’ relationship, and on the right-side comes the value that needs to be stored. This could be a variable reference, an asset, another thesaurus path,….
The second and third examples look identical and for all intent and purpose, they are. The only difference is on the inside: in the first example ‘plural’ is a known conjugation form, ‘opposite’ is not. The statement used for storing this information, is a little bit different. First of, the thesaurus path ends with a ‘->’ followed by the name of the relationship that you would like to edit. Next, we use the ‘=’ assign operator instead of ‘+=’ to indicate that we want to change the relationship value.
The 2 last examples demonstrate what happens when you use the –> operator together with the += assignment or when you use multiple –> operators. When combining += with –>, you will first calculate the full result of the left side. So in our example, we first take the singular value of ‘birds’, then we add a child to this result, which is ‘bird’. A similar thing happens when you use multiple –> operators: the value is calculated.
Except for the POS value at the start of the path, every other item in a thesaurus path can be a static, a variable reference, an asset path or another thesaurus path. This allows for tremendous flexibility in the way that you store data. We could generalize some of the previous statements like this:
^noun.building += $value;
^noun.($singular)->plural = $value;
^adj.good->($relationship) = $value;
Removing values from the thesaurus is done using the ‘-=’ operator or by assigning to the ‘null’ value. Like with storing, all parts can be static or variable. This is probably best explained with some examples:
| A house is not a building | ^noun.building -= ‘house’; |
| Bird has no plural | ^noun.bird->plural = null; |
| A $value is not a $node [.] | ^noun.($node) -= $value; |
Assets
As already mentioned, assets could theoretically also be used in the input, but that’s not yet supported. If someone has a need for this, let me know, it’s not that tremendously difficult to add, it just creates a little more overhead.
Anyway, like thesaurus paths, asset paths can be used in output, do and conditional patterns. They are declared in much the same way as thesaurus paths by using the ‘.’ (dot) or ‘:’ (function) operators, except that they start with a # and ‘–>’ (links) are not supported. For the thesaurus path, the ‘.’ (dot) operator selected a child node, for assets, this selects an attribute value. Here are a few output examples:
My name is #bot.name
your children are called #user.child.name:interleave(“, “, “ and “)
a book is made of $(#(^noun.book).component.name):interleave(“, “, “ and “)
Bot and User are hardcoded assets and refer to me and you respectively, from the bot’s point of view. In the third example, the first value in the asset path, is actually a thesaurus path. This results in concrete information about abstract data (a book is made of paper, ink, glue,…).
Also in the last example, the entire asset is the first value in a normal variable path, because an asset path will always calculate it’s result based on 1 value, if the previous path item resulted in multiple values (like ‘component), the next part of the path is calculated as if there was only 1 result (internally, a split is done), and only at the end of the path, all results are joined. This doesn’t work for ‘:interleave’, it expects a list of values to combine. A variable path can do this, hence this construct.
To store asset data, the = (assign), += (assign add), != (assign not) and !+= (assign add not) operators are used. Removing data is done with the –= (assign remove) operator. Take the following examples (input statement to the left, how to store/remove it to the right):
| My eyes are blue | #user.eye.color = ‘blue’; |
| I have a dog. | #user += ‘dog’; |
| My dog’s name is not doggy | #user.dog.name != ‘doggy’; |
| I don’t have a tiger | #user !+= ‘tiger’; |
| I have big blue eyes | #user.eye.color.extra.size = ‘big’; |
| My eyes are also brown | #user.eye.color &= ‘brown’; |
| my eyes are brown or blue | #user.eye.color = ‘brown’; #user.eye.color |= ‘blue’; |
| my eyes are brown, blue | #user.eye.color = ‘blue’; #user.eye.color ;= ‘blue’; |
| Remove my dog | #user –= ‘dog’; |
| remove my eye color | #user.eye –= ‘color’; |
When you use the ‘=’ (assign) operator, you declare an ‘is’ relationship: ‘color’ becomes the attribute, ‘blue’ the value. Since ‘blue’ is not an asset, but just a word, we have a terminator: blue can’t have any more children. But, there is a way to cross this border, by using the static ‘extra’, as in the 5th example.
If instead, you want to declare that something is not y, you can use the not-assign operator (!=). This allows you to still store the information that something is not. Be careful though, there is a thin line between being and not being, if you don’t check on this in the conditions, you might say that something is, while it isn’t (sounds familiar?).
The ‘+=’ or assign-add operator is used to create ‘has’ relationships, like in the second example. The major difference with the first one is that ‘dog’ becomes the attribute and the value becomes a new asset that will represent the dog. There is also the not version: !+= which is used to indicate that the asset doesn’t have something.
If you want to create a list of values, you can use either the ‘;=’, ‘|=’ or ‘&=’ operators. The first one creates (or adds to) a generic list, the second is for OR lists and the last for AND lists. The generic list operator can add to any type of list without modifying it’s type. The |= and &= operators will change a generic list to OR and AND respectively. When you try to add an item to an OR list with an AND operator, you create a new list object that contains the original OR list and the newly added item. the same goes for an OR operator with an AND list.
Finally, you can also actually remove an attribute value. This is done with the ‘-=’ operator. The right-side should be the name of the attribute that you want to remove. The value that is removed get’s cleaned up automatically, so if the value was another asset which isn’t referenced anymore after the remove, the entire asset will be destroyed. (Removing items from a list has to be done with the ‘RemoveChild’ instruction, more on that later, or if you’re in a hurry, drop me a note).
As already mentioned, every asset value can always use the ‘extra’ static to get to a sub-asset. There are a few other statics worth mentioning which allow you to expand the dataset that the asset can store. These are used to declare things like when, where, why, how, amount,… Functions are:
| why | provides access to the ‘reason’ path | #user.dog.why = “likes dogs”; //single string, non indexed |
| when | provides access to the ‘time’ path | #user.dog.when = L(10 years ago); //creates list |
| where | provides access to the ‘location’ path | #user.where.preposition = ‘in’; #user.where.object = ‘chair’; or #user.where = L(in the chair); |
| how | provides access to the ‘method’ path | #user.dog.how = L(received from some friends who had a bit of an accident); |
| who | provides access to the ‘persons’ path | #user.see.who = ‘man’; //user sees a man |
| what | provides access to the ‘objects’ path | #user.eat.what = ‘food’; //user eats food |
| then | provides access to the causality path | #user.eat.then.who = #user; #user.eat.then.attribute = ‘state’; #user.eat.then.value != ‘hungry’; or #user.eat.then = “I’m not hungry”; |
Mid term memory
Some functions, like the ‘:attribute’ function (which is able to extract, for instance, ‘color’ from ‘blue’, or ‘name’ from ‘Jan), make use of context, if it is declared. This context is usually a list of asset paths that point to some memory region of the bot. The idea is that, together with a response, you also generate the meaning of what was said and store this information in the asset that you declared as context. If you refresh this context on each run, you effectively have simulated mid term memory.
The basic setup for using mid term memory consist out of:
- a global context declaration so that the system knows where to go look for contextual info.
- some global do-after-each-statement patterns. These are responsible for erasing the previously collected data and possibly creating an echo.
- some global do-on-startup patterns which will remove any data from the previous run.
- do-patterns on each rule to actually collect the knowledge about what is being said.
Context:
#bot.memory.subject
#bot.memory.attribute
#bot.memory.object
#bot.PrevMem.subject
#bot.PrevMem.attribute
#bot.PrevMem.object
Do after output:
#bot.prevmem = #bot.mem; //this is code, so it requires a ‘;’ at the end, context statements are just output lines, no code
#bot –= mem;
do on startup:
#bot –= mem;
#bot –= prevmem;
on pattern (example pattern = I like $value [.])
#bot.mem.subj.val = #user;
#bot.mem.attrib.value = ‘like’;
#bot.mem.obj.val = $value;
Mid term memory also becomes very useful once you start working with recursive sub rules/topics. This technique allows you to rebuild the extracted data.
About bindings
Asset, variable and thesaurus paths as they are used in the code sections, output patterns or conditionals (but not the input patterns), are defined through bindings. A binding is a state machine definition that declares the different paths that you can use. The bindings used by chatbots, are defined in the chatbot module (included in the installer) in NNL (Neural network language). In other words, the neural network of a bot doesn’t just declare the patterns, but also the code used by those patterns. This means that you can extend or change the bindings yourself. For instance, if you like to add a new function to asset paths, you need to go to the ‘assets.nnl’ file, add it in the binding and reload the module.
Patterns
All the different types of patterns (input, output, conditional) could also be considered as a form of long term memory. Internally, they are stored in exactly the same manner as all the other data. As such, they can also be manipulated in a similar manner as the other long term data. Although, at the time of writing, there is still limited support for this. More on that to come.