COVID-19 time and Alexa Skill, part III

Pawel Piwosz
4 min readApr 8, 2020

--

(continued)

Time for Backend preparation

As I mentioned in the first part, for my Alexa Skill, the AWS Lambda function is Backend. Officially we have three SDKs available: https://developer.amazon.com/en-US/docs/alexa/sdk/alexa-skills-kit-sdks.html. The most popular is node.js one, and I think it is the most developed one from this set. That is why I have chosen the Python one :) To clarify one thing — I’m not a developer, although I know something about node.js and Python. And I prefer to do my skills with Python, even if this SDK is still behind the node.js one.

Infrastructure first

For prerequisite, I need to create an infrastructure for the Lambda function. There are not many resources needed. As you may remember, I said in the first part of this story that I didn’t use the API Gateway as an endpoint for Alexa Skill. I connected it directly to Lambda. Anyway, API Gateway is the thing you should consider as an endpoint for the Skill.

So what resources are needed? Lambda function, obviously, IAM role for this Lambda (to allow your function to do some interactions, in my case it is only the S3 bucket and CloudWatch access) and Lambda trigger to connect it with the Skill. The reason behind it will be explained later.

Those resources are a must-have. You can consider the S3 Bucket for storing artifacts (I did it, although I do not deploy from Bucket). Another option to consider — use of VPC. I didn’t put the function in VPC though, as it doesn’t need any specific resource — the functionality here is very simple: after trigger, connect to external API and send the response. No other resources are needed, therefore there is no need to enclose the function inside the VPC.

Ok, now let’s go back to the trigger I mentioned before.

This is how it looks in CloudFormation:

MainFunctionSkillTrigger:
Type: AWS::Lambda::Permission
Properties:
Action: lambda:InvokeFunction
FunctionName: !GetAtt
- COVIDFunctionLambda
- Arn
Principal: alexa-appkit.amazon.com
EventSourceToken: !Ref SkillArn

What is there? And why? Well, ‘why’ is simple: this is a kind of security-ish approach, by defining the trigger, we are saying “this Lambda can be triggered only by specified resource”, in my case, it is the Alexa Skill, which is provided by parameter SkillArn. This is also the answer for “what is there” :)

Lambda function — the code

Well, as I mentioned, I’m not a developer :) So the code is probably one of the ugliest you have ever seen. But hey! It is doing its job :)

Anyway, how did I structure the code? Very simply. First, I used one of the examples from AWS to create the main part of the function. This part is responsible for Intents’ logic on the backend side.

The most important thing in my approach was to differentiate the answers. As I described it in part II of this series, I’ve created only one Intent, therefore I was “pushed” to do all the logic inside the code. In my case, there were four types of questions to answer:

  • tell global info
  • tell global info for specific data (like number of recovered)
  • tell country related info
  • tell country related info for specific data

So, as you can see, there are two different types (global and country specific) of data, and also two different calls to API with different data received. So the easiest way was to create two functions for it.

Also, I’ve create the function called person_or_people, as I believe it looks very bad to say 1 people, or 10 persons. Even if such a function already exists, it is so small that I even didn’t bother to look for it. The code is so simple:

def person_or_people(amount):
""" define the output word for amount of people """
if int(amount) == 0:
crowd = ""
elif int(amount) == 1:
crowd = "person"
else:
crowd = "people"
return crowd

I found this function very important. I believe the biggest challenge for “talking devices” and “talking interactions” is to make it as natural as possible. And believe me it is very hard to achieve.

Logic for returning data to Alexa

I mentioned two elements for differentiating functions. For returning data I needed to do a similar thing — logic for returning all data and for returning specific information. But that is obvious, I believe. I show one of those functions (it is the function for global information) below.

def create_response_all(payload, range):
""" Create response for Alexa with world data"""
response_start = "Global information"
if range == "all":
response_line = "Cases {}, deaths {}, recovered {}, active {}".format(
payload['cases'],
payload['deaths'],
payload['recovered'],
payload['active']
)
message = "{}. {}".format(
response_start,
response_line
)
else:
message = "{}. There is {} people {} at the moment.".format(
response_start,
payload[range],
range
)
return message

And yes, I know it can be written in a better way :D

--

--