Questions, answers and redirections

When a bot asks a question, it needs to know that a response is expected and, when an incorrect response is given, the bot should also know how to react. Furthermore, to correctly process an answer, memory and context often play an important role. Finally, the content of the questions that are asked (the topic) should also be controllable. In this post I’ll try to present a way to approach these issues using the chatbot designer.

The output rendering process

output rendering processBefore we delve into the actual process of asking questions and dealing with answers, lets take a closer look at the entire output rendering process. Understanding this is pretty important for setting up a proper chatbot system. To the right, you can see a screenshot of pseudo code that represents all the steps in the output rendering process as used at the time of writing. (The drawing was done with the code editor of the neural network designer. In other words, the real code is also debugged this way, it just looks a bit bigger).

The basic output rendering process is pretty straight forward. Once the entire input stage is done, the system first checks if there were any questions that needed answering. If so, and the parse-result of the input doesn’t contain a valid response for that question and if the question has a ‘invalid response’ section, then one of these invalid-response-outputs is selected.

After any previous question has been handled, we go through each pattern that was found in the input (aka the parse result). First, we execute any ‘calculations’ section that was attached to the rule that governs the pattern. Next, we check if the pattern was also used in the previous input(s), and if so, any possible repetition text is rendered. After this process, we determine the list of outputs that needs to be used. If the pattern responds to a question, that only this section of the output is used, otherwise the default section (that doesn’t have any ‘responses-for’ values) is used.  If this section has conditions (the Output when sections) and one of them  evaluates to ‘true’, it’s output list is used, otherwise, the default list (which has no condition) is selected. Finally, when all result-patterns have been processed and there was absolutely no output generated by any of the patterns, one of the fallback-values is generated, or if there aren’t any, the default text ‘no output defined’ will be rendered. And that’s it.

Setup

To explain the question-answering system, lets use a common chatbot topic ‘like’ and make a system that can do a little more then merely store information and answer questions as was explained in the previous post.

So, lets start out with a simple rule:

Input:
   ^s:pronoun like $var

Calculate:
   #bot.mem.who.val = $s:ResolvePerson;  //get the asset for $S 
   var iInverted = $s:InvertPerson;   //so we only need to calc 1 time
   #bot.mem.who.inv =  iInverted;     //store in long-term mem
   $inv = iInverted;                  //store in short-term-mem for fast access
   #bot.mem.who.val.like.($var) = ‘true’; //store that $S likes $var

Output:
   Ok, I see, $inv like $var.

For this demo (included with the installer), I’ve only put 2 values in the ‘pronouns’ list under ‘subject’: I and you (and hooked them up with the ‘Personal mappings’ context menu item). This way ^s:pronoun can only be: I or you. So possible valid inputs for these 2 patterns could be: I like chocolate, you like French fries,…  And the bot would respond with something like: Ok, I see, you like chocolate.  The ‘calculate section is the longest. It calculates the ‘asset’ value for $s (for future references), it’s inverted text (I <-> you and such) so that this only has to be calculated 1 time and in the last line it stores the fact that $S likes $var.

By the way, this tutorial is based on the {my documents}\NND\demos\QuestionAnswer.dpl project (all the individual source files for the project can be found under ‘NND\Topics\QuestionAnswer\’). If you plan to build the project yourself, don’t forget to turn off ‘use the $output var’ in the chatbot properties view. This is to make certain that all parts (answer and question) are properly joined by the chatbot. If you turn this parameter ‘on’, you will have to declare an ‘$output’ in all of your response/questions. Otherwise you can loose part of the total result statement (but you have greater control over the output format).

Asking questions

So, now that we have a starting point, lets extend it a little and make the bot ask a follow up question. The easiest way to do this, is by simply adding an extra question at the end of the output part like so:

Input:
   ^s:pronoun like $var

Calculations:
   ...

Output:
   Ok, I see, $inv $var. Is there anything else $inv like?

