2016/04/23

Partial Intents and Conversational Tone

As I read through the Alexa UX testing guidelines prior to submitting my new skill for certification, I realized I had not handled partial intents.  The challenge I had was that two different intents each specified a numeric slot type and, based on the state of the skill, the user might have said either prior to saying a number.

The First Attempt

After some playing around, I settled on three core intents:

FooIntent foo {MyNumber}
BarIntent bar {MyNumber}
NumberIntent {MyNumber}

This allowed me to catch the user filling in the numeric slot while saying foo or bar.  It also allowed me to ask the user to provide a number if they did not when saying foo or bar.  I thought this was fairly solid and updated the intent implementations to handle the user leaving out a number:

exports.handleFooAction = function(intent, session) {
    var myNumber = parseMyNumber(intent);

    if (typeof myNumber === 'undefined') {
        setPriorAction(session, 'FOO');
        return { 'view': 'Error_FooToUnspecifiedNumberView', 'data': data };
    }
    ...

}

I added code for the new intent:

exports.handleNumberAction = function(intent, session) {
    var priorAction = getPriorAction(session);
    if (priorAction === 'FOO') {
        clearPriorAction(session);
        return exports.handleFooAction(intent, session);
    } else if (priorAction === 'BAR') {
        clearPriorAction(session);
        return exports.handleBarAction(intent, session);
    } else {
        return { 'view': 'OK_HelpView', 'data': data};
    }
}

In the case of no prior action, the code sends help text to the user, so they can understand how to use the application (we'll come back to this shortly).  After running through a number of tests, I was confident that it behaved as expected and submitted the skill for certification.

Getting Feedback

I did not pass certification ("Alexa, Sad Trombone") due to the NumberIntent triggering the help text when invoked directly.

I'll admit, my first thought was, "Well, yes, if the user does not understand how to give the skill commands, it tries to give them help."

My second thought was, "Forcing the user to say things in an arbitrary order is not good UX, I should fix this."

Back to the Drawing Board

I reworked the code so that, if there is no possibility the user meant bar, the skill assumes the user meant foo.  Otherwise, the user is asked if they would like foo or bar:

exports.handleNumberAction = function(intent, session) {
    ...
    } else {
        if (!isBarAllowed(session)) {
            return exports.handleFooAction(intent, session);
        } else {
            var myNumber = parseMyNumber(intent);
            ...
            setPriorNumber(session, myNumber);
            return { 'view': 'Error_FooOrBarRequired', 'data': data };
        }
    }
}

Both the foo and bar action handlers were updated to account for prior numbers having been provided:

// Check if a prior number was given.
var myNumber = getPriorNumber(session);
if (typeof myNumber !== 'undefined') {
    clearPriorNumber(session);
} else {
    myNumber = parseMyNumber(intent);
}

After some play testing and debugging, the skill appears stable and the interactions feel more natural for having made this change.

Conclusion

I think I'm ready for my next attempt at passing certification.  The lesson I took away is that there is no substitute for user testing when building user experiences.