Alexa Icon

In this tutorial, I will show how to set up a simple Alexa Flash Briefing skill using AWS Lambda and Python. The skill will grab the latest entries from the subreddit r/todayilearned, where users submit interesting factoids, and read them to the user. You will learn how to:

  • create an Alexa Flash Briefing Skill
  • set up an Amazon Lambda Function
  • upload Python code to the Lambda and connect it to your skill

Introduction

A while ago Amazon added the feature of Flash Briefing Skills, which are skills that are executed when users ask Alexa for their daily summary (which, for example, give a news summary). This is neat because a good skill here is potentially used on a daily basis.

The code for a skill itself is often not difficult to program, but getting all the moving parts of the AWS ecosystem in place can be a bit of a hassle, which is why I created this tutorial. We’ll develop a Flash Briefing skill, but you can easily build on this tutorial if you want to create a regular skill with a more complex interaction structure.

Note: This tutorial is written for Python 3.6, but most of it applies to other languages supported by AWS Lambda as well.

Creating the Alexa Skill

First, open the Amazon Developer Console (if you don’t have an account yet register for one), go to Alexa and add a new skill. Then select the Flash Briefing option.

Creating a new Alexa skill in the Amazon Developer Console

For a regular skill you would now have to define the interaction model, but with Flash Briefing skills this is pre-defined because there is just one interaction (call the skill).

Next, in the configuration menu enter an error message and click on “Add new feed”. Fill out the information here up until URL. Here we have to enter the endpoint of our AWS Lambda which returns the response for the feed - which does not exist yet. Time to change that!

Configuring the Flash Briefing Feed

Setting up the execution role for our Lambda

Before we program the Lambda, we have to set up its execution role. When you create a Lambda, you have to give it a role from Amazon’s Identity and Access Management (IAM). This way AWS knows what permissions your Lambda has. If you haven’t set up a role for a Lambda before that you can use, here’s how you do it:

Open up the AWS IAM console. Click on Create Role and go through the steps to set up the role:

  • Step 1: Choose the service that will use this role, which is Lambda in our case
  • Step 2: Attach a permission policy for the role, which is AWSLambdaBasicExecutionRole (you can find it by searching for lambda) The IAM console step 2, choose permission role
  • Step 3: In the final step, choose a name for the role. This just has to be unique for your account and something you recognize, like “todayilearned”.

After you have created the role, look at its summary and note down the Role ARN which we will need later when uploading our Lambda code from the command line.

If you get stuck at any point have a look at the AWS tutorial on how to set up role.

Creating the function code

So what’s our goal here? Our function will load the most recent posts from the subreddit r/todayilearned and return the text of the top post, which Alexa will then read. You can also return multiple items in your feed, but we’ll stick to one for the sake of simplicity.

Alexa expects a response in JSON or RSS format. You can see the exact specification in the Flash Briefing API reference. In a new folder, create a file called index.py and paste the following code:

import json
from datetime import datetime

def response(error, reply):
    return {
        'statusCode': '400' if error else '200',
        'body': json.dumps({
            'uid': '1',
            'updateDate': datetime.utcnow().isoformat() + 'Z',
            'titleText': 'Today I Learned',
            'mainText': json.dumps(error['message'] if error else
                               reply['message'], ensure_ascii=False),
            'redirectionUrl': reply['url']
        }, ensure_ascii=False),
        'headers': {
        'Content-Type': 'application/json',
        },
    }

def handler(event, context):
    fact_text, url, error = get_fact() #To be done
    reply = {
        'message': fact_text,
        'url': url
    }
    return response(error, reply)

When our Lambda will be executed, the function handler is called which will return the response in the correct format. A few notes here:

  • The updateDate requires “that the date should be specified in UTC/Zulu time”, hence the additional 'Z' (see the API reference). Don’t look at me, I didn’t make this up.
  • Don’t forget ensure_ascii=False in the json.dumps, which is True by default and can break with non-ASCII characters (I was stumped by that for a while).

The only thing missing is the function get_fact which loads the text from reddit.

import requests
import unidecode
import time

