Using a skip list

You can use n-best processing to implement a skip list. A skip list allows the application to avoid choosing the same wrong interpretation twice, thus adding some intelligence to improve performance. The following example from the GatherPhoneNumber sample application shows how to implement a skip list.

Suppose an application subdialog asks the caller for a phone number, and then asks the caller to confirm the recognized number. If the user doesn’t confirm the number, the application adds it to a skip list and then reprompts the user. When the user says the number again, the recognition handler compares the top interpretation to the skip list.

If the interpretation is already in the skip list, the handler tries the next-ranked interpretation, and so on. In this way, the handler ensures that the application won’t ask the user to confirm the same wrong number twice.

To accomplish this, we define a “skiplist” variable and initialize it as a new object. If an incorrect number is chosen, it will be set as a property on this object.

<var name="skiplist" expr="new Object()"/> 

Next, define the field where it will collect the phone number. Then set the maxnbest property to the maximum number of interpretations we wish to receive from the recognition service. In this example, the property enables N-best processing by making up to 10 different interpretations available:

<field name="phoneNumber">
    <property name="maxnbest" value="10"/>

Next, the application defines the grammar for the field. Since it is using a built-in grammar, the grammar could normally be specified as an attribute of the field. However, <nuance:nbest> must be a child of a <grammar> element, so the <grammar> element is used. This is functionally identical to specifying the grammar as an attribute, though slightly more verbose.

    <grammar src="builtin:grammar/phone"> 

The <nuance:nbest> element then defines the recognition handler for this <grammar>. The handler iterates through the list of interpretations until it finds one that isn’t in the skip list. When it finds one, it returns the interpretation. If every item in the array is in the skip list, it returns the first item in the list.

        <nuance:nbest>
            <![CDATA[
                [...]
                var skipitems = "";
                var prop = "";
                for( prop in skiplist )
                    skipitems += prop + " ";
                [...]
                for( var i=0; i < lastresult$.length; i++ )
                {
                if( skiplist[ lastresult$[i].utterance ]
                    == undefined )
                    return lastresult$[i];
                }
                // If we got to this point, then all of the possible
                // results were on the skip list, so just try the 
                // first one again.
                // If we had instead returned undefined, then the
                // interpreter would treat it as a <nomatch>.
                return lastresult$;
            ]]>
        </nuance:nbest>
    </grammar>

Now the prompt for this field is defined.

    <prompt>What number would you like to call?</prompt>
</field>

When this field is filled, the application moves on to the next field to confirm the number with the user.

<field name="confirmNumber" type="boolean">
    <prompt>I heard
        <say-as type="phone">
            <value expr="phoneNumber"/>
        </say-as>
        Is this correct?
    </prompt>

If the user rejects the number (cond="confirmNumber == false"), the application adds the number to the skip list so it won’t ask the caller to confirm this number again. Then it clears the phoneNumber and confirmNumber fields, and since they are no longer filled, returns to the phoneNumber field and prompts for the number again. If the user accepts the number, the application simply returns to the document that called this subdialog.

    <filled>
        <if cond="confirmNumber == false">
            <script> skiplist[ phoneNumber ] = true </script>
            
            <clear namelist="phoneNumber confirmNumber"/>
            <else/>
            <!-- The user confirmed the number. This module is now 
                complete. -->
            <return namelist="phoneNumber"/>
        </if>
    </filled>
</field>

When the user says a number after being reprompted, the recognition handler executes again. It compares the top interpretation with the items in the skip list. If the interpretation is not in the skip list, the handler returns the interpretation and the application goes on to confirm it with the user. If the interpretation is in the skip list, the handler moves to the next interpretation by adding one to the lastresult$ index and checks its interpretation against the skip list.

It repeats this process until it finds an interpretation that is not in the skip list. Then it returns the interpretation for that result and leaves the handler. If every result in the array is on the skip list, the application returns the first interpretation in the array.