alexa-app
alexa-app copied to clipboard
Add contexts, provide another layer of routing for intents
I've got a feature idea that I am going to implement in to my own fork of this. I'd be happy to submit it as a Pull Request if this is something you'd like!
The feature introduces the idea of "Contexts" which provide another layer of routing for intents. Basically, registered Intents can have one or more "Contexts" associated with them. When a request comes in indicating that it is in a particular context (by way of a private sessionAttribute managed by the module), the actual Intent event that fires will be the one that is for that context.
This sort of thing will be very useful in situations where many different multi-step "conversational" responses are expected. For example, there might be 5 different situations in which Alexa asks the user a yes or no question, and so that same "YesIntent" would need to behave very differently in each. Currently the way to deal with this is:
app.intent("YesIntent", function(req, res) {
if (req.session('question' === "AreYouHappy") {
// User answered "Yes" to "Are you happy?"
} else if (req.session('question' === "IsItRaining") {
// User answered "Yes" to "Is it raining?"
} else if (req.session('question' === "HaveYouEaten") {
// User answered "Yes" to "Have you eaten?"
}
}
But with my proposed implementation, it would be this, which is much easier to understand at a glance what is going on. Not to mention more modular!
app.intent({intent: 'YesIntent', context: 'AreYouHappy'}, function(req, res) {
// User answered "Yes" to "Are you happy?"
}
app.intent({intent: 'YesIntent', context: 'IsItRaining'}, function(req, res) {
// User answered "Yes" to "Is it raining?"
}
app.intent({intent: 'YesIntent', context: 'HaveYouEaten'}, function(req, res) {
// User answered "Yes" to "Have you eaten?"
}
If this were implemented to have fallbacks to handle Intents not found in the specific context as well as allow lists of contexts to be specified for a single event, this could end up being very powerful and easy to use for the developer.
Anyway, I'm starting work on it, but let me know if this is something you'd like PRd here!
I like the idea of this and I am interested in seeing what you come up with. What comes to my mind is that this more of a state machine, and you want to map the request to code that is written to handle a specific state. So, the intent may be YesNoIntent, but under condition X, it is in state Y, so you write code to handle state Y. That way, multiple conditions or intents could be mapped to the same state. How you define the conditions might be configurable, or might be code-driven. It could be based on session data, or time of day, or if today is a holiday, etc. This came up to me because I was thinking of writing a "princess story" app for my daughter to play with. For that, there would be many yes/no responses or other single words, but depending on where she is in the story, it would generate a different response. So I would need a way to track where she is, and what she said, and decide what state that puts me in. That can be done in code manually, like you point out, but an abstraction to make it easier would be great. Instead of a fork, do you think this could be an add-on module? Or depending on what you come up with, merging it back into the core module might be fine.
Cool, I'll keep you updated. The context definitions could certainly be expanded to support more complex matching patterns. A string means a simple "context", but a method could manually define the parameters that qualify the state. That'd be an easy thing to expand later without breaking this initial simpler implementation.
I'm not sure how I would write it as an addon as I'm not familiar with how you've set up the module to accept addons.
Another pattern I am considering is based on the Mocha test patterns, which has a nice organization to it:
app.context('AreYouHappy', function() {
app.intent("YesIntent", function(req, res) {
// User answered "Yes" to "Are you happy?"
}
app.intent("NoIntent", function(req, res) {
// User answered "No" to "Are you happy?"
}
app.intent(null, function(req, res) {
// User used an intent that valid for this context. We could provide a prompt clarifying the options
}
})
Pros and cons to each. This version groups the contexts together very clearly which I like. It could also support nested contexts which could make very complex conversational apps (perhaps like the one you outlined) even easier to organize...
I have been doing something similar by storing a "lastIntent" session variable and then checking it in my handler. It works well but I would also be interested to see how a built in context handler might impact code conciseness.
+1 for the hierarchical context callbacks. This maps well to how I conceptualize the problem. The intent handler only makes sense from within the context. Also this provides the bonus ability to initialize a local scope that is common to all intent handlers within a context:
app.context('AreYouHappy', function() {
var contextInfo = getContextInfo();
app.intent("YesIntent", function(req, res) {
// User answered "Yes" to "Are you happy?"
}
...
})
This is something I'm looking for too. Looks like some great ideas here.
I'm thinking of a structure like this. I think I can make it actually function. I've been racking my brain trying to come up with a code structure that could (a) represent a conversational structure in the code, and (b) execute correctly on each call, based on previously-established context. This is very difficult.
app.map(function() {
say('do you want a pizza?');
if (intent('yes')) {
say('do you want extra cheese?');
if (intent('yes')) {
say('do you want pepperoni?');
if (intent('yes')) {
}
else if (intent('no')) {
}
}
else if (intent('no')) {
}
}
else if (intent('no')) {
}
else if (intent()) {
}
Here's a very rough POC of how code like this might function: https://jsfiddle.net/8b4zg4ft/ Any thoughts?
I find that structure very difficult to read. The branching on any app that requires more than a simple yes or no (good apps should allow the user to specify stuff out of order) would become out of control quickly.
I think the app.context method proposed a couple times earlier is a bit more flexible and organized.
I was trying to make the code kind of model the conversational structure of a skill. But if the skill requires more complicated branching like more of a FSM, then this definitely wouldn't work. I think that defining separate contexts and intent handling within them is fine, you just lose the logical nesting that makes code like my example easy to follow. Let me ponder it a little more. I think we can get a little better than the one-level flat context/intent mapping and create some kind of structure that better represents the flow. Maybe.
This feature is implemented in in alexia - similar framework for handling Alexa requests (for those who are interested) See actions section in https://github.com/Accenture/alexia
I've taken a slightly different approach to this problem.
With a very simple change that adds routes to the schema object and app.route as a top level app object (merely to keep the style consistent), I can route intents to different functions, based on a __STATE__ session variable.
This leaves intents responsible only for routing, and routes responsible for the output and user flow. A route can also be re-used in different intents.
app.route('mainMenu', function(request, response){
response.session('__STATE__', 'mainMenu');
response.say('Do you like rock music?');
});
app.route('artistType', function(request, response){
response.session('__STATE__', 'artistType');
response.say('Do you like The Clash?');
});
app.route('albumName', function(request, response){
response.session('__STATE__', 'albumName');
response.say('Do you like London Calling?');
});
app.launch(function(request, response) {
app.route.mainMenu(request, response);
});
app.intent('YesIntent', {
'routes': {
'mainMenu': 'artistType',
'artistType': 'albumName'
},
'utterances':[
'yes',
]},
function(request, response) {
var state = request.session('__STATE__'); //e.g. 'artistType'
var func = this.schema.routes[state];
app.route[func](request, response); //e.g. call app.route.albumName
}
);
Now, when YesIntent is invoked, it calls the correct route function based on where the user came from i.e. the last __STATE__.
To make it cleaner, setting the __STATE__ session and calling the correct route function could be handled automatically by alexa-app in the routes and intents -- the idea being to keep the default behaviour the same and fallback to the normal intent function call if no routes are defined.
It feels a little more straight-forward than nested contexts, and this demo functionality only required a couple of one line changes to alexa.app.
What do people think of this type of approach?
Just adding my 0.02c but I wonder whether this can be implemented on top of alexa-app. It's fairly opinionated and is not how Amazon dictates authoring intents.
If anybody is keen on something like this, I think its worth pointing out this feature request in the forums that would actually make this feature useful.
Worth giving it a thumbs up if you have a chance
@h0lmie do you have a gist of how you accomplished this? i'm looking at trying todo the same thing.