Discovering QiChat, the SBR language for creating chatbots

# Building a dialogue tree¶

The robot is now quite good at answering questions, but there are some times when he will have to be a bit more proactive, for example to ask for confirmation, and act accordingly. This way, the dialogue evolves from a simple Q&A (one question, one answer) to a decision tree: one question leads to another, and the path varies according to the user's choices. In order to do that, it's necessary to introduce the notion of rule priorities and scope.

## Priority and scope: how does it work?¶

User rules are active as long as the Chat action is running. The order in which rules are written has no incidence on matching, and any of them can be matched at any moment.

This is not the case for subrules: they are subordinated to a user rule (or another subrule), and are only matchable once their parent rule has been matched.

For example, here is the correct way to ask for confirmation:

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great!
u1:(no)
No problem!


In a topic containing this user rule and subrules, if you say "yes" out of the blue, nothing will happen. But if you say "I'm bored", the robot will suggest tongue twisters, and you will then be able to answer yes or no.

Subrules define a scope which opens when the parent rule has been said, and closes when one of the subrules matches. Subrules with the same number and the same parent rule are in the same level of scope, and it is possible to define cascading scopes:

u:(input1)
u1:(input2)
u1:(input3)
u2:(input4)
u3:(input6)
u2:(input5)


Here after "answer 1", you can either say "answer 2" or "answer 3", and after "answer 3" either "answer 4" or "answer 5". "Answer 6" can only match after "answer 4".

Note: the indentation is not compulsory, but it is a good practice to maintain as it underlines the relationship between a rule and its subrules.

Subrules have priority over normal rules, as they represent expected answers in this particular context. As they are only active when the parent rule has been matched, subrules can contain smaller inputs that could degrade the speech recognition if they were put in normal rules (such as "yes" and "no" for example).

User rules are still active when subrules are, so you can jump out of a scope by matching a user rule: this will close the subrules' scope.

So, let's see what happens when the user says "yes" to the tongue twister game!
Please note that the following examples follow an incremental progression to present new functionalities: they are all functional and testable, but you may prefer to implement only the last one for more efficiency.

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great! Repeat after me: I wish to wash my Irish wristwatch.
u2:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u2:(*)
That's not really it, sorry.
u1:(no)
No problem!


Here, if you say yes, the robot will suggest a tongue twister. Note that in a subrule, you can use the wildcard on its own to match any input but the ones matched in the other subrules of the same level. In this example, be careful, as you only have one try to get it right: if you can't match "I wish to wash my Irish wristwatch", the wildcard (*) will match instead, and the scope will close!

To be able to retry a few times, you can use the ^stayInScope function, that will keep the scope open:

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great! Repeat after me: I wish to wash my Irish wristwatch.
u2:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u2:(*)
That's not really it, sorry. Try again! ^stayInScope
u1:(no)
No problem!


But in that case, the user can get stuck in an infinite loop if he can't pronounce the tongue twister right. This is why when you use the ^stayInScope function, you should always be careful to plan a way for the user to say he wants to stop (such as "I give up"), and a maximal number of tries allowed, after which the robot ends the game himself.

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great! Repeat after me: I wish to wash my Irish wristwatch.
u2:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u2:(I give up)
Oh, too bad, you'll do better next time!
u2:(*)
["That's not really it. Try again! ^stayInScope" "That's still not right. Try again! ^stayInScope" "Well, you'll do better next time!"]
u1:(no)
No problem!


This way, the user can either give the right answer or say "I give up". If he says anything else, the wildcard will match, and thanks to the non-random list, the robot will allow him to retry twice, and then end the game.

## Proposal, bookmarks and "goto" function¶

This is fine when only one subrule has subrules of its own, but in some cases it can get really confusing: embedding too many subrules is not a good idea, as it can be difficult to maintain and lead to many errors if all paths are not checked thoroughly. It is best to stick to one level of subrules only, so as to keep all parent rules at the same level.

There are two types of parent rules: the user rules such as we have used since the beginning of this lesson, where the robot replies to a user input, and those where the robot asks a question proactively: the proposals.

A proposal is like a user rule without an input:

proposal:
Repeat after me: I wish to wash my Irish wristwatch.
u1:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u1:(I give up)
Oh, too bad, you'll do better next time!
u1:(*)
["That's not really it. Try again! ^stayInScope" "That's still not right. Try again! ^stayInScope" "Well, you'll do better next time!"]


So now instead of a block of embedded subrules, we have two clear parent rules and their respective subrules:

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great!
u1:(no)
No problem!

proposal:
Repeat after me: I wish to wash my Irish wristwatch.
u1:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u1:(I give up)
Oh, too bad, you'll do better next time!
u1:(*)
["That's not really it. Try again! ^stayInScope" "That's still not right. Try again! ^stayInScope" "Well, you'll do better next time!"]


But if we leave it as is, the robot will not say the tongue twister after the user confirmed. To do so, you have to link the confirmation rule and the proposal: add a bookmark to the latter, and use the ^enableThenGoto function to jump to this bookmark.

u:(I'm bored)
Shall we do some tongue twisters then?
u1:(yes)
Great! ^enableThenGoto(TONGUE_TWISTER)
u1:(no)
No problem!

proposal: %TONGUE_TWISTER
Repeat after me: I wish to wash my Irish wristwatch.
u1:(I wish to wash my Irish wristwatch)
Wow, you're doing great!
u1:(I give up)
Oh, too bad, you'll do better next time!
u1:(*)
["That's not really it. ^stayInScope" "That's not really it. ^stayInScope" "Well, you'll do better next time!"]


Functionally, this is strictly equivalent to the version we wrote earlier with u1 and u2 subrules, but it is far easier to read and maintain. Furthermore, using this structure enables you to avoid duplicating code in case you want to reuse the tongue twister, as a given proposal can be activated from different places in a dialogue, or even directly from the code.