Run a sub-tree until a specific phrase is heard


#1

I have a skill which needs to run a subtree until either the subtree completes or until a specific phrase is heard from a rule file. I figured this would be as simple as adding a SucceedOnListen to that subtree, but unfortunately it tends to always succeed when Jibo hears anything, not just the phrase in my rule file. So I found another solution which works fine, but I would like to see if there’s a better way.

First, here’s the most simple working solution I found for this issue:

Basically, I use a Parallel to have the subtree run at the same time as a Listen behavior. I set the succeedOnOne parameter to true to make it succeed if the subtree finishes (succeeds) or if the Listen behavior succeeds.

Subtree behavior
The subtree has a SucceedOnEvent decorator with an EventName argument of “stopListening” so that it will listen for that event and succeed (jumping out of the Parallel) when that event is heard.

Listen behavior
The listen behavior has a WhileCondition decorator so that it runs more than just once, if needed. It has heyJibo and incremental set to false and detectEnd set to true. The code that executes when something is heard is this:

(listener) => {
    listener.on('cloud', function(asrResult, speakerIds) {
      if(typeof asrResult.NLParse.phraseHeard !== "undefined"){
        emitter.emit('stopListening');
      }
    });
}

This simply emits “stopListening” when the phrase from my rule file is heard (via the phraseHeard passthru) and succeeds the subtree.

Then in the WhileCondition conditional, this code will break out of the while loop when the emitter emits:

() => {
    if(!emitter._eventsCount){ return false; }
    return true;
}

So this setup works fine, but I was hoping there was a more elegant solution available, maybe a single decorator or set of decorators that can be added to the subtree to achieve the same effect. Is this the only way to do this?

EDIT: Added WhileCondition conditional code above and succeedOnOne reference. These are required for the setup to work.


#2

Hey @michael!

In your Subtree, try nesting your code in two Sequence's: the first with the WhileCondition decorator and the second with the SucceedOnListen decorator.

The SucceedOnListen decorator will force the Sequence and leaf behaviors to finish with status SUCCESS when it hears something, but the WhileCondition decorator will cause the subtree to loop until it parses properly. When nested, Sequence*SucceedOnListen will then go back into IN_PROGRESS when it loops back around, but when they are decorating the same Sequence, it still remains with status SUCCESS.

Downside is it will break out of the sequence and restart it. This may be helpful depending on the context. I’m going to keep doing some digging to see if I can find a better method than what you currently have implemented.

–Example–
WhileCondition > conditional:

() => {
    return notepad.what !== "what";
}

SucceedOnListen > onResult:

(listener) => {
    listener.on('cloud', function(asrResult, speakerIds) {
      if (asrResult && asrResult.NLParse) {
        notepad.what = asrResult.NLParse.what;
        console.log("notepad.what is set to " + notepad.what);
      };
    });
}

Rules File:

TopRule = $* $what {what='what'}$*;

what = (what);

Attached files for this example:
main.bt (1.0 KB)

subtree.bt (7.5 KB)

helloworld.rule (52 Bytes)

Hope this helps!


#3

Possible Solution #2:

In your Subtree, swap the top Sequence for a Parallel parent behavior with the SucceedOnCondition decorator. This will force the whole subtree to succeed if it meets a certain condition. Use conditional arguments to force the whole Parallel to succeed if it meets conditions that it parsed properly or if the Sequence finishes.

Have your code in a Sequence nested in the Parallel behavior along with the Listen behavior with WhileCondition decorator so it loops until it parses properly.

I’ll talk with the team tomorrow and see if there is a more elegant solution.

Updated subtree.bt for this example. Use the previous main.bt and helloworld.rule files:
subtree.bt (6.8 KB)


#4

@michael
Variation of the above option, but set up similar to your skill. Instead of declaring some variable in an ExecuteScript behavior in the Sequence, you can just declare the variable in the treeResult behavior argument of your Subtree and then use this in the SucceedOnCondition decorator.

You do want to keep the WhileCondition on the Listen behavior to make sure that it continues listening until it hears the right phrase.

subtree.bt (3.1 KB)

subtree0.bt (3.9 KB)


#5

Thanks, Joe! I’ll take some time today to play with the different options you presented to see how each works. I’ll post my results here once I’m finished. Thanks for taking time to put these together.


#6

I have tested all of the options we laid out here to find out which was the most understandable and easiest to implement. Narrowing down everything, we have 3 distinct versions, all which work well but use different methods to achieve the same outcome…I’ll outline each below with necessary code but without any additional explanation since they were described in our posts above.

###OPTION 1: Single Parallel
Here’s the setup from the top down:

Parallel
Set succeedOnOne to true.

Subtree
Use any subtree. Attach a SucceedOnEvent decorator with the eventName “stopListening”.

Listen
Use a .rule file which returns a variable “phraseHeard” with any value. Use the following code in the onResult argument:

(listener) => {
    listener.on('cloud', function(asrResult, speakerIds) {
      if(typeof asrResult.NLParse.phraseHeard !== "undefined"){
        emitter.emit('stopListening');
      }
    });
}

Attach a WhileCondition decorator with the following code in the conditional argument:

() => {
    if(!emitter._eventsCount){ return false; }
    return true;
}

###OPTION 2: Stacked Sequences
Here’s the setup from the top down:

Sequence (first)
Attach a WhileCondition decorator with the following code in the init argument:

() => {
  notepad.playSubtree = true;
}

…and the following code in the conditional argument:

() => {
    return notepad.playSubtree;
}

Sequence (second)
Attach a SucceedOnListen decorator. Use a .rule file which returns a variable “phraseHeard” with any value. Use the following code in the onResult argument:

(listener) => {
    listener.on('cloud', function(asrResult, speakerIds) {
      if(typeof asrResult.NLParse.phraseHeard !== "undefined"){
        notepad.playSubtree = false;
      }
    });
}

Subtree
Use any subtree. Use the following code in the onResult argument:

(treeResult) => {
  notepad.playSubtree = false;
}

###OPTION 3: Parallel w/Sequence
Here’s the setup from the top down:

Parallel
Attach a SucceedOnCondition decorator with the following code in the init argument:

() => {
  notepad.playSubtree = true;
}

…and the following code in the conditional argument:

() => {
    return notepad.playSubtree;
}

Sequence
No decorators…just a plain old sequence.

Subtree
Use any subtree. Use the following code in the onResult argument:

(treeResult) => {
  notepad.playSubtree = false;
}

Listen
Attach a WhileCondition decorator. Use a .rule file which returns a variable “phraseHeard” with any value. Use the following code in the onResult argument:

(listener) => {
    listener.on('cloud', function(asrResult, speakerIds) {
      if(typeof asrResult.NLParse.phraseHeard !== "undefined"){
        notepad.playSubtree = false;
      }
    });
}

Analysis of the above options

I think the first two options above are the most straight-forward, but when it comes to ease of implementation, I would go with the second option (“Stacked Sequences”). It only requires one notepad variable (notepad.playSubtree) to track movement through the subtree/listen process and can be easily modified to track whether the subtree playback was cut short or not if needed.

Thanks @joe.t for your suggestions! I’ve already updated my skills to use what you recommended. If you ever run into an even simpler version of this setup, please update this thread.


#7

Thank you @michael for taking the time to put this together in a very clear and readable format. I think this will be a great resource for other developers and if anyone finds another way to achieve the same goal, definitely post it here