266 lines
8.9 KiB
ReStructuredText
266 lines
8.9 KiB
ReStructuredText
|
|
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:
|
||
|
|
|
||
|
|
.. code-block:: javascript
|
||
|
|
|
||
|
|
"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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/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:
|
||
|
|
|
||
|
|
.. contents::
|
||
|
|
:local:
|
||
|
|
:backlinks: none
|
||
|
|
|
||
|
|
Mapping Alexa Requests to View Functions
|
||
|
|
----------------------------------------
|
||
|
|
|
||
|
|
📼 Here is a video demo on `Handling Requests with Flask-Ask video <https://youtu.be/6RoSi3G1chk>`_.
|
||
|
|
|
||
|
|
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 <https://youtu.be/AnyZG2AJE4o>`_.
|
||
|
|
|
||
|
|
|
||
|
|
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 <https://youtu.be/gP86uugBico>`_.
|
||
|
|
|
||
|
|
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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference#Slot%20Types>`_,
|
||
|
|
just pass the strings in the second column below:
|
||
|
|
|
||
|
|
📼 Here is a video demo on `Slot Conversion Helpers with Flask-Ask video <https://youtu.be/2RoRoABK_VE>`_.
|
||
|
|
|
||
|
|
=================== =============== ======================
|
||
|
|
Amazon Data Type String Python Data Type
|
||
|
|
=================== =============== ======================
|
||
|
|
``AMAZON.DATE`` ``'date'`` ``datetime.date``
|
||
|
|
``AMAZON.TIME`` ``'time'`` ``datetime.time``
|
||
|
|
``AMAZON.DURATION`` ``'timedelta'`` ``datetime.timedelta``
|
||
|
|
=================== =============== ======================
|
||
|
|
|
||
|
|
**Examples**
|
||
|
|
|
||
|
|
.. code-block:: python
|
||
|
|
|
||
|
|
convert={'the_date': 'date'}
|
||
|
|
|
||
|
|
converts ``'2015-11-24'``, ``'2015-W48-WE'``, or ``'201X'`` into a ``datetime.date``
|
||
|
|
|
||
|
|
.. code-block:: python
|
||
|
|
|
||
|
|
convert={'appointment_time': 'time'}
|
||
|
|
|
||
|
|
converts ``'06:00'``, ``'14:15'``, or ``'23:59'`` into a ``datetime.time``.
|
||
|
|
|
||
|
|
.. code-block:: python
|
||
|
|
|
||
|
|
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:
|
||
|
|
|
||
|
|
.. code-block:: javascript
|
||
|
|
|
||
|
|
"slots": {
|
||
|
|
"age": {
|
||
|
|
"name": "age",
|
||
|
|
"value": "?"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Recover gracefully with the ``convert_errors`` context local. Import it to use it:
|
||
|
|
|
||
|
|
.. code-block:: python
|
||
|
|
|
||
|
|
...
|
||
|
|
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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference#Request%20Format>`_
|
||
|
|
has four top-level elements: ``session``, ``context``, ``request`` and ``version``. Like Flask, Flask-Ask provides `context
|
||
|
|
locals <http://werkzeug.pocoo.org/docs/0.11/local/>`_ 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 <https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interface-reference>`_
|
||
|
|
in the Alexa Skills Kit documentation.
|