Argg, while doing some maintenance with the ‘online file explorer’ instead of a regular ftp manager, I accidentally wiped out the entire root directory. The database remained in tacked though, so no posts were lost, but lots of images are gone. I’m hoping that by tomorrow everything is up again. Sorry for the inconvenience.
Ahh well, lets just file this one under: win some, loose some.
With today’s post comes a new release: version 0.8.7. Not just so that it includes the demo for the new tutorial, but also to clean up some bugs. Most important items:
- The spell checker has been cleaned up some. The custom dictionary is now shared across the entire application.
- There was still a bug in the core split functionality that sometimes resulted in incorrect weights for an object when the same object was the result of multiple paths, but with different weights.
- Asset editors now have a child topic-editor by default in the project view.
- Some cleanups in the import/export functionality of the thesaurus and assets.
- a bug in the handling of variables in patterns (which resulted in invalid value refs sometimes) is also fixed.
- a new tutorial that shows how to do questions, answers and redirections.
Get the latest release from the download page.
Getting information from the internet into a chatbot’s output can be very useful at some times. Not only to show continuously changing values, like weather information, but it can potentially also be used for learning, although the latter is obviously a little trickier.
Retrieving, or scraping info from the internet can be done remarkably easy with the chatbot designer. Here’s a screencast of a bot that retrieves weather information from the google weather api.
In the video, a .net plug-in is used to retrieve information from the internet by means of XPaths. This plugin is included by default in the application. Note though that plug-ins are only supported in the pro version. Basic users will be able to use these projects, but they can’t create or edit any patterns that rely on plug-ins. Also, plug-ins are loaded on a project by project basis. So if you want to use the scraping features in your own project, you will first need to make certain that the correct .net functions have been loaded. Once this has been set up though, all plug-ins will
automatically be loaded when the project is opened.
Loading
To load a plug-in, go to view/communication channels/OS. This will bring up a view like the one on the right. From here, you can load and unload dlls, classes and functions. First up is the dll. This can be loaded with one of the buttons on the toolbar. The first one gives access to the cache (dlls that have already been loaded). With the next button, you can select a file from disk. Note that, even though the ‘CmdShell.dll’ file (which contains the scraping functions) is part of the installation, it isn’t guaranteed that it’s already loaded in cache, so you might have to select it from the ‘program files/Chatbot designer pro/’ path. By the way, you can remove a dll by selecting it and pressing delete. Functions can be selected/deselected with the checkbox in front of the name. You can alternatively (de)select the entire class or lib at once. Notice the blue label behind each function name: this is the name that you can use in the patterns. You see, the do-patterns evaluator has no knowledge whatsoever of namespaces, classes or functions, it just knows a single name. This means that all function names should be unique across a single project. If you try to enter a duplicate name, a red box will be displayed round the newly mapped name.
There are quite a few functions available for scraping. Basically though, there are 3 groups: some functions to open/close web-pages, some functions to get data from those opened pages and finally the same functions that don’t require you to first open/close any files but which can do a scrape directly.
Short scrapes
Depending on how much data you need to retrieve, you can use one or the other technique. If there is only 1 xpath that you have to run on a page, then you can probably best use the short/direct functions that don’t require you to first open the web-page. Instead the address is supplied as an argument, together with the xpath. Here’s a list of the available quick scrapers:
| Name | Arg 1 | Arg 2 | result |
| ScrapeText | file or web path | XPath | 0, 1 or more text values |
| ScrapeInt | file or web path | XPath | 0, 1 or more int values |
| ScrapeDouble | file or web path | XPath | 0, 1 or more floating point values |
| ScrapeDate | file or web path | XPath | 0, 1 or more dates |
And a short usage example to get the temperature info from the google API for a city that’s defined in ‘$place’:
$value = ScrapeText(“http://www.google.com/ig/api?weather=$place:interleaf(+)”, “/xml_api_reply/weather/current_conditions/temp_c/@data”)
As you can see, the first argument specified the web-page to open. The second is an xpath to the data attribute of the ‘temp_c’ element. Note that we use ‘:interleaf(+)’ cause the google API expects city-names that contain multiple words to be separated with a ‘+’ like: New+York.
More scraping
The second scraping method is primarily useful if you need to run multiple xpaths on the same content. In this case, it’s far more economical to first retrieve the page, run all the queries on the cached file and finally, when done, release it again. This can be accomplished with the remaining scrape functions.
You open a file or webpage with either ‘OpenScraper’ or ‘OpenScraperHTML’. The first works on xml content, the second on html. That is, the second will convert html to xml so that the xpath can be run on it. Both return an integer that needs to be used in subsequent calls. Basically, the integer replaces the filename as a reference. It allows you to have multiple files open and to have the system run multi-threaded and let it serve multiple people at the same time.
The scraping functions themselves are almost identical as the quick versions, except that they take an integer as first argument instead of a path. Other then that, usage is exactly the same, with the same types: one for text, integers, doubles and dates.
Once you are done with the file, you have to call ‘CloseScraper’ with, as argument, the integer that was returned by ‘OpenScraper(HTML)’, so that resources can be cleaned up. This is important, if you forget to do this, the system will eventually buckle, crack and give up.
In a normal usage situation, you would do a short salvo: open a page, do a few scrapes and close it again, all in 1 block, but this is not a requirement, you can keep the page open across multiple inputs. As long as you maintain a reference to the scraper (the integer) somewhere in memory so that you don’t loose track of it.
Html scraping
As already mentioned, html scraping is done by first converting the page into xml before the xpath is executed. This conversion can cause some ‘changes’ in the structure of the file. In other words, the path that you would calculate, based on the html file might not be correct for the xml version. This means that you best build your xpaths based on the xml version of the HTML pages.
The conversion routine that’s internally used by the chatbot designer is based on the SGMLReader library. This provides a command-line tool to manually convert html to xml files. This can be very useful for building the correct query. I’ve included a direct download for the command line html to xml conversion tool. Here’s a short description on how to use it (taken from the original documentation):
sgmlreader <options> [InputUri] [OutputFile]
| -e “file” | Specifies a file to write error output to. The default is to generate no errors. The special name “$stderr” redirects errors to stderr output stream. |
| -proxy “server” | Specifies the proxy server to use to fetch DTD’s through the fire wall. |
| -html | Specifies that the input is HTML. |
| -dtd “uri” | Specifies some other SGML DTD. |
| -base | Add an HTML base tag to the output. |
| -pretty | Pretty print the output. |
| -encoding name | Specify an encoding for the output file (default UTF-8) |
| -noxml | Stops generation of XML declaration in output. |
| -doctype | Copy <!DOCTYPE tag to the output. |
| InputUri | The input file name or URL. Default is stdin. If this is a local file name then it also supports wildcards. |
| OutputFile | The optional output file name. Default is stdout. If the InputUri contains wildcards then this just specifies the output file extension, the default being “.xml”. |
Examples:
sgmlreader -html *.htm *.xml
Converts all .htm files to corresponding .xml files using the built in HTML DTD.
sgmlreader -html http://www.msn.com -proxy myproxy:80 msn.xml
Converts all the MSN home page to XML storing the result in the local file “msn.xml”.
sgmlreader -dtd ofx160.dtd test.ofx ofx.xml
Converts the given OFX file to XML using the SGML DTD “ofx160.dtd” specified in the test.ofx file.
Building an XPath
Once you have your xml file, getting the xpath to the element that you want can still be a little challenging. Html files simply aren’t designed with this type of usage in mind (and hey, if it can be easier for xml files, why not). Enter FireBug, an add-on for Firefox that allows developers to get a closer look at the html…. Or xml. After you have installed firebug and loaded up the xml file into firefox, go to tools/Web developer/Firebug/Open firebug so that you can see the debug panel. In this panel, select the element that you which to query, open the context menu and select ‘copy XPath’. And that’s it, simply paste this path in the chatbot designer and your done.
Besides text, a chatbot should also be able to handle other types of basic data, like numbers, date-time and time-spans.
Numbers
The engine of the chatbot designer app will automatically convert every part of the input text that it can, into an integer or double. Furthermore, with the aid of the thesaurus, you are also able to pick up words that represent numbers and convert them into their corresponding number values. This allows us to easily work with and manipulate these numbers. Here are a few input pattern examples:
^n:number
^n:integer
^n:double
^n:noun.(language unit).numeral
The first 3 examples are the simplest: you can read a double (floating number), an integer or if you don’t care which, you can simply use ‘number’. The last one is a bit more interesting. Here we use the thesaurus to catch words like ‘one’, ‘two’,… Of course, that’s very limiting, cause this way, you’d have to declare a thesaurus-entry for every single number in the universe, which is, well…, impossible. So better to write the last pattern like so:
^n:noun.(language unit).numeral { [and] ^n:noun.(language unit).numeral}
The {} operator is used to find a repeating block in the input. So this catches things like: two thousand and five hundred. As you can see, we use the same variable name for the number in the front and repeating part. This means that most likely, the ‘n’ variable will contain a list of words instead of just 1. Also, we don’t collect the ‘and’ cause it would screw up the conversion procedure. A small warning about the {} operator though: The application can’t find duplicate patterns when the {} operator is used, so watch out for this.
For completion, you could also include a pattern that handles mixed numbers and words. The conversion algorithm is perfectly capable of handling this mixed type of input.
Working with numbers
So, now that we can read in the numbers, lets look at how we can use them. let me just pick up where we ended: with word-numbers. Right now, the variable is still containing words (or a mix of words and numbers), but we actually need a single number, so this needs to be converted. Luckily, you don’t need to do this manually, but there’s already a function available for this. Here’s a usage example:
$value = $n:ToNumber
:ToNumber will convert the content of the variable into a number (only integers are currently supported, a ‘.’ is not yet recognized). Each individual word is converted using the thesaurus and finally added/multiplied together to form a single number. Now, this conversion algorithm is language dependent and at the time of writing, there is only an implementation for English. If you need it for a different language, best to contact me.
Of course, the primary purpose for having real numbers, is so that we can do calculations with them. As you’ll soon see though, this is done a little bit different compared to a traditional programming language. That’s because the chatbot designer’s pattern definition language is text oriented and not math. This means that if you were to type something like $n + $w, it would not perform the calculation, but print/store the ‘+’ sign in between the 2 numbers. So, to perform arithmetic, you need to use functions. here are some examples:
$result = $n:add
$result = $n:add($w)
$result = $n:subtract($w,$d,$f)
$result = $n:subtract($w,$d,$f:Add($h))
…
The basic principle is always the same: since a variable can contain a list of items (a set), it doesn’t really need arguments to perform the arithmetic operation, as long as it has enough items in the list. Because of this, most mathematical operations can be performed on more then 2 items. So you don’t write a+b+c, but if all 3 are in the same variable, you simply write :add.
If the variable doesn’t contain enough values or you want to include additional values, you can add those as arguments. For most functions, this list is unlimited and can always be nested, so you can do mathematical operations inside others.
For completion, here are all the currently supported mathematical operators:
| Function | Description | Nr of arguments |
| Add | Adds all the numbers | any length |
| Subtract | subtracts each item from the first | any length |
| Multiply | multiplies each item with the first | any length |
| Divide | divides each item with the first | any length |
| Modulus | returns the modulus of the 2 numbers | 2 items |
| Complete | completes the sequence of the 3 numbers | 3 items |
| Count | counts the nr of items in the list | any length |
| StDev | calculates the standard deviation of the values | any length |
| Min | returns the smallest number in the list | any length |
| Max | returns the biggest number in the list | any length |
| Avg | gets the average of all the values. | any length |
| Complement | removes all the items in the argument from the input variable and returns the result (the difference between the 2). | any length |
| Reverse | Reversed the order of the items in the list (last becomes first) | any length |
| Distinct | Removes all duplicates. If there are any arguments, these are first added to the end of the list before duplicates are removed. | any length |
| Union | Adds the arguments to the end of the input items. | any length |
Date and time
Next on the menu are dates and times. These are very closely related to numbers. Internally, the system doesn’t see a difference between a date and a time, it’s always a DateTime. Depending how you use the value, you can regard it as a date, time or DateTime.
First of, there is a special variable to get to the current system date/time: $time. This returns a DateTime-cluster which contains a list of numbers that together form the date and time. The order is predetermined by the system to make it language independent. For ease of use, there are functions to get to each part of the date, so you don’t need to memorize each position in the format (though it helps for putting dates back together). Here’s a table of all the functions to get each part of the date list (+ the index position of the item in the list):
| Index | Function | Meaning |
| 0 | Year | Gets the year number. |
| 1 | Month | Gets the month of the year. |
| 2 | Day | Gets the day of the month. |
| 3 | Hour | Gets the hour (24 hours in a day) |
| 4 | Minute | gets the minutes (60 per hour) |
| 5 | Second | gets the nr of seconds (60 per minute) |
| 6 | DayOfWeek | gets the nr day number of the week (0=Sunday, 1= Monday,…) |
And here are some output patterns to show how you can use them:
$time:day/$time:month/$time:year
$time:hour : $time:minutes : $time:second
The current month is ^noun.month[$time:month-1]
Note that I deliberately put spaces between the variables and the time indicator (‘:’) to avoid using the escape character (\). Also, in the last example, we get the name of the month from the thesaurus through the index number. A thesaurus child at a specific index position can be accessed with the [] operator in the variable path. Because there’s no point of putting words between the brackets and this so often requires a small calculation (0 vs 1 based indexes), I have opted here for a more traditional calculus notation. Supported operators are: +-*/%.
Often it’s also useful to recompose a date, based on a series of numbers. For instance, to build the birthday of someone. As you probably already suspected, there are functions to do just that. Here are some examples:
$date = $list:ToDate
$time = $list:ToTime
$date = $year:ToDate($month,$day)
For building the date, the first value in the list has to be the year. This is the only required item, but more can follow: month, day, hour, minute, second. In that order. To build a time value, the first item has to be the hour. Minutes and seconds are optional, but have to be provided in the previously mentioned order. If you want to omit the minutes (or any other) part , use 0 instead.
Why not simply store the date values as individual numbers in memory fields yourself, you might wonder instead of going through the hassle of converting a number into another number (which these conversion functions basically do). Well, that will become apparent in the next section.
querying on date
The date and time values are stored in such a way that a year, month, day, minute, second or week in a date is always represented by the same neuron. This means that you can easily find all the dates (and therefor the attached assets) that meet certain criteria (all in the month June for instance). This is very useful in queries like “Who else is born in the same year and month as I am?”. Getting the date objects is real easy. Here’s an example:
$people= #user.birthday:year:GetDates:ValueFor(birthday)
$people = $people:Complement(#user)
$people = #($people).name
–or, for year and month:–
$people= #user.birthday:year:GetDates(#user.birthday:Month):ValueFor(birthday)
$people = $people:Complement(#user)
$people = #($people).name
Note: For a full example, check the Date-Time demo found at {documents}\nnd\demos\dateTime.dpl.
In the first line, we get the year section of the user’s birthday. With the ‘:GetDates’ function, we retrieve all the date objects that the system has stored for the same year as the user’s birthday. If you supply extra arguments, those values will also have to be included in the date, as is shown in the second example. With ‘:ValueFor’ we retrieve all the assets that link to the date. The ‘birthday’ argument, specifies that we only want the assets that link to the date value with the ‘birthday’ attribute. We could have omitted this, in which case we would have gotten all the assets, no matter how they are linked (it could be a ‘when’ reference for instance). Or, we could also have given more arguments, specifying a list of possible attribute values.
At this point, we have a list of assets that reference a date of the same year as the user’s birthday. There are still 2 things that need to be done: first we need to remove the ‘#user’ from the asset list, cause the ‘GetDates’ returns all objects, including the date object that represents the user’s birthday, but since the bot was asked ‘who else’, we need to remove the ‘#user’. This is done with the ‘:complement’ function. This is a mathematical function (borrowed from set-theories) and will return a list that contains all the items from the input with all the arguments removed.
We do the call to ‘:Complement’ on a different code line and not on the first one for a very specific reason: asset paths, like in the first assignment, always provide a single value as input to each function in the path, so even if ‘GetDates’ returns a list of items, ‘ValueFor’ will always work on a single value. At the end, the asset path joins all the results together. This, of course, is of no use for the Complement function, cause we need to remove the items from the list and not from a single value. Luckily, regular variable paths do provide a list of items as input values. That’s why we started a new assignment, using a variable path to calculate the complement. There are a couple of other functions (like Interleaf, Filter, Add, Multiply, Avg, Min, Max,…) which require a list of items as input. These should never be used in an asset path, but only in variable paths, for the reason explained above.
In the last line, we get the name value of all the assets that we found. This is again on a different line cause we need an asset path to get the ‘name’ of an asset. Regular variable paths don’t know how to do this. Hence the extra 3th line. And thus, we get the names of the known objects that have a birthday in the same year as the user.
Time ranges
Besides the Date-Time type, there is also a Time-Span type. This specifies a length of time instead of a specific moment. You get a Time-Span by building it with a function, or as the result of some Date-Time calculations (see later). To build a time range (or time-span), you can do something like:
$range = $list:ToTimeSpan
$range = $days:ToTimeSpan($Hours)
Unlike a DateTime, a timespan contains the number of: days, hours, minutes and seconds (no year or month sections). so there are only 4 elements in the list. The first item in the list should always be the nr of days, possibly followed by the hours,… Missing data is set to 0. Like with :ToDate and :ToTime, you can also specify missing data as arguments. The same order applies.
For retrieving values in a TimeSpan, you can use the same functions as for the DateTime. They operate in exactly the same way, but the index position of the actual items in the list is different compared to a DateTime. here’s an overview:
| Index | Function | Meaning |
| 1 | Day | Gets the total nr of days in the range. |
| 2 | Hour | Gets the remaining nr of hours. |
| 3 | Minute | gets the remaining nr of minutes. |
| 4 | Second | gets the remaining nr o seconds. |
At the time of writing, TimeSpans don’t yet support the same level of querying as the DateTime object does. If anyone has an urgent need for this, let me know, and I’ll see what I can do.
Calculating with dates and times
Like with regular numbers, you can also perform some calculations on dates, like subtracting one date from another. The syntax for arithmetic with dates/times is exactly the same as with numbers. Here’s an overview of what’s supported:
| Function | combination | results |
| Add | Time + {range} | Time |
| range + {range} | Range | |
| Subtract | Time – {Time} | Range |
| Min | Time, Time,… | Time |
| range, range,.. | Range | |
| Max | Time, Time,… | Time |
| range, range,.. | Range | |
| Avg | Time, Time,… | Time |
| range, range,.. | Range | |
| StDev | Time, Time,… | Time |
| range, range,.. | Range |
In general, the principle is always the same: the function input determines the type of the result, except for ‘Subtract’ which always returns a range and only works on Time values.
Boolean operations
As a final topic, perhaps a word about logical or Boolean operations on numbers, dates and ranges. Well, for the most part, these work as you would expect both for regular numbers and times/ranges. There’s just 1 small twist. The mathematical people out there will probably have noticed that set-theory is very present in this system: all variables can contain a list of items. This means that Boolean operators need to be prepared for this. Here’s an overview on how each operator handles lists:
- Equality (==): If the left part contains a list, the right side must have the exact same list: with the same nr of items, in the same order.
- Difference (!=) If the left part contains a list, the right side is different if it contains a different amount of items, the sequence is different or one or more of the items differ.
- Contains (contains): The right side of the operator should be part of the list found on the left side of the operation.
- doesn’t contain (!Contains): The right side of the operator should not be part of the list found on the left side of the operation.
- >, >=, <, <= (Bigger, bigger or equal, smaller, smaller or equal): If the left part contains a list, each item in the list will be evaluated to each item found on the right side. The operation succeeds if all items are compared and none failed.
There is plenty more to say about this topic. But my time-range has run out, a time to move on has been reached. All that’s left for me to say is: Stay tuned and CY later.
‘A because B’ and ‘Why C’ are 2 tricky statement types for a chatbot to handle correctly. Not only does the data need to be stored and retrieved correctly, but often, some words need to be replaced, like ‘I’ vs ‘you’. Here are some possible techniques you can use with the chatbot designer to handle such input.
Because
Let me first give an example of a ‘because’ statement that a user might say:
I have a dog because I like animals.
This statement is a bit of an oversimplification, perhaps just right for an example. It doesn’t only specify why I have a dog, but also that I have a dog and that I like animals. All this data should be stored and retrievable so that the system can answer questions like:
Why do I have a dog?
Do I have a dog?
Do I like animals?
So, before we can store the data, we need to find an input pattern that’s able to handle ‘I have a dog because I like animals’. To start with, we could do something like:
I have a $value because $reason [.]
This can work for many possible input values, except that the ‘I’ vs ‘you’ change is difficult to do since there is no knowledge of the context (‘you’ can change into ‘I’, ‘me’,… depending on the context), so this is the least usable pattern, but there are other possibilities. For example, lets take:
I have a $value because I like $object [.]
or better:
I have a ^value:noun because I like ^object:noun [.]
With this input pattern, we are able to extract all the valid data so that it can be stored in memory and we also have static text for ‘I’, which can easily be transferred manually. A small improvement might be to replace ‘$value’ with ‘^value:noun’ so that only a proper noun can be captured instead of things like ‘I have a pretty big keyboard on my lap sitting there because I like typing’. The same goes for ‘$object’. As in the previous example, ‘typing’ could probably better be transformed to ‘type’ before you store it, so in the pattern you could replace ‘$object’ with ‘^object:verb’ or ‘^object:noun’,… Anyway, lets take a look at how the do-patterns would look like for storing this info:
#user += $value
#user.like.($object) = yes
#user.($value):why = “because you like $object”
In the first line, we create a ‘has’ data item for ‘$value’. The second line adds the ‘object’ to the ‘like’ list, with the value ‘yes’ so that it’s clear the user likes it. And the third line stores the reason why the user has a ‘value’. ‘:why’ is a function that provides access to the ‘reason’ data-tree. What you store in ‘:why’ is completely up to you. In this example, we store a text, between brackets, cause I wanted to preserve the spacing, but you could also treat it as a sub asset (or anything else, like a link to another rule), allowing you to write paths like #user.($value):why.object.
Why
Once you have stored the information, you can retrieve it again if the user asks a why question. Here’s a possible input pattern that you could use for capturing this question:
Why do I have a $value [?]
or better:
Why do I have a ^value:noun [?]
And the output pattern could look something like:
You have a $value #user.($value):why
or better:
Bot says when: #user.($value):why
You have a $value #user.($value):why
else:
I don’t know why you have a $value
That was easy enough. The first line is the most basic approach, the only problem with it: if the bot doesn’t know, it returns ‘You have a’. Which is no good. Better to put it in a conditional and check if there is a ‘why’ value, if there is non, let the bot say it doesn’t know. To check if there is a ‘why’ value, simply put the path in the ‘when’ close. This will check if the result of the path has a value. To check if it doesn’t have a value, use the not (!) operator in front of the pattern, like so:
Bot says when: !#user.($value):why
I don’t know why you have a $value
else:
You have a $value #user.($value):why
Improvements
Right now, we have a working system to handle because/why type of things, but it’s not very flexible, I mean, there are tons of reasons why you can have something, and not just because you like something else. Also, there are more people then just ‘I’, so lets improve the input patterns a bit and make things more flexible.
Using the Thesaurus
Lets start with ‘I’. As you had probably already guessed, the thesaurus variables are ideally suited for this. So, here’s an improved input pattern:
^s:pronoun.subject (have|has) a ^value:noun because ^w:pronoun.subject like ^object:noun [.]
Ok, that’s already a bit more complicated. The ^s variable can capture any child of the pronoun ‘subject’. If you check in the thesaurus, under the pronoun POS, there are a bunch of greyed-out items, one of which is ‘subject’. These are ‘placeholders’, that is: these thesaurus items don’t actually contain a word, only a label. So our ^s variable can’t actually catch the word ‘subject’, but only it’s children: I, you, he, she, they, we, it.
The do-patterns also become a bit more complicated, since we will now have to calculate the inverse of the pronoun and we also have to extract the asset out of it. Luckily, there are some helper functions for this:
#($s:ResolvePerson) += $value
#($w:ResolvePerson).like.($object) = yes
#($s:ResolvePerson).($value):why = “because &w:InvertPerson ^verb.like:conjugate($w:ResolvePerson) $object”
:ResolvePerson is able to extract the asset (or concrete representation) out of a word. At the time of writing: I and you are supported. Names, ‘He’, ‘she’, ‘it’,… need some further testing.
:InvertPerson uses thesaurus links to jump from one person to it’s inverse, if there is one. So ‘you’ becomes ‘I if ‘you’ was used as a subject.
:Conjugate will try to find the correct conjugation of a verb, based on the argument that is supplied. This has to be an asset, usually the asset representation of the sentence-subject. So if you want to conjugate for ‘I’, you pass in ‘#bot’. ‘You’ becomes ‘#user’, and so on.
Split the patterns
The second improvement that we can make to the pattern is a bit more radical and relies on a special feature of the pattern matcher. You see, the pattern matching process is not restricted to finding just 1 pattern in the input. It will try to find the longest possible sequence of patterns that it can. That is, if the same words can be caught with a single pattern, this pattern will get precedence over a sequence of patterns. But if the pattern matcher can do a longer match by using 2 patterns instead of 1, that will become the result. This allows us to split up the first part of the sentence: ‘I have a $value’ from ‘I like $object’. opening up a whole new range of possibilities, and more importantly: saving use lots of duplicate work. The do-patterns become a little different though. First the input-patterns:
Topic: HAVE
^s:pronoun.subject (have|has) a ^value:noun
because ^s:pronoun.subject (have|has) a ^value:noun
Topic: LIKE
^w:pronoun.subject (like|likes) ^object:noun [.]
because ^w:pronoun.subject (like|likes) ^object:noun [.]
As you can see, not much has changed except that they are now 2 sets of patterns and each set has a version with and without ‘because’ in the front. We could also have written them as:
Topic: HAVE
[because] ^s:pronoun.subject (have|has) a ^value:noun
Topic: LIKE
[because] ^w:pronoun.subject (like|likes) ^object:noun [.]
But we aren’t. Instead, we keep each input-pattern in it’s own rule so that each can have it’s own set of do-patterns. This way, the ‘because’ version can be treated differently. Basically, what it comes down to is this: when there is no ‘because’, we simply store an extra memory field called ‘subj’ which allows us to recall the left part of the asset operation (excluding the attribute). When we have a because, we check if the ‘subj’ field is set and if so, we store the ‘:why’ in this field. Here are the do patterns:
Topic: HAVE
#bot.who = $s:ResolvePerson
#bot.Inverted = “#bot.Inverted because $s:InvertPerson ^verb.have:conjugate(#bot.who) a $value”
#bot.attribute = $value
#bot.who += $value
#bot.value = #bot.who.($value)
#bot.Subj = #bot.Who
#bot.Subj.(#bot.attribute):why = “because $s:InvertPerson ^verb.have:conjugate(#bot.who) a $value”
Topic: LIKE
#bot.who = $s:ResolvePerson
#bot.Inverted = “#bot.Inverted because $s:InvertPerson ^verb.like:conjugate(#bot.who) $value”
#bot.value = yes
#bot.attribute = $value
#bot.who.like.($value) = yes
#bot.Subj = #bot.who.like
#bot.Subj.(#bot.attribute):why = “because $s:InvertPerson ^verb.like:conjugate(#bot.who) $value”
The full code example can be found in {documents}\NND\Demos\Why_Because2.dpl. As you can see, it’s a bit more code then where we originally started. Though if you look a little closer, a lot of it is boiler-plate stuff: store who, attribute, value, subj, inverted.
Key advantage here: with only a few patterns we can have any type of combination: have because like, like because have, have because have, like because like, because have, because like, have, like, have because like because have,….
The basic set-up is always the same, we dissect the sentence into it’s parts so that they can be reused in other parts. here’s what we need:
- calculate ‘who’ (the asset form of the subject part in the sentence = I, you, he,…) so we can reuse it and don’t have to recalculate it each time.
- store the the inverted sentence, for output generation (can always be useful)
- the attribute and value (‘color’ is the attribute of ‘yellow’). In this example, strictly speaking not really required, but other parts of the concept rely on this data, so best to get used to it.
- subj: sometimes, the ‘who’ isn’t enough to find out where some data needs to be stored. ‘like’ (and most other verbs) is a good example of this. So if we want to get to the correct data path later on, we need to store this new path, hence the existence of ‘subj’.
- If ‘when’, ‘where’, ‘how’,… is also present in the sentence, these are all data parts that can be stored like attribute, value or more complex sub structures.
There are a few extra do-patterns required to get this working correctly. The ‘who, inverted, value, attribute, subj’,… fields of the #bot are all temporarily, that is: they are supposed to be used as mid-term memory, for as long as the current input is being processed, so that the information can be passed along from one pattern to another. This means that we need to remove the data after the input has been processed so that it wont interfere with any of the next input. This can be done from the Chatbot’s properties view (select the menu item view/chatbot properties, next go to the ‘Do after output’ tab.). here’s how it would look like for this example (can be simplified, which we will do in the next example):
#bot –= Inverted
#bot –= Who
#bot –= Subj
#bot –= attribute
#bot -= value
Also, in this example, I only used thesaurus variables. You can achieve similar results with regular variables, but there has to be a small change in the pattern definition for it to work properly. Everything has to do with the fact that a regular variable can only determine it’s end by what is defined after the variable, and if the pattern definition ends with a regular variable, it will collect the remainder of the input and never jump to another pattern. So we need to put something behind the variable if the ‘because xxx’ needs to be handled correctly. This can be done by moving the ‘because’ from front to back like so:
Topic: HAVE
I have a $value [.]
I have a $value because
Topic: LIKE
I like $value [.]
I like $value because
The biggest disadvantage: you need extra patterns to handle a ‘because xxx’ (not shown or included in the demo), but on the plus side, the do-patterns become a little simpler using this type of pattern definition style. For a complete example, see: {Documents}\NND\Why_Because.dpl.
Sub topics
There’s one more trick we can use to make the patterns more flexible and which will also resolve a final problem caused by using multiple rules as we did in the previous step. You see, finding a list of unrelated rules is primarily done to recognize multiple sentences in the input and not for splitting up a single sentence. That’s because all the outputs from those rules are always automatically combined. So you can’t change the order or content. This can make output management a little tricky.
The solution comes in the form of sub-topics or sub-rules. With this technique, it’s possible to reference a topic or rule by it’s name from within a pattern in the same or other topic. This allows us to split the ‘because’ out into a third topic and then group them all back together into a single rule, which will be the final, single result. So, on the input side, it becomes more flexible, and thanks to a special variable ‘$output’ and a switch in the chatbot properties, we can also take control of the output side. more on that later, lets first start with the input patterns, how do you declare sub-topics?
Topic: HAVE
^s:pronoun.subject (have|has) a ^value:noun
Topic: LIKE
^s:pronoun.subject (like|likes) ^value:noun [.]
Topic: BECAUSE
because
Topic: BECAUSEHANDLER
~have
~like
~becauseHandler ~because ~becauseHandler
The first 2 input patterns should be familiar by now, nothing has changed since before, only the ‘because’ version has been eliminated. The first new topic is also nothing special, just a single word ‘because’. The magic happens in the last topic, which groups all the other topics together.
~ is used to indicate a Topic reference. It’s always followed by the name of a topic and possibly a dot followed by the name of a rule. It’s meaning is simple: include all the patterns in the rule(s) of the specified topic at the location of the reference. Here’s an example for sub rules:
~have.statement because ~like.statement //presumes that topics ‘have’ and ‘like’ have a rule labeled ‘statement’
Now, if you look at the last topic: ‘BECAUSEHANDLER’ you’ll notice that it only contains references to other topics, it’s a root topic. In this example, it’s purpose is to provide a place to declare the output for every topic, so we can combine things correctly + it also stores the ‘why’ relationship. Also, if you look at the last pattern, it references it’s own topic, 2 times. This is recursion and allows us to recognize a sequence of ‘becauses’ like: I have x because I like y because I have z,….
You might be wondering why it’s the BECAUSEHANDLER that stores the ‘:why’ link and not the ‘BECAUSE’ itself. That is because at the time of the ‘Because’ pattern, the reason is not yet known (this is defined in the next pattern, which hasn’t been processed yet), so it can’t link anything up yet. To overcome this, we make certain that there is an extra rule that gets executed after every other part of the sentence: the BECAUSEHANDLER. In other words, the ‘becauseHandler’ is a way to perform some code after all the patterns have been processed.
By the way: topic names can be edited in the ‘project view’ (select the topic, press F2 or right mouse click/rename). A topic name should be unique within the project if you want to use it as a sub-topic, otherwise it’s not that important, but the UI will always warn about duplicate names and those topics will have a red icon instead of blue. The name of a rule is always visible in the ‘description view’s title when the rule is selected. It can be changed in the topic editor: select the entire rule (don’t click on a pattern, but on the background of the rule). With F2, you get a dialog to change the name.
Let’s continue with the do patterns. For ‘have’, ‘why’ and ‘like’, they remain very similar: the patterns are used to store the inverted sentence, who (asset), the attribute, the value and the ‘subject’. In this example though, instead of directly storing it under the ‘bot’, it is stored underneath ‘mem’ so that we can move the entire result set with 1 statement later on. So here’s a small example of the ‘have’ do-patterns:
#bot.mem.attribute = $value
#bot.mem.who = $s:ResolvePerson
…
And so on. The really interesting stuff happens in the ‘BECAUSE’ and ‘BECAUSEHANDLER’ topics. Note that this time, we have some do-stuff in the ‘calculate’ area and others in the ‘do’ section. The major difference here: ‘Calculate’ is done, just before any of the conditions are evaluated, so this allows us to do pre-calculations that can be used in those conditions.
Topic BECAUSE:
#bot.because = #bot.mem
#bot -= mem
Topic BECAUSEHANDLER:
Rule ~becauseHandler ~because ~becauseHandler
Calculate:
$result = “because #bot.mem.Inverted”
$path = #bot.because.Subj.(#bot.because.attribute)
Output when: #($path):why == $result
Yes, I now #bot.because.inverted because #bot.mem.inverted\.
else
I see, #bot.because.inverted because #bot.mem.inverted\.
Do
#($path):why = $result
Rule ~have
$output
First off, BECAUSE: this moves the ‘mem’ field to ‘because’ and ‘mem’ is cleaned/deleted. Basically, we store the data of the previous sentence and prepare to collect the data for the next sentence. The ‘because’ field will later on be used to link to the newly collected ‘mem’ field. Note that this rule doesn’t generate any output.
Secondly comes the ‘BECAUSEHANDLER’. This builds up the result that needs to be stored in ‘:why’ and calculates the location where this info needs to be stored (in $path). Before actually committing the data to memory though, a check is done to see if it was already known. If so, a different response is given compared to when it is not yet known. In the latter case, the info is also stored.
For the other rules (~have and ~like), we simply declare the $output variable in the output section, indicating that we want to reproduce the output of previous topic. Note that the use of this $output variable can be controlled in the chatbot’s properties view. When turned off, it will function as a regular variable, and the output of all the patterns will simply be combined. This allows you to select between a simple styled bot or something more advanced.
Finally, as already mentioned, we still need to do some clean-up after the input. Since we have grouped all the mid-term memory in 2 fields: ‘mem’ and ‘because’, cleanup becomes a little simpler (Note: sometimes the ‘because’ part doesn’t exist, but that’s ok, nothing will be removed in this case):
#bot –= Mem
#bot -= because
I guess that’s about it for now.
Well, we went from simple, straight forward, fire-cracker-leveled patterns to something that’s more akin to ‘rocket science’. The combination of memory, thesaurus and sub-topics might just turn out to be a very explosive mix. I for one, am very interested to see where all this will eventually lead too… Stay tuned.
The latest release, version 0.8.5 is now available for download. The major changes in this version is centered round speech input and output.
support for multiple speech engines
The first, and most fundamental change is the fact that the system now supports multiple speech engines. At the moment, there is an implementation for the managed and unmanaged Microsoft SAPI versions. Others might follow. The major reason for this feature is that the SAPI libraries behave differently on different systems and OS’s. This was most apparent in the fact that some voices aren’t properly supported in the managed version. That’s why the unmanaged version has become the default, since it is the most powerful and most stable (also in terms of timing), but XP for instance doesn’t support SAPI 5.3, so this requires the managed version. To select the engine you want to use, go to the menu item: tools/options, select the ‘Speech’ tab and use the combo box to select one of the engines. Note: before switching engine, it’s best to turn off audio input/output on all chatbot channels.
Alternate input
Also new in this version is the ability to select between alternative input text for voice recognition. The system wont yet automatically be trained with this information though. You can turn this on/off with a new button on the chatbot-local toolbar, to the right of the Audio-in button. When turned on, and the speech engine can provide alternate text values, a dropdown-box will be displayed just below the text (one for each speech salvo). You can select an alternate text from this box. Note that if there aren’t any alternatives, no dropdown is shown. Also, the managed engine can give different results compared to the non-managed version.
New demos
Finally, there are a few new demos included in the installer. 3 new demos that show different ways to implement why-because (and also show how to do have and like). The previous release also had a demo for why-because, but it still had some issues.
That’s it for now, more things to follow…
I just had to get a release out on this date, I mean: 1/11/11, ye!
For the curious, there are actually a number of fixes/updates all over the place:
- Importing/exporting part of a thesaurus has been fixed.
- There was a problem in the ‘rebuild project’ procedure for the thesaurus which has been fixed.
- A do-statement of the form #x.z = #y now gets a correct y value.
- There’s a new CmdShell extension for scraping xml and html files (from the internet or disk)
2 new demo projects:
- weather.dpl: uses the scraper extension to get the weather info from the Google weather api.
- Why_because.dpl: shows a way how you can correctly handle why/because statements. In the process, it also shows how to do ‘be’, ‘have’ and ‘like’. 2 more versions of this demo will follow shortly.
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). These are the 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.
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.
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:interleaf(“\, “, ” 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 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 ‘,’. Note: if you use the ‘,’ sign as an argument value, it must always be escaped with a \ Also, if you need to preserve spaces, the argument should be placed between brackets (as in the last example).
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:interleaf(“\, “, “ and “)
a book is made of $(#(^noun.book).component.name):interleaf(“\, “, “ 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 ‘:interleaf’, 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 ‘:extra’ function, 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 ‘:Remove’ function.)
As already mentioned, every asset value can always use the ‘:extra’ function to get to a sub-asset. There are a few other functions 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” |
| :when | provides access to the ‘time’ path | #user.dog:when = “10 years ago” |
| :where | provides access to the ‘location’ path | #user:where.preposition = in #user:where.object = chair or #user:where = “in the chair” |
| :how | provides access to the ‘method’ path | #user.dog:how = received from some friends who had a bit of an accident |
| :amount | Allows you to specify that the same value should be counted multiple times. When the value is an asset, it indicates how many identical assets should be counted. | #User.hand:count = 2 |
| :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.memory
#bot –= memory
do on startup:
#bot –= memory
#bot –= prevmem
on pattern (example pattern = I like $value [.])
#bot.memory.subject = #user
#bot.memory.attribute = like
#bot.memory.object = $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.
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.
It’s possible to create your own characters for the chatbot designer app. The process consists out of 2 parts: first you need to create a set of images, once that’s done, you need to make a ccs file that combines all the images.
Images
The animations and visemes are stored as a series of images which are displayed in rapid succession, much like how old school film works. Before you can display these images though, they need to be created. Now, in the olden days, they used to have big chunky camera’s for that. Things have changed a bit since. We can use tools like DAZ3d, Poser, Blender,…
Content
What you put in the images is entirely up to you. There aren’t many limits with respect to content in the sense that there aren’t any ‘expected’ parts. That said, since we are dealing with chatbots, I’d try to put in something resembling a mouth somewhere, just to get some lip-syncing working. Most 3D tools these days, come with some way to manipulate mouth positions. The designer uses 21 different images for lip-sync (+ 1 for silence, which is actually the background image). Most 3D tools only provide morphs for 16 mouth positions. The remaining 5 visemes can be created by reusing other images or by creating your own, if the 3d app allows it. Here’s a good visual overview of all the visemes.
Animations like blinking eyes or moving hair also need to be exported from your 3D package as a series of images. Luckily most 3d software provides a feature to just that. Also, some animations can use the same image multiple times, it’s always best to reuse the same image in those cases, to save memory. For instance, an eye blink can be done with 2 or 3 images: eye fully open is provided by the background, half closed and fully closed.
File formats
Currently, the following file formats are supported:
| BMP | bitmap |
| GIF | Graphics interchange format |
| JPEG | Joint photographics experts group |
| PNG | Portable network graphics |
| TIFF | Tagged Image file format |
It should also be possible to use xaml files (to create flash typed characters) , but this is not yet tested and will most likely not yet work. Give me a shout if you would like to do this.
Processing
Once you have created all the images, it’s best to process them a little further. At this stage, every image always uses up the full viewable area of the character. That’s ok, if all you want to do is lip-sync and possibly a little animation during idle times. But in this setup, your character can’t blink while speaking. To accomplish this, we need to cut out only the valid parts of each image and make the rest transparent. This way, the system can overlay multiple images and create the illusion of different parts moving at the same time.
Another added advantage of this process is that it usually (depending on the file type) shrinks the file. PNG files, when edited with the correct tools, will use less space if there is a bigger transparent area.
There are multiple ways you can crop the images. You can use the eraser tool found in most bitmap editing tools like paint.net. Though, I’ve noticed that at least some versions of Photoshop don’t shrink png files when using the eraser, so that’s maybe not the best tool for the job. Personally, I used a little home-made app to do the
job. You can download it from here.
It’s very simple to use. As you can see on the screenshot, there are 2 big buttons to the left which contain the source images. Press on each button to load the images. The top image should contain the root, in the bottom, you put the images that need to be cropped. To the right, on top, you see the result for the currently selected target image (seen in the bottom button). With the slider, you can scroll through the loaded images.
On the next line, there is a checkbox and a slider. These control the calculation process: do you want the parts of the image that are different or the same, and with the slider you select the tolerance level used to calculate the difference. When you put the slider fully to the left, there is 0 tolerance, meaning that there can be no difference between the pixel in the source image and that of the image that needs cropping. Put the slider fully to the right and the difference has to be very big.
Finally, with the button labeled ‘save’, you can save the processed images to a directory that you select (the original files are kept).
Depending on the way that the images are rendered in the 3D package, the file format and the quality of the rendering engine, there can be a bigger or smaller difference between parts of the images that should be the same. That’s why you normally have to play a little with the tolerance level. The lower, the better. Sometimes it’s better to keep a lower tolerance level and manually remove the remaining dots with an eraser in a bitmap editing package.
CCS file
When you’re ready with your images, it’s time to put them all together, a bit like a collage. Unfortunately, there currently isn’t yet a ready-made tool for this, so you are gonna have to do a little xml writing. Fortunately, there’s a lot of copy paste involved. The outer xml tag, the start of the file, is a <Character> tag, like so:
<?xml version="1.0" encoding="utf-8"?>
<Character xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
</Character>
Now, Let’s go over each section in order of the file content:
Character Info
The first section, and the easiest, is the character information. This is how it looks like:
<CharacterInfo>
<Name>Mika</Name>
<Author>Ady Di Pierro</Author>
<Copyright>Copyright 2011 Ady Di Pierro</Copyright>
<License />
<AuthorWebsite>http://www.laticisimagery.com.au/</AuthorWebsite>
<CreationDate>2011-08-07T14:43:26.602+02:00</CreationDate>
<LastUpdateDate>2011-08-07T14:43:26.602+02:00</LastUpdateDate>
<Rating>
<Rating>Unknown</Rating>
<Sexual>false</Sexual>
<Violence>false</Violence>
<Other>false</Other>
<Description />
</Rating>
<OnlineOptions>
<OnlineCharacterBaseUrl />
<PreferredWidth>150</PreferredWidth>
<PreferredHeight>150</PreferredHeight>
</OnlineOptions>
</CharacterInfo>
Just copy the above and paste it into your xml file, just under <Character>. This part only contains reference information about the character: what’s it’s name, who designed it, what’s the licence, possibly a website,… All of the info in this section is optional, you can leave the tags empty, but it’s best to include them. This information, by the way, is displayed in the popup window on the ‘chatbot window’ (the button in the lower-left corner on the images window).
Online options are currently still skipped but will probably be used in future, online versions.
Background
<Background>
<ImageResource>images\Kima00.png</ImageResource>
<ImageResource>images\KimaEars.png</ImageResource></Background>
The background section defines the images that should be used as background (obviously). 2 things worth mentioning: a background is defined as an ImageResource. This is used often throughout the file. Whenever you want to reference an image file, you use this element. The text part of the xml element defines the relative path to the image (that is relative to the CCS file).
Also, and that’s perhaps the weirdest part, you can declare multiple backgrounds. Every image in this list will be displayed as a background, unless an animation turned one of the images off. And that’s the main usage of having multiple backgrounds: so that animations can turn part of the background off while playing. A good example are Mika’s ears. They are drawn in a separate background image, so that the ‘flip-ears’ animation, can hide the background-ears while it’s playing. We do this cause some images in the animation sequence are smaller then the ears in rest, which would otherwise give ugly results with half an ear overlapped and the rest still visible. I’m certain there are plenty of other cool tricks to done with this feature. Hiding a background image is explained in the ‘Animations’ section.
Animations
The ‘Animations’ section is one of the bigger parts of the file. This is where you declare all the animation sequences available to the character for emotional expressions and idle times. The background animations (which run all the time, not jus at idle times), are declared somewhere else. Anyway, here’s how an animation definition looks like:
<Animations>
<Animation>
<AnimationFrames>
<AnimationFrame>
<Duration>5</Duration>
<ImageResource>images\other\Kima Ears (01).png</ImageResource>
<VisemeGroupName/>
</AnimationFrame>
<Duration>5</Duration>
<ImageResource>images\other\Kima Ears (01).png</ImageResource>
<VisemeGroupName />
</AnimationFrame>
</AnimationFrames>
<EnableFrameSpeaking>true</EnableFrameSpeaking>
<HoldLastFrameForSpeak>false</HoldLastFrameForSpeak>
<FirstFrameUnderlay>false</FirstFrameUnderlay>
<BackgroundSuppress>KimaEars.png</BackgroundSuppress>
<Name>earsmove1</Name>
<ZIndex>0</ZIndex>
</Animation>
</Animations>
The section starts with an <Animations> element, which can contain 0, 1 or more <Animation> elements. Each animation defines a series of ‘frames’, where a frame represents a single image in the animation. A frame contains a Duration section, an ImageResource and a VisemeGroupName. The last one, you can forget about, it’s there because of backward compatibility reasons with the original CCS file format and should always be empty (for now). We’ve already been over the ImageResource element and the duration simply declares how long that the image should be displayed. This is expressed in milliseconds.
Underneath the frames, you need to declare some more info about the animation. Besides the name of the animation, which is used to start the animation in a rule’s output patterns, you have:
| EnableFrameSpeaking | When true, speech is allowed during the animation. When false, speech will wait until the animation is done. |
| HoldLastFrameForSpeak | Is currently not used and can be true or false. |
| FirstFrameUnderlay | When true, the complete background is hidden and the first frame of the animation is used as background |
| BackgroundSuppress | This element can be declared multiple times. Each element contains the name of a background image that needs to be hidden while the animation is running. |
| ZIndex | Optionally determines the ZIndex at which the animation is displayed. This is useful to move parts in front or behind other parts. The background always has a ZIndex of 0. |
It might seem a tremendous task at first to declare every frame in an animation like this. But it turns out that most animations can be build using between 3 and 7 images, sometimes reusing images to build sequences of about 10-20 frames. So all in all, this is still manageable.
VisemeGroups
The next section declares the images used for lip-syncing. The basic structure looks like the following xml snippet (note that it doesn’t contain an entry for all 22 viseme images, just the first 2).
<VisemeGroups>
<VisemeGroup>
<Name>Default</Name>
<ZIndex>1</ZIndex>
<VisemeImages>
<VisemeImage>
<VisemeIndex>0</VisemeIndex>
<ImageResource>images\Kima00.png</ImageResource>
</VisemeImage>
<VisemeImage>
<VisemeIndex>1</VisemeIndex>
<ImageResource>images\Visemes\Kima03 EH.png</ImageResource>
</VisemeImage>
</VisemeImages>
</VisemeGroup>
</VisemeGroups>
The file format already allows for multiple viseme groups to be declared, although at the time of writing, only the first one is used (and supported). Multiple viseme groups could be useful for moving heads: a viseme group for each head position. As such, the ‘name’ element for each group would be used to reference a group. But, as already mentioned, this is something for the future.
The ZIndex element defines the Z-order that should be applied to the viseme images. This allows you to manipulate the order of the images. For instance, you could use this to move a part of the background image on top of the viseme images.
Next comes the ‘VisemeImages’ group, with a ‘VisemeImage’ for each mouth position + optionally 1 extra image for the silence position. Each VisemeImage contains an ImageResource (as described above) and an Index (VisemeIndex), which determines the letter that the image represents (so the order in which the VisemeImages are declared, is irrelevant).
The silence is primarily for backward compatibility with verbot characters (which don’t have a separate background section). If you have a ‘Background’ section, the viseme image at index 0, will be skipped, otherwise it’s used as the background. That’s why the ‘background’ section has to be declared before the VisemeGroups.
IdleLevels
Idle time is the time when a bot doesn’t have anything to say or any emotion to show. In other words, nothing’s happening. In order to create the illusion of being alive during this period, you can use idle levels to start animations (that were declared in the ‘Animations’ section) when the bot is idle. Here’s the definition:
<IdleLevels>
<IdleLevel>
<MinStartDelay>5</MinStartDelay>
<MaxStartDelay>15</MaxStartDelay>
<MinDuration>5</MinDuration>
<MaxDuration>15</MaxDuration>
<MinInterval>2</MinInterval>
<MaxInterval>8</MaxInterval>
<AnimationNames>
<AnimationName>earsmove1</AnimationName>
<AnimationName>eyes squint</AnimationName>
</AnimationNames>
</IdleLevel>
</IdleLevels>
Like most other sections, you can again declare multiple IdleLevels. In this example, we only have 1 though. The idea behind multiple IdleLevels is this: when the idle time starts, the first idle level is activated, but when the duration of the level has ended, the next one is activated until the last one is reached, which remains running until some activity happens. This way, you can have a bot act differently after x amount of idle time, progressively.
Each idle level contains 4 bits of information: 3 time ranges and a list of animation-names that the idle level can use. Each range has a Min and Max component, indicating the lower and upper part of the range. The different times are used for:
| Delay | A value is selected at random from this range to delay the start of the animation (which is otherwise immediately after the last output) |
| Duration | A value is selected from this range to determine the duration of the idle level. This is only used if the level isn’t the last in the list. |
| Interval | Each time an animation finishes, a value is selected from this range to determine how long the system should wait before it starts another animation from the list (this is selected at random). |
BackgroundAnimations
Idle levels are very useful to create a sense of liveliness, but they can be expanded upon. Sometimes, it’s also useful to have animations run all the time in the background, even while speaking. A good example of this could be breathing or eye blinking. For this purpose, there is the final section, called ‘BackgroundAnimations’, which contains a series of special animation definitions, all of which will run all the time, in a loop. Here’s the definition:
<BackgroundAnimations>
<Animation>
<AnimationFrames>
<AnimationFrame>
<Duration>10</Duration>
<ImageResource>images\other\Kima Nose Flare (01).png</ImageResource>
<VisemeGroupName/>
</AnimationFrame>
<AnimationFrame>
<Duration>10</Duration>
<ImageResource>images\other\Kima Nose Flare (02).png</ImageResource>
<VisemeGroupName />
</AnimationFrame>
</AnimationFrames>
<Name>nosebreath</Name>
<LoopStyle>VarTimer</LoopStyle>
<MinStartDelay>1</MinStartDelay>
<MaxStartDelay>3</MaxStartDelay>
<ZIndex>2</ZIndex>
</Animation>
</BackgroundAnimations>
Unlike idle levels, the animation is declared inline this time. That’s because there is a small difference in declaration between this type and idle/emotion animations. Another reason is to make certain that background animations can’t be used as emotions (they run all the time anyway, no need to start them separately).
The way that the individual frames are declared is the same as with regular animations: a list of ‘AnimationFrame’ objects that define the image, the duration and an optional viseme group. The difference is in the extra options: there is an extra ‘LoopStyle’ and ‘StartDelay’ range. ZIndex is also the same as in animations: It determines the Z-Order at which the animation is displayed, the higher the number, the more to the top it will be.
The ‘StartDelay’ range is used to build in a small idle time between each animation loop. Each time that the animation needs to be started, a value is selected at random from within the range. That’s the delay which will be used. Not every loop-style makes use of this delay, some do, others skip it.
Finally, the LoopStyle element can have the following values:
| None | The least useful: no looping at all. |
| Jojo | At each start, the end frame is selected at random from the entire list of frames. When this end is reached, the animation is played in reverse until the first frame is reached and the loop starts again. Each start is delayed by x amount of time, where x is a random number picked from the StartDelay range. |
| FrontToBack | The images is played continuously, front to back without pauses. |
| VarTimer | The images is played front to back in loop, but each time with a delay that is selected from the StartDelay range. |
And that’s it. Once you’ve got all these sections laid down, you’ve got yourself a character. All that remains, is to copy the files to the {my documents}\NND\Characters dir and restart the chatbot designer. Enjoy.
The chatbot designer uses a number of different file types to accomplish different tasks. Here’s a short overview of all possible files (and sometimes directories):
Projects
A project contains the entire neural network (in binary form) together with a bunch of configuration files in a directory. This directory is accompanied by the main project file. Both have the same name. When moving around a project, make certain you have both file and directory. When opening and saving projects, you only need to worry about the file, the directory will be created/managed automatically (done in such a way that extra directories like for version control systems, will remain functioning).
The project files themselves (extension ‘.dpl’), are simple xml files which contain various configuration data for the designer. In general though, you don’t need to edit this file manually, all settings can be managed from within the designer.
The most important files in the project directory are binary and should never be manually edited. Some files are xml based, but apart from the test-cases, should be left ‘as-is’ since they contain vital configurations for the internal network.
Topic files
Topics form a major component of a chatbot designer project. They define what the bot can do. Internally, topics are stored as neurons, but in order to share a topic across multiple projects and users, Topic files were introduced. This is an xml file (extension ‘topic.xml’) that contains all the rules defined in the project.
The designer provides functionality to import and export topics one by one or in bulk from the File menu or the context menu on the Project tool-window. So ideally, you should never be confronted with the internals of these files. For the die-hard text-editor fans, here’s a little more details on the structure:
A minimal topic file should contain a topic, name, rules and questions sections. Like:
<Topic>
<Name>Who has</Name>
<Rules />
<Questions />
</Topic>
And a bit more challenging, containing a single rule with 1 input and output pattern pattern:
<Topic>
<Name>Time</Name>
<Rules>
<Rule>
<Name>new rule 1</Name>
<Patterns>
<Pattern>
<Expression>What time is it [?]</Expression>
</Pattern>
</Patterns>
<Conditionals />
<Outputs>
<Output>
<Expression>It is $time:hour\:$time:minute\.</Expression>
<QuestionCanFollow>True</QuestionCanFollow>
</Output>
</Outputs>
<IsOutputSequenced />
<DoPatterns />
<Calculate />
</Rules>
<Questions />
</Topic>
For the curious, the expression: $time:hour\:$time:minute\. is used to render something like 17:30. The expression has to be split up in 4 parts:
| time:hour | returns the hour |
| \: | we need to escape the : cause it is just after a variable path. If we don’t do this, the parser thinks that we will specify another function call. |
| time:minute | returns the minute part of the time value. |
| \. | The . also needs to be escaped, otherwise the parser thinks we are trying to further specify the path. |
Global patterns
In the ‘chatbot properties’ view (From the menu: View/chatbot properties), you can specify a bunch of global patterns like the opening statements, fallback values, ‘do-patterns’ that need to be executed at startup, just after each statement,… This also includes all the OS function-mappings that were declared in the ‘OS channel’. All these can be exported/imported using 1 file, with the extension ‘global.xml’. Import/export can be done from the menu: ‘file/import/global patterns’ or ‘file/export/global patterns’.
As you probably already guessed, this is also in xml format, but like with the topics, the content can be fully managed from within the designer, so there is no need to edit it manually.
Thesaurus files
Thesaurus files are used to share, exchange or replace part of, or the entire thesaurus that is used in a single project (thesauri are project specific and not shared across different projects within the designer). It’s the same routine again as for the previous file types: the extension is ‘thesaurus.xml’, yes, it’s an xml file format and can be fully managed from within the designer. In fact, for the thesaurus, it is more than advised to use the designer and not edit it manually since a thesaurus node can be hard to define as there are multiple levels at which words are stored. The only scenario where you might have to do some manual editing, is when you create a new thesaurus, completely from scratch when all the functionality needs to be preserved and you don’t have access to a network-designer version of the chatbot designer. In that case, the ‘bindings’ section on certain words (numbers, pronouns like I, me, myself,.. needs to be done manually. Here’s a small example of a binding for the object ‘myself’:
<Object Name="myself">
<Text Value="myself" Index="0" POSGRP="false" />
<Bindings>
<ref>I</ref>
<ref>Singular</ref>
</Bindings>
</Object>
Importing/exporting is done from the context menu on the thesaurus. If no node is selected, the entire thesaurus will be exported, or the content of the imported file will be added to the root. If a node is selected during an import operation, all items will be added as a child of the selected node. For export, only the selected item and all it’s children will be exported recursively.
It’s also possible to import or export just a single level of children of a selected node, in the form of comma-separated-value (csv) files. For imports, you don’t even need to get the data from a file, but you can just as easily use the clipboard: just copy a csv list to the clipboard, go to the context menu of the parent node and select ‘Import\wordlist from clipboard’. This feature allows you to quickly build up trees of related words.
Asset files
Assets store concrete information like ‘you are reading this now’ and ‘I have written this text’. By now, the theme should have become clear to you: asset files are stored in the xml format, they have the extension ‘asset.xml’. But here’s where we stray a little from the path: currently asset import/export is only supported in the pro version (that is, it’s still a little bit under construction, so it will soon be available). The same for editing: the pro will have an asset editor, but not the basic version. The latter only has the chatbot window for adding input statements.
Test cases
Test case files aren’t independent files like ‘topic thesaurus, asset or global patterns’ files, but are rather part of the project directory. As such, you can’t currently export or import them automatically from within the designer (though you can fully manage them as in copy, delete, edit,.. from within the designer). That said, they do deserve a special mention since it could sometimes be useful to extract some test-cases and copy them to other projects. And, because a test-case file is actually a stand-alone xml file, this is certainly possible.
Test case files are located at {project directory}\DesignerData\ and have the extension ‘TESTCASE’. If you copy a test-case from one project to another, or you want to delete some manually, make certain that the project is closed.
CCS files (characters)
As a final file format, I’d like to talk a little about characters and their files. A character is stored as an xml file (extension ccs) together with a bunch of images. These images can be located in a subdirectory of the ccs file, as long as the path can be described, relative to the ccs file.
For the chatbot designer to be able to use a character, the files (and possibly directory structure) of the char need to be copied to the directory: {My documents}\NND\Characters\CharName, where ‘Charname’ is the name of the character. Best to do this when the chatbot designer isn’t running.
Unfortunately, ccs files can’t yet be edited in the designer (or any other UI tool). They need to be created manually. Luckily, the structure is relatively simple and allows for lots of copy/paste. More info on the file structure will come shortly.