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
Before 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 declared this way, it just looks a bit bigger).
The basic output rendering process is pretty straight forward. Once the entire input has been processed, we go through each pattern that was found. 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 one of the conditions (the bot says when section) evaluates to ‘true’, it’s output list is used, otherwise, the default list (which has no condition) is selected. Before we render an output from this list, we check if the system is expecting an answer to a question, and if so, whether the selected list actually contains any valid responses for that question. If this is the case, or if the question didn’t have any ‘invalid response’ section, one of the output patterns in the list is generated after the ‘do-section’ of the condition is executed. If however, there was no valid response to be found in the list and the previous question does have an ‘invalid response’ section, one of these invalid-response-outputs is selected and no ‘do-patterns’ are executed. 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 again 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 posts. This time round, we’re going to let it be more engaging with the user by making the bot asking questions.
So, lets start out with a simple rule:
You say:
^s:pronoun like $var [,|.]Bot says:
Ok, I see, ^s:InvertPerson like $var.
Pretty simple. For this example, I’ve only put 2 values in the ‘pronouns’ list: I and you (and hooked them up with the ‘Personal mappings’ context menu item). This way ^s:pronoun can only be: I or you (duh). 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.
By the way, this tutorial is based on the {my documents}\NND\demos\QuestionAnswer.dpl project. 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 ‘off’, 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 to simply add an extra question to the output part like so:
You say:
^s:pronoun like $var [,|.]Bot says:
Ok, I see, ^s:InvertPerson like $var. Is there anything else s:InvertPerson like?
This works, but it’s a bit limiting: any conditionals you want to use for asking a question need to be declared in the output section. This can quickly result in a complete wilderness of conditionals where checks for the output are mixed with verifications for rendering the
questions. Also, if you would want to ask the same question for different outputs, you’d need to duplicate a lot of stuff. Far better to separate the questions from the outputs in each topic. To this end, each topic editor contains 2 pages: a Replies and Questions page. By default, you always get to see the ‘Replies’ page where you declare the rules with the ‘You say’ and ‘Bot says’ 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 ‘Bot says’ section of a rule.
Tip: Ctrl+! is the shortcut code for the Replies page
Ctrl+? gets you to the questions 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
You say:
^s:pronoun like $var [,|.]Bot says:
Ok, I see, ^s:InvertPerson like $var.
Questions:
Is there anything else s:InvertPerson 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:
Calculations: //defined on the ‘Replies’ page in the rule: variable values remain valid in the questions.
$Inverted = $s:InvertPerson
$who = $s:ResolvePerson
Questions:
when: !(#($Inverted:ResolvePerson).like.($var)) && !($topics contains ~LikeQuestion)
Do $s like $var?
when: !#($who).like.($var):why && #($who).like.($var) == yes
Why do $Inverted like $var?
when: !#($who).like.($var):why && #($who).like.($var) == no
Why don’t $Inverted like $var?
else
Is there anything else $Inverted like?
This example first checks if the ‘like $var’ value is known for the inverted person 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. It could also have been written like ($topics !contains ~LikeQuestion), which is actually a little faster. Other system variables that provide information about the input are:
| $Topics | Lists all the topics found in the current input. |
| $AllTopics | Lists all the topics found in the entire conversation. |
| $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 !#($s:ResolvePerson).like.($var):why, we check if there is any ‘why’ data available (the section gets activated if there isn’t any because of the ! in front of the path).
And finally, there is a fallback question that doesn’t have any condition. So this gets executed if non 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
certain 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. 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 no 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.
Response 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 section: each output can have a list of other outputs for which it is a possible answer. 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 the questions for which an output can be an answer. Every output line has a ‘V’ in front of it. When you click on this button, the output line will expand and a ‘Response for’ list becomes visible. This contains a drop down box for each selected output (+ an extra one to add new items). 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 you delete the question itself, the reference in the ‘Response for’ list will also be removed automatically.
Also, you can quickly jump to the definition of the question with the local context menu: right click on the dropdown-button and select ‘Go to output pattern’. You can also list all the other responses for the selected question 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’.
Partials
Some answers to questions are pretty common, like ‘yes’ and ‘no’. You could put these statements in their own topics, but this would generally require for each answer to have a huge ‘bot-says-when’ list so it can provide a custom response for each possible situation. Far better to have a topic-local response for such statements. 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. To determine which duplicate to activate, the topic context is used: the duplicate in the topic that was last used, gets activated. As already mentioned, if non is found, the fallback duplicate is used.
![]()
To 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 ‘chatbot 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 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 Current and Previous 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. Here’s an example that builds on where we left off with our ‘like’ topic:
Calculations:
$Inverted = $s:InvertPerson
$who = $s:ResolvePerson
$InvWho = $Inverted:ResolvePerson
Questions:
when: !(#($InvWho).like.($var)) && !($topics contains ~LikeQuestion)
Do $s like $var?
do:
#bot.NextQuestion.who = $InvWho
#bot.NextQuestion.value = $var
when: !#($who).like.($var):why && #($who).like.($var) == yes
Why do $Inverted like $var?
do:
#bot.NextQuestion.who = $Who
#bot.NextQuestion.value = $var
when: !#($who).like.($var):why && #($who).like.($var) == no
Why don’t $Inverted like $var?
do:
#bot.NextQuestion.who = $Who
#bot.NextQuestion.value = $var
else
Is there anything else $Inverted like?
do:
#bot.NextQuestion.who = $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
#bot.question = #bot.nextQuestion
#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?’
You say:
yes [[,] ^s:pronoun.subject do]
Bot says when: #bot.Question.who == $s:ResolvePerson && #bot.Question.value
Ok, so $s:InvertPerson also like #bot.Question.value\.
do:
#bot.Question.who.like.(#bot.Question.value) = yes
#bot.mem.who = #bot.Question.who
#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 follow it, otherwise, only the registered results. If no correct result is found, an invalid response is generated. However, the system will only render invalid responses when it has processed all the patterns that were found in the current input and no answer was found. This is to make certain that the invalid response section doesn’t get rendered to many times. Suppose there is 1 question waiting for an answer when the user activates 2 new patterns, but none can be used as a valid response. In this case, the system should only render the invalid response 1 time: at the end instead of for each question.
Redirections
Thanks to thesaurus variables, sub rules/topics and other features, it becomes relatively easy 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 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: ^noun.($var):GetTopic:GetRule(Like)
^noun.($var):GetTopic:GetRule(Like):Render(true)
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 ‘$var:GetTopic:GetRule’? 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, which is done like so: ^noun.($var).
‘GetTopic’ returns any topic that is attached to the input that is provided to the function. When the input is 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. That’s why it’s always best to use thesaurus items for the redirections. Theoretically, they can also work on regular words (or any other type of data), but without the search features of thesaurus items.
In 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)
‘GetRule’ requires a topic as input and always expects an argument: the name of the rule that it should search for. So 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.
A small warning about the argument should be mentioned: at the time of writing, thesaurus-items are not yet allowed, only regular text (or variables that reference regular text) should be used.
‘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: ^noun.($var):GetTopic:Render . In this case, only a question of the topic will be produced. Also, the ‘Render’ function can have 1 optional argument: a bool that indicates if the do-patterns of the requested rule or topic, also need to be executed (or not). By default, this is true (so the argument in the example was strictly speaking, not needed), but if you don’t want the do-patterns to execute, you’ll need to pass a ‘false’ as argument value.
Here’s another example of how these redirections can be used:
You say:
~subject (has|have|’ve) ~object [.]
bot says when: #bot.mem.who:GetTopic:GetRule(#bot.mem.value)
#bot.mem.who:GetTopic:GetRule(#bot.mem.value):Render
bot says when: #bot.mem.value:GetTopic:GetRule(have)
#bot.mem.value:GetTopic:GetRule(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’.
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!