Handling Requests¶
With the Alexa Skills Kit, spoken phrases are mapped to actions executed on a server. Alexa converts speech into JSON and delivers the JSON to your application. For example, the phrase:
“Alexa, Tell HelloApp to say hi to John”
produces JSON like the following:
"request": {
"intent": {
"name": "HelloIntent",
"slots": {
"firstname": {
"name": "firstname",
"value": "John"
}
}
}
...
}
Parameters called ‘slots’ are defined and parsed out of speech at runtime.
For example, the spoken word ‘John’ above is parsed into the slot named firstname
with the AMAZON.US_FIRST_NAME
data type.
For detailed information, see Handling Requests Sent by Alexa on the Amazon developer website.
This section shows how to process Alexa requests with Flask-Ask. It contains the following subsections:
Mapping Alexa Requests to View Functions¶
📼 Here is a video demo on Handling Requests with Flask-Ask video.
Flask-Ask has decorators to map Alexa requests to view functions.
The launch
decorator handles launch requests:
@ask.launch
def launched():
return question('Welcome to Foo')
The intent
decorator handles intent requests:
@ask.intent('HelloWorldIntent')
def hello():
return statement('Hello, world')
The session_ended
decorator is for the session ended request:
@ask.session_ended
def session_ended():
return "{}", 200
Launch and intent requests can both start sessions. Avoid duplicate code with the on_session_started
callback:
@ask.on_session_started
def new_session():
log.info('new session started')
Mapping Intent Slots to View Function Parameters¶
📼 Here is a video demo on Intent Slots with Flask-Ask video.
When Parameter and Slot Names Differ¶
Tell Flask-Ask when slot and view function parameter names differ with mapping
:
@ask.intent('WeatherIntent', mapping={'city': 'City'})
def weather(city):
return statement('I predict great weather for {}'.format(city))
Above, the parameter city
is mapped to the slot City
.
Assigning Default Values when Slots are Empty¶
Parameters are assigned a value of None
if the Alexa service:
- Does not return a corresponding slot in the request
- Includes a corresponding slot without its
value
attribute - Includes a corresponding slot with an empty
value
attribute (e.g.""
)
Use the default
parameter for default values instead of None
. The default itself should be a
literal or a callable that resolves to a value. The next example shows the literal 'World'
:
@ask.intent('HelloIntent', default={'name': 'World'})
def hello(name):
return statement('Hello, {}'.format(name))
Converting Slots Values to Python Data Types¶
📼 Here is a video demo on Slot Conversions with Flask-Ask video.
When slot values are available, they’re always assigned to parameters as strings. Convert to other Python
data types with convert
. convert
is a dict
that maps parameter names to callables:
@ask.intent('AddIntent', convert={'x': int, 'y': int})
def add(x, y):
z = x + y
return statement('{} plus {} equals {}'.format(x, y, z))
Above, x
and y
will both be passed to int()
and thus converted to int
instances.
Flask-Ask provides convenient API constants for Amazon AMAZON.DATE
, AMAZON.TIME
, and AMAZON.DURATION
types exist since those are harder to build callables against. Instead of trying to define functions that work with
inputs like those in Amazon’s
documentation,
just pass the strings in the second column below:
📼 Here is a video demo on Slot Conversion Helpers with Flask-Ask video.
Amazon Data Type | String | Python Data Type |
---|---|---|
AMAZON.DATE |
'date' |
datetime.date |
AMAZON.TIME |
'time' |
datetime.time |
AMAZON.DURATION |
'timedelta' |
datetime.timedelta |
Examples
convert={'the_date': 'date'}
converts '2015-11-24'
, '2015-W48-WE'
, or '201X'
into a datetime.date
convert={'appointment_time': 'time'}
converts '06:00'
, '14:15'
, or '23:59'
into a datetime.time
.
convert={'ago': 'timedelta'}
converts 'PT10M'
, 'PT45S'
, or 'P2YT3H10M'
into a datetime.timedelta
.
Handling Conversion Errors¶
Sometimes Alexa doesn’t understand what’s said, and slots come in with question marks:
"slots": {
"age": {
"name": "age",
"value": "?"
}
}
Recover gracefully with the convert_errors
context local. Import it to use it:
...
from flask_ask import statement, question, convert_errors
@ask.intent('AgeIntent', convert={'age': int})
def say_age(age):
if 'age' in convert_errors:
# since age failed to convert, it keeps its string
# value (e.g. "?") for later interrogation.
return question("Can you please repeat your age?")
# conversion guaranteed to have succeeded
# age is an int
return statement("Your age is {}".format(age))
convert_errors
is a dict
that maps parameter names to the Exceptions
raised during
conversion. When writing your own converters, raise Exceptions
on failure, so
they work with convert_errors
:
def to_direction_const(s):
if s.lower() not in ['left', 'right']
raise Exception("must be left or right")
return LEFT if s == 'left' else RIGHT
@ask.intent('TurnIntent', convert={'direction': to_direction_const})
def turn(direction):
# do something with direction
...
That convert_errors
is a dict
allows for granular error recovery:
if 'something' in convert_errors:
# Did something fail?
or:
if convert_errors:
# Did anything fail?
session
, context
, request
and version
Context Locals¶
An Alexa
request payload
has four top-level elements: session
, context
, request
and version
. Like Flask, Flask-Ask provides context
locals that spare you from having to add these as extra parameters to
your functions. However, the request
and session
objects are distinct from Flask’s request
and session
.
Flask-Ask’s request
, context
and session
correspond to the Alexa request payload components while Flask’s correspond
to lower-level HTTP constructs.
To use Flask-Ask’s context locals, just import them:
from flask import App
from flask_ask import Ask, request, context, session, version
app = Flask(__name__)
ask = Ask(app)
log = logging.getLogger()
@ask.intent('ExampleIntent')
def example():
log.info("Request ID: {}".format(request.requestId))
log.info("Request Type: {}".format(request.type))
log.info("Request Timestamp: {}".format(request.timestamp))
log.info("Session New?: {}".format(session.new))
log.info("User ID: {}".format(session.user.userId))
log.info("Alexa Version: {}".format(version))
log.info("Device ID: {}".format(context.System.device.deviceId))
log.info("Consent Token: {}".format(context.System.user.permissions.consentToken))
...
If you want to use both Flask and Flask-Ask context locals in the same module, use import as
:
from flask import App, request, session
from flask_ask import (
Ask,
request as ask_request,
session as ask_session,
version
)
For a complete reference on request
, context
and session
fields, see the
JSON Interface Reference for Custom Skills
in the Alexa Skills Kit documentation.