This works and is very easy to do, but it’s a little bit limiting: any conditionals you want to use for asking a question needs to be declared in the output section or in the code, RepliesQuestionFiltersTabwhich can make the entire rule seem bloated.  Also, if you would want to ask the same question for different outputs, you’d need to duplicate a lot of stuff. It’s far better to separate the questions from the outputs in your topics. To this end, each topic editor contains multiple pages: a Replies a Questions page (also a topic-filters page, but that’s for later). By default, you always get to see the ‘Replies’ page where you declare the rules with the ‘Input’ and ‘Output’  sections. The second page (labeled Questions) is used to declare all the questions that can be asked for the topic. It looks and behaves similar to a single ‘Output’ section of a rule. The topic filters are used for AIML-style topic filtering (more on that later).

Tip: Ctrl+! is the shortcut code for the Replies page
Ctrl+? gets you to the questions page
Ctrl+~ jumps to the topic-filters page.

So, going back to our example, a better declaration would move the ‘Is there anything else you like?’ to the questions page so that it can be shared with every rule in the topic. This would look something like:

Replies:

   Input: 
      ^s:pronoun like $var

   Calculation:
      ...  

   Output:
      Ok, I see, $inv like $var.

Questions:
   Is there anything else $inv like?

Because the ‘questions’ page has it’s own ‘when’ section, you can declare conditionals and make the questions a little more varied and relevant. For instance, if a user says, ‘I like chocolate’, the system could check if it knows why the user likes chocolate, what chocolate is, if the bot likes chocolate,… Here’s an example of a questions section (the demo included with the installer has other ‘when’ examples):

//defined on the ‘Replies’ page in the rule: variable values remain valid in the questions.
Calculations:         
   //use s simple var instead of #bot.mem… for simplicity
   $Inverted = $s:InvertPerson; 
   $who = $s:ResolvePerson;