def get_fact():

  # Insert your credentials here
  user_dict = {
    'user': 'REDDIT_USER',
    'passwd': 'REDDIT_PW',
    'api_type': 'json'
    }
  try:    
    session = requests.Session()
    session.headers.update({'User-Agent': 'Testing Alexa'})
    session.post('https://www.reddit.com/api/login', data = user_dict)
    time.sleep(0.5)
    url = 'https://reddit.com/r/todayilearned/.json?limit=1'
    html = session.get(url)
    data = json.loads(html.content.decode('utf-8'))
    fact_data = data['data']['children'][0]['data']
    # All posts start with 'TIL ', so we'll format the string accordingly
    fact_text = 'Today I learned ' + fact_data['title'][4:])
    fact_url = fact_data['url']
    return fact_text, fact_url, None
  except Exception as error:
    return None, None, error

Add this function to your file. That’s it for the code - you can test whether it works by adding the snippet

if __name__ == '__main__':
  print(handler(None, None))

and running your script locally. It should give you the correct reply. Note to remove these lines before uploading our code in the next step or it will break the Lambda.

Uploading the Lambda deployment package

You can in principle create a Lambda directly in the AWS Lambda console, but the default Lambda Python 3 environment contains only a limited set of libraries. If you want to include additional libraries, you have to create a deployment package, containing your function code and additional libraries and upload it. Although we’re not doing fancy stuff, we do require the requests library to contact the Reddit API which is unfortunately not in the AWS Lambda default environment - so deployment package it is.

In the same folder where you have index.py, run

pip install requests -t

to install requests and dependencies in the folder. Next, package everything into a zip file.

zip -r deployment.zip *

Don’t forget the -r flag or subfolders of the libraries won’t be included.

When we have our deployment package, we can upload it using the AWS Command Line Interface. You can install it with pip:

pip install awscli

To upload the deployment package and create an new lambda, run the following command:

$ aws lambda create-function \
--function-name todayilearned  \
--region us-east-1 \
--zip-file fileb://path-to/deployment.zip \
--role EXECUTION-ROLE-ARN \
--handler index.handler \
--runtime python3.6  \

For the --role flag, insert the Role ARN we have created above. The --handler flag specifies the file and function that will be called when the Lambda is run.

You can now go to the AWS Lambda console and check that the function has been created. Here you can also edit the function code directly without having to upload it again; the libraries in our deployment package will remain saved with the lambda.

If you have any problems with deploying the Lambda, have a look at the AWS Tutorials on how to create a Python deployment package and how to upload the deployment package.

Adding a Trigger and API Gateway

We’re almost there. Now we have to make sure our Lambda is called correctly. The Alexa skill can call an URL endpoint, so we need to set up an API Gateway trigger that exposes our lambda by an URL.

In the AWS Lambda console, select our function todayilearned, go to Triggers, add a new trigger and choose “API Gateway”. You can set the security policy to “Open”, which means that anyone can call this endpoint. This is fine as we are not doing anything sensitive, just be aware of this.

The menu for setting up an API Trigger in AWS Lambda

After you created the Gateway, if you click the name of your Trigger or go to the API Gateway Console you should see the new Gateway. Go to “Stages” and select the GET method of the stage (create a stage if none exists yet). You should see the invoke URL. Our Alexa skill will use this URL to call the Lambda Function, so copy it or keep the page open.

Let’s test whether the Lambda works - copy the invoke URL into your favorite browser. When you hit enter, our Lambda is called and you should see the correct JSON response. It’s alive!

Testing the invoke URL in a Browser returns the correct response

You can take a look at the API Gateway docs if you get stuck at any point.

Finalizing the Skill

We’re almost done! All that’s left to do is tell our Alexa skill the new endpoint for our Lambda. Go back to the Amazon Developer Console where we created our skill, go to the Configuration tab, select the feed and paste the URL from the previous step into the field URL. Now you can save the skill. It should give you a confirmation if the endpoint returns a correct response.

That’s it! Test the skill by subscribing to it in the Alexa app.

Summary

In this Tutorial, you learned how to

  • set up an Alexa Flash Briefing Skill
  • create an Amazon Lambda Function
  • upload Python code to the Lambda

There are many directions where you can go from here. If you want to build a regular Alexa app (not a Flash Briefing), you actually know almost everything needed for that. The only difference is that a regular App can have a more complex interaction model.

Leave a comment below if you found any errors or have feedback. Cheers!