Questions:

  //if don’t know that other persons likes $var and not in same question
  when: count(#($Inverted:ResolvePerson).like.($var)) == 0 && $topics !contains ~LikeQuestion   
    Do $s like $var?

  when: #($who).like.($var) == ‘yes’ && count(#($who).like.($var).why) == 0
     Why do $Inverted like $var?

  when: #($who).like.($var) == ‘no’ && count(#($who).like.($var).why) == 0
     Why don’t $Inverted like $var?

  else
 
   Is there anything else $Inverted like?

This example first checks if it is known whether the other person likes $var’ or not and if the user isn’t asking for this data so that it can ask the inverted question. In other words, if the user says ‘I like x’, the bot will check if ‘bot likes x’ is not known and if the user didn’t ask: ‘do you like x’, so that it can ask itself: ‘Do I like x?’. This is a bit of a weird example, I know, we normally don’t go around asking people if we ourselves like something. The main purpose for including this was to show the use of ‘$topics’ which is a system variable that contains all the topics that were found in the current input statement (the full statement, not just the part being processed right now). The statement  $topics !contains ~LikeQuestion checks if the ‘LikeQuestion’ topic was not found in the current input.   Other system variables that provide  information about the input are:

$Topics gets/sets all the topics found in the current input.
$AllTopics Lists all the topics found in the entire conversation.
$NextTopics Gets/sets the list of topics that the next input should be restricted too
$Rules Lists all the rules found in the current input.

The next 2 ‘when’ parts of the example are used to print statements like ‘Why do you like chocolate?’ or ‘Why don’t you like chocolate’. From the previous tutorial, we used a ‘yes’ or ‘no’ to indicate if something is liked or not, so these 2 ‘when’ sections are used to make certain that the question is formatted correctly to ask the ‘why’ statement. With the statement count(#($s:ResolvePerson).like.($var).why) == 0, we check if there is any ‘why’ data available. #($s:ResolvePerson) converts the content of variable ‘s’ to an asset and makes certain that we are using asset-paths instead of a regular variable path so that .like.($var) is correctly handled.
And finally, there is a fallback question that doesn’t have any condition. So this gets executed if none of the other conditions evaluated to true.

As was mentioned earlier, you don’t have to use the ‘questions’ page. You can simply put the question in the output section of the rule. This also means that you can mix the 2 declaration techniques: you can put questions in the output parts and in the questions page at the same time in a single topic. To make Capture2certain though that the system doesn’t ask 2 questions after each other, it’s best to tag the question in the output so that the system knows it doesn’t need to render another one. This can be done visually in the editor with a toggle button at the end of the output line, as shown in the image to the right. The example, by the way, renders a question like ‘I thought you didn’t like chocolate’ when the user said ‘I like chocolate’ while in memory, we have already stored that he/she doesn’t.

Processing answers

Once you’ve asked your question, it’s time to wait for an answer. Many systems declare possible answers for a question as a list of children for that question. The chatbot designer reverses this paradigm (like AIML). Instead, we let the rule that traps the answer know that it can function as the answer to a question. Why is this done? Well, this way, there is less need to create duplicate patterns: one for the answer to a question and another one in case that the rule got triggered without the presence of a previous question. In other words, the system is set up to prevent as much duplication as possible. That doesn’t mean it isn’t possible to view the statements, questions and the answers as a list/tree view, but at the time of writing, this hasn’t been added yet to the designer (if you have a need for this, let me know and I’ll see what I can do).

responsesForSectionNewResponse for

So, how do you let a rule know that it can be the answer for a question? Well, this is done in the output part: you can divide the output list in ‘Responses-For’ sections: every section contains all the outputs that can be used as the answer to 1 or more questions which you assign to that section.  So you really have to look at it in the reverse order: don’t declare the answers to a question, but the questions for an answer. The screenshot to the left shows how to declare these questions. Every output section has an empty line at the top. When you click in this region, either a drop down box or a text-box become active. To switch between dropdown- and text-box, you can use the ResponseForAsPattern  button on the topic’s local toolbar.

With the drop down box, you can select a previously defined output pattern (either in the output part or the questions page). The text-box on the other hand allows you to declare a new input-pattern which will be used to match against the previous output.  In other words, when you use the textbox, you declare an input pattern that can potentially match a whole set of questions like AIML’s ‘That’ element, while the drop-down box will always match exactly 1 possible output. Both methods have their advantages and disadvantages. References to outputs (so the dropdown-box approach) are much faster for the engine to evaluate (almost no cost), but they are very restrictive and require you to declare every possible question for which it is an answer. The input-pattern approach (AIML’s ‘that’ element) though is slower as it requires an entire new pattern-match but it offers a lot more flexibility as you can use all of the input pattern’s features including sub-patterns and thesaurus paths.

responsesForSectionWorking with the drop-down box is pretty straight forward. Hover the mouse over the text to make the dropdown-button visible. The dropdown-box itself contains all the output of the current topic, another page with all the outputs of every topic in the project (minus the ones attached to thesaurus items) and a final tab with all the outputs found in the topics that are attached to thesaurus items (more on those later). So every output statement, except the ones declared in the global ‘chatbot properties’ view, are available from this dropdown-box.

To remove an item from the list, use the little red x in the top left corner of each drop down box. This will remove the line. When it’s an input-pattern, you can simply delete the text and press ‘backspace’ one more time to delete the line itself.

Also, you can quickly jump to the definition of the output pattern with the local context menu: right click on the dropdown-button and select ‘Go to output pattern’. Obviously, this doesn’t work on the text-boxes since there is no actual output pattern being referenced. You can also list all the other responses for the selected output pattern from the context menu with ‘Find all references’. This will open up the search-page and display all the results, grouped per topic. On the context menu of an output line, you can also use the context menu to quickly find all the responses already declared for the question with the menu item ‘Find references’.

Topic filters & Partials

Some input statements can be used as the answer for many different types of questions, like ‘yes’ and ‘no’. You could build unique rules and topics for them so that there are never duplicate pattern definitions in the bot, but this would generally require for each rule to have a huge ‘Output-when’ list so it can provide a custom response for each possible situation (the responses could potentially also be build with a lot of redirections (see later), but that produces a similar problem). It is often better to place these types of statements in the same topic as the question. This means: duplicate patterns.

In order to support duplicate patterns so that the system automatically tries to select the most appropriate rule, the chatbot engine makes use of the topic context in multiple ways: First, if the newly found rule(s) belong to (a) topic(s) that has ‘topic filters’, then these are evaluated first. Each topic can have a list of input patterns (declared in the last tab on the topic editor) which is evaluated against the content of the system variable ‘$Topic’. This works the same as ‘Responses-For’ input patterns but instead of evaluating against the previous output, the content of $Topic is used which should always be a string value or empty. If there are still multiple remaining rules left (those that didn’t have a topic filter or that produced a match), then a second, simpler filter is used: only select the rules that are located in the same topic(s) as were found in /assigned to the previous input statement. The topics of the previous input are automatically calculated, but you have the ability to modify this or add new topics to the result. This can be done through the ‘$topics’ variable (to add new topics, use the += operator).  Finally, if there were still multiple results left or none of the rules came from one of the previous topics, then a ‘fallback  partial rule’ will be selected if there was one present in the result set, otherwise 1 rule will be selected at random.


Calculate:
   $topics += ~Pets;   //add topic ‘pets’ to list of topics for current input
   $topic = L(I like pets);  //assign sentence ‘I like pets’ to be filtered by topic filters.

As with the ‘responses-for’ section, the ‘topic filters’, which use input patterns and therefor require a new pattren-matching process, are much slower then a comparison to the list of topics assigned to previous input. But the topic filters provide more flexibility. They also allow for an easier way to jump between topics without having to know the actual topic to jump too (and finally, they are required for AIML support). All in all though, I favor the comparison against the topics of the previous input since it is faster and requires less work.

Since there are multiple ways to deal with duplicate input patterns, the system allows you to specify how the compiler should handle them: generate an error (and not allow the compilation), produce a warning, but compile or simply treat them as any other patterns. This allows you to chase down all those unwanted duplicate patterns. But even when the system is set to generate errors for duplicate patterns, it can still be useful to allow some duplicates here and there without the system complaining about them. To this end, partial patterns were introduced. These patterns can have duplicates, as long as each duplicate is in it’s own topic and no topic has 2 similar patterns. Furthermore, a set of partials can have 1 fallback pattern that gets activated if non of the other duplicates was valid.

capture 2Capture3To toggle between the different states, you can use the toggle-button in front of each pattern, as shown in the images to the left and right. A half paper with the ripped-of part to the bottom is a regular partial. If the ripped part is pointing upwards, like for the ‘no’ reply to the left, it means that it’s a fallback value, which will be activated if no other ‘no’ produced a valid match. Usually, you put a response here something like ‘No what?’.

As a side node, these partials can be used to create topic-local, general purpose, fallback values which will override the global fallbacks that get declared in the ‘project properties’ view. This is done by using a single variable as pattern, like in the bottom example to the left ($var). The pattern will simply consume the entire input because of the variable and if there is no other pattern that matches a single word, this gets activated. By making the pattern a partial, you can always have a fallback in each topic.  For instance, if the conversation was about ‘cars’, but the user says something unknown, the ‘cars’ topic could try and pick up the conversation with something like ‘I don’t know about that, does it have anything to do with cars?’

Memory

We humans tend to be a bit lazy in our answers, so we leave out as much information as we possibly can. When processing the answers, we need to be prepared for this. In other words, the bot needs to have some sort of memory. Now, this topic has already been covered, so I’m not going to dig into every little detail again. The basic principle is always the same: we use assets to store data in the long-term memory.  Key here is to consistently use the same memory structure everywhere. In the previous tutorials, we already saw how best to divide the memory into a Mem and PrevMem section, so that we can remember details about past statements. Now, when asking questions, we could store the information into the same structures, but it’s usually better to have a separate  memory section for the questions, so that we can see the difference between statement and question data. In concrete, NextQuestion is used to store info about the question that has just been rendered and should be responded to by the next input. Question defines the data for the question that should be answered with the current input statement. Here’s an example that builds on where we left off with our ‘like’ topic:

Calculations: 
//use s simple var instead of #bot.mem… for simplicity
$Inverted = $s:InvertPerson;
$who = $s:ResolvePerson;
$InvWho = $Inverted:ResolvePerson;

Questions:

  when: count(#($Inverted:ResolvePerson).like.($var)) == 0 && $topics !contains ~LikeQuestion
     Do $s like $var?

     do:
        #bot.NextQuestion.who.Val = $InvWho;
        #bot.NextQuestion.value = $var;

  when: #($who).like.($var) == ‘yes’ && count(#($who).like.($var).why) == 0
     Why do $Inverted like $var?

     do:
        #bot.NextQuestion.who.val = $Who;
        #bot.NextQuestion.value = $var;

  when: #($who).like.($var) == ‘no’ && count(#($who).like.($var).why) == 0
     Why don’t $Inverted like $var?

     do:
        #bot.NextQuestion.who.val = $Who;
        #bot.NextQuestion.value = $var;

  else
     Is there anything else $Inverted like?

     do:
        #bot.NextQuestion.who.val = $who;

Next, in the global chatbot’s properties view, we need to declare the lines that move the next question data to the current (or previous) data (depending on how you look at it) after the statement has been processed. If we don’t do this, and the next output doesn’t overwrite this memory location, the system will think that the question is still valid, and it will use old data. That’s why things need to be ‘forgotten’, like so:

#bot.prevMem = #bot.mem;
#bot –= ‘mem’;                  //important to put between ‘’ so that dictionary is used
var iNextQuestion = #bot.nextQuestion;
if(count(iNextQuestion) > 0)    //only do if there is a next question waiting.
{
  #bot.question = iNextQuestion;
  #bot –= ‘NextQuestion’;
}

And finally, the answer pattern can use this data to fill in any missing data/do validity checks. The next example will handle the answer to ‘Do I like xx?’ or ‘Do you like xx?’

Input:

   yes [[,] ^s:pronoun.subject do]

Output when: #bot.Question.who.val == $s:ResolvePerson && count(#bot.Question.value) > 0
   Ok, so $s:InvertPerson also like #bot.Question.value\.

   do:
      #bot.Question.who.like.(#bot.Question.value) = ‘yes’;
      #bot.mem.who = #bot.Question.who;   //copy entire who –> val + inv
      #bot.mem.value = #bot.Question.value;

In the ‘when’ section, we check if the ‘who’ of the question equals the ‘$s’ value to make certain that we don’t act incorrectly when the user replied with ‘yes I do’ if the question was ‘Do I like xx?’ (in other words, check if the subjects in both sentences match). Attend readers might note that this isn’t really complete: what about if the user simply said ‘yes’? Well, this situation isn’t handle in the lines above, for simplicity reasons. Check out the demo for some inspiration to solve this.
Storing the actual data is done with a regular asset assignment:  #bot.Question.who.like.(#bot.Question.value) = ‘yes’. The other 2 statements are for doing the housekeeping so that we retain all the information about the statement: who, what, value, when, where,… This way, the user can continue leaving out parts of information that we can fill-in again.

Invalid responses

In general, you don’t want to be too restrictive in the conversation flow: when a user doesn’t answer a question, don’t sweat it and simply move on. Sometimes however, you really need the answer before you can do something else. If you find yourself in need of such a scenario, you can use the ‘Invalid responses’  section that’s found in the extended area of every output statement. This is jus a list of output statements that gets activated if the question didn’t get answered by one of the patterns that contains a correct ‘Response for’ reference.

When a question has no invalid-responses defined, any other pattern can come after it, otherwise, only the referenced patterns. If no correct result is found, an invalid response is generated just before the output that came from the current input. This will only happen once though.

 

Redirections

Thanks to thesaurus variables, sub rules/topics and other features, it becomes possible to define general purpose patterns that are able to capture lots of different input forms. This is great for reducing the number of patterns that need to be managed, but poses a problem in the area of response generation and answer handling: the more general your patterns are, the bigger the ‘bot-says-when’ list or code section becomes for handling content specific responses.

So, in order to provide better content related output while using general purpose patterns, the concept of redirections was introduced. The basic idea comes down to redirecting the output rendering from one rule to another rule (or a topic’s questions page). In concrete, this is done by attaching topics to thesaurus items and assets. These then can be activated with do-patterns from within other rules. This is probably best explained with an example, so lets go back to the ‘like’ pattern and let it generate a question specific to the ‘xxx’ value that is liked (or not), if it is available.

Questions:

When: ~GetTopic(^noun.($var)).Like:Exists
   ~GetTopic(^noun.($var)).Like:Exists:Render

This condition checks if there is a topic attached to a noun with the same label as can be found in ‘$var’ and if that topic contains a rule called ‘Like’ (case insensitive). You might be wondering why not just do ‘~GetTopic($var).like’? Well, because in this instance, we are certain that $var doesn’t contain a thesaurus item, but a regular word and, as already mentioned, it’s the thesaurus items that have an attached topic, so we need to go from text to thesaurus item, more specific, we want a noun, which is done like so: ^noun.($var).

‘GetTopic’ returns any topic that is attached to the function input. When the input can be converted to a thesaurus item, it will go up the thesaurus tree until it finds an item that has a topic. This means that even if the actual word that was said doesn’t have any topic, a redirection can still happen if there is a parent in the thesaurus tree which does. So if the user mentioned ‘Ford’ and there is a topic for ‘car’ but not for any specific brand, the ‘car’ topic would be used.

CaptureIn case you are wondering how you can attach topics to thesaurus items: this is real easy, go to the item, right-click with the mouse to open the context menu and select ‘View patterns’. This will open up a topic editor. When you create the first rule in this topic, the thesaurus item will get a topic icon in front, like in the image to the right. For assets it’s even easier: every asset editor in the project tab has a topic editor as child node which you can open as any other regular editor. Note that this child node will only be available if the asset has data, empty assets can’t have any. Deleting attached topics happens much in the same way: for the thesaurus, open the context menu and select ‘Delete patterns’. Topic editors that are children of assets can be deleted as any other editor (note that asset editors still need some work at the time of writing)

In the example above, we are searching for a rule called ‘Like’ in the topic attached to a thesaurus item. This is a convention: you can pick any name you want, it’s just a convenience that the name of the topic doing the redirecting is the same as the name of the rule we are looking for. This way, the topic attached to a thesaurus item can have a series of rules who’s names match the topics for which they provide custom responses.

The ‘:Exists’ function is used to determine if the rule was found or not. We could also have done count(~(^noun.($var)).Like) > 0, which does the same, but using ‘exists’ is shorter and perhaps a little easier to read.

‘Render’ is the workhorse: this will generate the actual output for the item that you want. In the example above, it will generate the output of the ‘Like’ rule. If it has any conditions, they will be evaluated first. Once the rule has been processed, a topic question will also be rendered as is done for regular output. ‘Render’ can also work on a topic directly: ~GetTopic(^noun.($var)):Render . In this case, only a question of the topic will be produced.

Here’s another example of how these redirections can be used:

Input:
   ~subject ^v:Verb.has ~object

Output when: ~GetTopic(#bot.mem.who).(#bot.mem.value):exists
   ~GetTopic(#bot.mem.who).(#bot.mem.value):Render

Output when: ~GetTopic(#bot.mem.value).have:Exists
   ~GetTopic(#bot.mem.value).have:Render

In this example, we have a very common pattern that uses 2 sub topics ‘Subject’ and ‘Object’.  If these sub-topics are declared properly, the pattern should be able to capture many different forms of ‘x has y’ inputs. In the output, we first check if ‘x’ (who) has a specific rule for ‘y’ (value). This can be useful if you want to give a response specific to a person for certain things. In the second conditional, we check if the value has any specific rules for ‘have’.

SRAI

For those who are familiar with AIML, the ‘srai’ element should be very familiar. Basically, this function is able to send some text back into the parser and returns the output that was generated for it. This is another type of redirection that is available to you. Like with ‘Responses-for’ and ‘topic-filters’, the $SRAI function performs a new pattern-match, which is a lot slower compared to ‘GetTopic’ and ‘Render’. But $SRAI can be more flexible.  Here’s small example:

Output: $SRAI(L(I like pets));

Well, I hope your imagination has been sufficiently lit and new ideas are coming to you right now. All that’s left for me to say is: Enjoy!

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>