Lab 3: Create a Skill by using ACDL in Alexa Conversations
Welcome to lab 3 of our introductory course on building an engaging Alexa Conversations skill by using ACDL. In this lab, we'll learn how to create a skill called "Flight Search" with step-by-step instructions.
Note: This skill is an example only and is not intended to refer to actual airlines or airline data.
Time required: 60 minutes
What you'll learn
ACDL artifacts
Changing invocation name
How to add audio response templates
How to add types
How to add APIs
How to add utterance sets
How to add dialogs
How to create your skill backend code
ACDL artifacts
There are four key ACDL artifacts: Types, Actions, Events and Dialogs. The first three (Types, Actions, Events) are dependencies for dialogs.
Types are equivalent to data type representations in any programming language. They serve a variety of functions for skill building and define the data structures for request and response payloads, slot values, etc.
Events are equivalent to utterance groups in the developer console. Each event is linked to a type which defines the slot inputs a user may provide within those utterances.
Actions are always triggered by events. Actions define which back-end APIs are invoked on a given turn and define how to respond events within the context of an ongoing dialog.
Dialogs pull together all the other ACDL assets to create templatized sample interactions that form the basis of your experience.
Step-by-Step: Build Flight Search Skill
Step 1: Changing invocation name
Let's change the skill invocation name. Skill invocation name is found under skill-package/interactionModels/custom/en-US.json. You will see the current invocation name is "change me", so let's change the skill invocation name to "flight search". This invocation name will be used when customers open your skill. They can say "Alexa, open flight search".
Step 2: How to add audio response templates
Audio responses are under "response/prompts" folder. By default, we will have AlexaConversationsBye, AlexaConversationsOutOfDomain, Alexa ConversationsRequestMore, AlexaConversationsWelcome and AlexaConversationsProvideHelp response prompts. We will start by updating the welcome prompt. Let's open the document.json under AlexaConversationsWelcome folder and replace the "content" with our "flight search" welcome message.
"content":"Welcome to the flight search. I can help you find cheap flights in the main cabin. What do you want to do?"
This is the welcome message that Alexa will prompt when the skill is invoked. Your AlexaConversationsWelcome prompt will look as indicated below:
Copied to clipboard.
{"type":"APL-A","version":"0.1","mainTemplate":{"parameters":["payload"],"item":{"type":"RandomSelector","description":"Change 'type' above to try different Selector Component Types like Sequential","items":[{"type":"Speech","contentType":"text","content":"Welcome to the flight search. I can help you find cheap flights in the main cabin. What do you want to do?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
Now, let's add a prompt for departure city when Alexa asks the user to provide the city name.
First, create a new folder under "response/prompts" folder, and name it "RequestDepartureCityPrompt"
Create a new "document.json" file under that "RequestDepartureCityPrompt"
We will create the content of the "document.json" as follows:
Copied to clipboard.
{"type":"APLA","version":"0.8","mainTemplate":{"parameters":["payload"],"item":{"type":"Selector","strategy":"randomItem","items":[{"type":"Speech","contentType":"text","content":"OK, I can help with that. Where are you traveling from?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"},{"type":"Speech","contentType":"text","content":"great, where are you traveling from?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
Okay, it's easy, right? This prompt might look a bit different from the previous one. The reason is that we have two prompts (see under "items") from which Alexa will randomly pick.
Let's create the remaining response templates for arrival, date, confirmation and final response prompts by following the same steps.
"RequestArrivalCityPrompt":
Copied to clipboard.
{"type":"APLA","version":"0.8","mainTemplate":{"parameters":["payload"],"item":{"type":"Selector","strategy":"randomItem","items":[{"type":"Speech","contentType":"text","content":"Where are you traveling to?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"},{"type":"Speech","contentType":"text","content":"Where do you want to go?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
"RequestDatePrompt"
Copied to clipboard.
{"type":"APLA","version":"0.8","mainTemplate":{"parameters":["payload"],"item":{"type":"Selector","strategy":"randomItem","items":[{"type":"Speech","contentType":"text","content":"What date are you planning to travel?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"},{"type":"Speech","contentType":"text","content":"When are you planning to travel?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
"FlightSearchConfirmPrompt"
Copied to clipboard.
{"type":"APLA","version":"0.8","mainTemplate":{"parameters":["payload"],"item":{"type":"Selector","strategy":"randomItem","items":[{"type":"Speech","contentType":"text","content":"Great, you want to travel from ${payload.departureCity} to ${payload.arrivalCity} on ${payload.date}, is that right?","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
Note: At runtime, Alexa Conversations will replace whatever appears between ${} with the value it represents.
"FlightSearchResponsePrompt"
Copied to clipboard.
{"type":"APLA","version":"0.8","mainTemplate":{"parameters":["payload"],"item":{"type":"Selector","items":[{"type":"Speech","when":"${payload.flightResponse.cost == ''}","contentType":"text","content":"Sorry, I couldn't find any flights from ${payload.flightResponse.departureCity} to ${payload.flightResponse.arrivalCity}.","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"},{"type":"Speech","contentType":"text","content":"The cheapest flight I found from ${payload.flightResponse.departureCity} to ${payload.flightResponse.arrivalCity} is $${payload.flightResponse.cost}. It is at ${payload.flightResponse.time} on ${payload.flightResponse.date} with ${payload.flightResponse.airline}. Have a great trip!","description":"Expand on 'items' array to add multiple prompts, use response template arguments by adding it to 'content' like this ${payload.input_argument_name} and add SSML by changing 'contentType' to 'SSML' and adding SSML to 'content' <amazon:effect name=\"whispered\">like that</amazon:effect>"}]}}}
Note: We will define flightResponse as a custom slot type with properties to represent the data that our API will return.
Your response folder should look as follows:
Once we created the response templates, now we are ready to create "types" in ACDL.
Step 3: How to add types
Types define data structures which are declared with the keyword “type”. In the Flight Search skill, we will need to collect departure city, arrival city, and the date from customers. Therefore, we will create a type to collect these three built-in slots. As a response, we will return date, time, airline, and the cost for the given cities. We will create a custom slot type for the airlines.
"FlightDetails" type, which we will create later, will contain arrivalCity, departureCity, date, time, airline, and cost. We will also create a "FlightDetailsPayload" type for the payload which will have a child referral to "FlightDetails".
Let's open the en-US.json file under skill-package/interactionModels/custom folder.
We will add a new custom type called "Airline" in "types" object as follows:
Copied to clipboard.
{"values":[{"name":{"value":"Lightning Airways"}},{"name":{"value":"Hippogriff Air Lines"}},{"name":{"value":"Harpy Intercontinental"}},{"name":{"value":"Griffin Air"}}],"name":"Airline"}
Your json file should look as noted below:
Copied to clipboard.
{"interactionModel":{"languageModel":{"invocationName":"flight search","intents":[{"name":"AMAZON.CancelIntent","samples":[]},{"name":"AMAZON.StopIntent","samples":[]},{"name":"AMAZON.NavigateHomeIntent","samples":[]},{"name":"AMAZON.FallbackIntent","samples":[]},{"name":"PlaceHolderIntent","samples":["place holder for intent."]}],"types":[{"values":[{"name":{"value":"Lightning Airways"}},{"name":{"value":"Hippogriff Air Lines"}},{"name":{"value":"Harpy Intercontinental"}},{"name":{"value":"Griffin Air"}}],"name":"Airline"}]}}}
Now we are going to change the name of the ACDL file (skill-package/conversations/empty.acdl) to "flightSearch.acdl".
Let's open the "flightSearch.acdl" and change the namespace to "com.flightsearch".
We will import conversations, custom slot type and built-in types to the ACDL file:
Create an input type for the skill to collect departureCity, arrivalCity and date
Copied to clipboard.
type FlightSearchDetails {
optional DATE date
optional US_CITY arrivalCity
optional US_CITY departureCity
}
We create a type "FlightSearchDetails" with three optional slots, so customers can provide any permutations of these three slots or they might provide none of them.
Create an output type for the skill to return the flight details
Copied to clipboard.
type FlightDetails {
DATE date
NUMBER cost
US_CITY arrivalCity
US_CITY departureCity
TIME time
Airline airline
}
Create a payload for the output
Copied to clipboard.
type FlightDetailsPayload {
FlightDetails flightResponse
}
Here is the ACDL file that we created so far:
Copied to clipboard.
namespace com.flightsearch
import com.amazon.alexa.ask.conversations.*
import com.amazon.ask.types.builtins.AMAZON.US_CITY
import com.amazon.ask.types.builtins.AMAZON.DATE
import com.amazon.ask.types.builtins.AMAZON.NUMBER
import com.amazon.ask.types.builtins.AMAZON.TIME
import slotTypes.Airline
type FlightSearchDetails {
optional DATE date
optional US_CITY arrivalCity
optional US_CITY departureCity
}
type FlightDetails {
DATE date
NUMBER cost
US_CITY arrivalCity
US_CITY departureCity
TIME time
Airline airline
}
type FlightDetailsPayload {
FlightDetails flightResponse
}
dialog SampleDialog {
sample {
// your dialog flow goes here
}
}
Step 4: How to add APIs
We are still working on our "flightsearch.acdl" file, and now, we will create an action which will invoke a back-end API and return flight details to us. Our backend API will be named "FlightFinder". The below code is our API FlightFinder which requires date, arrival city and departure city. It will return the FlightDetails type object we created in step 3. Copy the below code under the FlightDetailsPayload type.
This is the declaration of our action which we will use in the dialog.
action ReturnType <action_name>(Type arg1, Type arg2, ...)
ReturnType → FlightDetails
action_name → FlightFinder
Type arg1 → Date date
Type arg2 → US_CITY arrivalCity
Type arg3 → US_CITY departureCity
"flightsearch.acdl" file should look as noted below:
Copied to clipboard.
namespace com.flightsearch
import com.amazon.alexa.ask.conversations.*
import com.amazon.ask.types.builtins.AMAZON.US_CITY
import com.amazon.ask.types.builtins.AMAZON.DATE
import com.amazon.ask.types.builtins.AMAZON.NUMBER
import com.amazon.ask.types.builtins.AMAZON.TIME
import slotTypes.Airline
type FlightSearchDetails {
optional DATE date
optional US_CITY arrivalCity
optional US_CITY departureCity
}
type FlightDetails {
DATE date
NUMBER cost
US_CITY arrivalCity
US_CITY departureCity
TIME time
Airline airline
}
type FlightDetailsPayload {
FlightDetails flightResponse
}
action FlightDetails FlightFinder(DATE date, US_CITY arrivalCity, US_CITY departureCity)
dialog SampleDialog {
sample {
// your dialog flow goes here
}
}
Step 5: How to add utterance sets
The last piece you need before you can start working on your dialogs are utterance sets. They are a collection of unique utterance variations your customer might say that deviate from your happy path. Although Alexa Conversations automatically predicts how your customer may deviate from your happy path, utterance sets allow you to directly influence what Alexa Conversations adds to your model.
For example, one utterance set will represent a set of utterances the Customer might say to request a flight search that includes all three slots you need to call the API. Utterance sets that belong to a group must include the same slots. Since we created an input with 3 optional slots, we can create one utterance set for the following cases:
Customer requests a flight search
Customer requests a flight search and provides departure city, arrival city and date
Customer requests a flight search and provides departure city, arrival city
Customer requests a flight search and provides departure city, date
Customer requests a flight search and provides arrival city, date
Customer requests a flight search and provides departure city
Customer requests a flight search and provides arrival city
Customer requests a flight search and provides date
In ACDL, the given utterances represent variations of the utterances that trigger the event and returns an utterance event.
Let's create an utterance set in our "flightsearch.acdl" file which covers the cases we defined above, you will start adding the utterances to the under the action we created in previous step:
Copied to clipboard.
@locale(
Locale.en_US
)
FlightSearchUtterances = utterances<FlightSearchDetails>(
[
"help me to find the cheapest flight",
"I want to find the cheapest flight",
"Find me the cheapest flight",
"Search for a cheap flight",
"Find me a flight",
"I would like to search for a cheap flight",
"I want to travel from {departureCity} to {arrivalCity} on {date}",
"I want to fly from {departureCity} to {arrivalCity} on {date}",
"I want to go from {departureCity} to {arrivalCity} on {date}",
"I want to travel to {arrivalCity} from {departureCity} on {date}",
"I want to fly to {arrivalCity} from {departureCity} on {date}",
"I want to go to {arrivalCity} from {departureCity} on {date}",
"I want to travel from {departureCity} to {arrivalCity} {date}",
"I want to fly from {departureCity} to {arrivalCity} {date}",
"I want to go from {departureCity} to {arrivalCity} {date}",
"I want to travel to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity} {date}",
"I want to go to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity}",
"I want to go to {arrivalCity} from {departureCity}",
"I want to travel to {arrivalCity} from {departureCity}",
"from {departureCity} to {arrivalCity}",
"I want to travel from {departureCity} on {date}",
"I want to fly from {departureCity} on {date}",
"I want to go from {departureCity} on {date}",
"I want to travel from {departureCity} {date}",
"I want to fly from {departureCity} {date}",
"I want to go from {departureCity} {date}",
"I want to go to {arrivalCity} on {date}",
"I want to fly to {arrivalCity} on {date}",
"I would like go to {arrivalCity} on {date}",
"I want to go to {arrivalCity} {date}",
"I want to fly to {arrivalCity} {date}",
"I would like go to {arrivalCity} {date}",
"from {departureCity}",
"to {arrivalCity}",
"I am traveling from {departureCity}",
"I want to fly from {departureCity}",
"I want to travel from {departureCity}",
"I want to go to {arrivalCity}",
"I want to fly to {arrivalCity}",
"I would like go to {arrivalCity}",
"on {date}",
"{date}"
]
)
Note: Expressions without the @locale annotation are global. In other words, the expressions apply to all locales. You can learn more about locales here.
Let's also create "Affirm" utterance set for the customer:
We use the type "Nothing" for affirm utterance group, so we need to import it to the ACDL. Add the following code to import Nothing. You should add this code to the section where you previously created imports (top of your acdl code):
Copied to clipboard.
import com.amazon.alexa.schema.Nothing
You are doing great, almost done! Here you can find a snapshot of your ACDL code to compare what you created so far:
Copied to clipboard.
namespace com.flightsearch
import com.amazon.alexa.ask.conversations.*
import com.amazon.ask.types.builtins.AMAZON.US_CITY
import com.amazon.ask.types.builtins.AMAZON.DATE
import com.amazon.ask.types.builtins.AMAZON.NUMBER
import com.amazon.ask.types.builtins.AMAZON.TIME
import slotTypes.Airline
import com.amazon.alexa.schema.Nothing
type FlightSearchDetails {
optional DATE date
optional US_CITY arrivalCity
optional US_CITY departureCity
}
type FlightDetails {
DATE date
NUMBER cost
US_CITY arrivalCity
US_CITY departureCity
TIME time
Airline airline
}
type FlightDetailsPayload {
FlightDetails flightResponse
}
action FlightDetails FlightFinder(DATE date, US_CITY arrivalCity, US_CITY departureCity)
@locale(
Locale.en_US
)
FlightSearchUtterances = utterances<FlightSearchDetails>(
[
"help me to find the cheapest flight",
"I want to find the cheapest flight",
"Find me the cheapest flight",
"Search for a cheap flight",
"Find me a flight",
"I would like to search for a cheap flight",
"I want to travel from {departureCity} to {arrivalCity} on {date}",
"I want to fly from {departureCity} to {arrivalCity} on {date}",
"I want to go from {departureCity} to {arrivalCity} on {date}",
"I want to travel to {arrivalCity} from {departureCity} on {date}",
"I want to fly to {arrivalCity} from {departureCity} on {date}",
"I want to go to {arrivalCity} from {departureCity} on {date}",
"I want to travel from {departureCity} to {arrivalCity} {date}",
"I want to fly from {departureCity} to {arrivalCity} {date}",
"I want to go from {departureCity} to {arrivalCity} {date}",
"I want to travel to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity} {date}",
"I want to go to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity}",
"I want to go to {arrivalCity} from {departureCity}",
"I want to travel to {arrivalCity} from {departureCity}",
"from {departureCity} to {arrivalCity}",
"I want to travel from {departureCity} on {date}",
"I want to fly from {departureCity} on {date}",
"I want to go from {departureCity} on {date}",
"I want to travel from {departureCity} {date}",
"I want to fly from {departureCity} {date}",
"I want to go from {departureCity} {date}",
"I want to go to {arrivalCity} on {date}",
"I want to fly to {arrivalCity} on {date}",
"I would like go to {arrivalCity} on {date}",
"I want to go to {arrivalCity} {date}",
"I want to fly to {arrivalCity} {date}",
"I would like go to {arrivalCity} {date}",
"from {departureCity}",
"to {arrivalCity}",
"I am traveling from {departureCity}",
"I want to fly from {departureCity}",
"I want to travel from {departureCity}",
"I want to go to {arrivalCity}",
"I want to fly to {arrivalCity}",
"I would like go to {arrivalCity}",
"on {date}",
"{date}"
]
)
@locale(
Locale.en_US
)
AffirmUtterances = utterances<Nothing>(
[
"correct",
"OK",
"Yeap",
"Yep",
"Yes"
]
)
dialog SampleDialog {
sample {
// your dialog flow goes here
}
}
Step 6: How to add dialogs
So far, we learned how to create responses, utterence sets, types, and APIs. We have all skill assets required for starting to write our dialog samples.
We will use response templates that we created earlier, but first we need to import them. We will add this import statement above the first type definition in our "flightsearch.acdl" file.
Copied to clipboard.
import prompts.*
Let's change the dialog name to "FlightSearch" and add locale annotation.
Now, we can create our dialog sample. Our dialog will expect the invocation from customers which can contain any permutations of the three slots, or none of them.
We created a dialog path for the slot collection through invocation from customers. However, what if customers don't provide all three slots in the invocation? We require all three slots for our API call to find the cheapest flight. Therefore, we will use "ensure" action here, which will make sure that all three slots are collected from customers before moving forward in the dialog flow. "Ensure" action helps to create dialog variations by using request args for the missing slots.
In our dialog, we expect customers to either "affirm" or "deny" the collected slots. As a developer, you can define the deny paths along with the affirm paths in your dialog samples, however, Alexa Conversations will also create the deny paths for the simulation out-of-the-box if you don't create the deny paths. In this example, we will just create the affirm path and let Alexa Conversation handle the deny path.
expect (Affirm, AffirmUtterances)
Once the customer has confirmed all slots, we can call the "FlightFinder" API and assign results to the variable (flightResult) we just created.
We finished making the changes in our ACDL file, so let's compile our changes.
You can compare your flightsearch.acdl file with the following snapshot:
Copied to clipboard.
namespace com.flightsearch
import com.amazon.alexa.ask.conversations.*
import com.amazon.ask.types.builtins.AMAZON.US_CITY
import com.amazon.ask.types.builtins.AMAZON.DATE
import com.amazon.ask.types.builtins.AMAZON.NUMBER
import com.amazon.ask.types.builtins.AMAZON.TIME
import slotTypes.Airline
import com.amazon.alexa.schema.Nothing
import prompts.*
type FlightSearchDetails {
optional DATE date
optional US_CITY arrivalCity
optional US_CITY departureCity
}
type FlightDetails {
DATE date
NUMBER cost
US_CITY arrivalCity
US_CITY departureCity
TIME time
Airline airline
}
type FlightDetailsPayload {
FlightDetails flightResponse
}
action FlightDetails FlightFinder(DATE date, US_CITY arrivalCity, US_CITY departureCity)
@locale(
Locale.en_US
)
FlightSearchUtterances = utterances<FlightSearchDetails>(
[
"help me to find the cheapest flight",
"I want to find the cheapest flight",
"Find me the cheapest flight",
"Search for a cheap flight",
"Find me a flight",
"I would like to search for a cheap flight",
"I want to travel from {departureCity} to {arrivalCity} on {date}",
"I want to fly from {departureCity} to {arrivalCity} on {date}",
"I want to go from {departureCity} to {arrivalCity} on {date}",
"I want to travel to {arrivalCity} from {departureCity} on {date}",
"I want to fly to {arrivalCity} from {departureCity} on {date}",
"I want to go to {arrivalCity} from {departureCity} on {date}",
"I want to travel from {departureCity} to {arrivalCity} {date}",
"I want to fly from {departureCity} to {arrivalCity} {date}",
"I want to go from {departureCity} to {arrivalCity} {date}",
"I want to travel to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity} {date}",
"I want to go to {arrivalCity} from {departureCity} {date}",
"I want to fly to {arrivalCity} from {departureCity}",
"I want to go to {arrivalCity} from {departureCity}",
"I want to travel to {arrivalCity} from {departureCity}",
"from {departureCity} to {arrivalCity}",
"I want to travel from {departureCity} on {date}",
"I want to fly from {departureCity} on {date}",
"I want to go from {departureCity} on {date}",
"I want to travel from {departureCity} {date}",
"I want to fly from {departureCity} {date}",
"I want to go from {departureCity} {date}",
"I want to go to {arrivalCity} on {date}",
"I want to fly to {arrivalCity} on {date}",
"I would like go to {arrivalCity} on {date}",
"I want to go to {arrivalCity} {date}",
"I want to fly to {arrivalCity} {date}",
"I would like go to {arrivalCity} {date}",
"from {departureCity}",
"to {arrivalCity}",
"I am traveling from {departureCity}",
"I want to fly from {departureCity}",
"I want to travel from {departureCity}",
"I want to go to {arrivalCity}",
"I want to fly to {arrivalCity}",
"I would like go to {arrivalCity}",
"on {date}",
"{date}"
]
)
@locale(
Locale.en_US
)
AffirmUtterances = utterances<Nothing>(
[
"correct",
"OK",
"Yeap",
"Yep",
"Yes"
]
)
@locale(
Locale.en_US
)
dialog Nothing FlightSearch {
sample {
findCheapFlights = expect (Invoke, FlightSearchUtterances)
ensure(
RequestArguments {arguments = [FlightFinder.arguments.departureCity], response = RequestDepartureCityPrompt},
RequestArguments {arguments = [FlightFinder.arguments.arrivalCity], response = RequestArrivalCityPrompt},
RequestArguments {arguments = [FlightFinder.arguments.date], response = RequestDatePrompt}
)
response(
response = MultiModalResponse {
apla = FlightSearchConfirmPrompt
},
act = ConfirmAction {
actionName = FlightFinder
},
payload = FlightSearchDetails {
date = findCheapFlights.date,
arrivalCity = findCheapFlights.arrivalCity,
departureCity = findCheapFlights.departureCity
}
)
expect (Affirm, AffirmUtterances)
flightResult = FlightFinder(
findCheapFlights.date,
findCheapFlights.arrivalCity,
findCheapFlights.departureCity
)
response(
response = MultiModalResponse {
apla = FlightSearchResponsePrompt
},
act = Notify {
success = true,
actionName = FlightFinder
},
payload = FlightDetailsPayload {
flightResponse = flightResult
},
)
}
}
To compile the skill artifacts, you need to open the terminal and navigate to your flight search skill main directory (we created a folder called "FlightSearch"). Then, enter the following command:
Copied to clipboard.
askx compile
Step 7: How to create your skill backend code
We completed our skill artifacts and compiled our skill. Now let's work on our backend logic. We will create a lambda handler to handle our API call and we will create a simple database which contains the flight information for the search. Let's start!
Open the "lambda" folder in your flightsearch directory. We will start by creating the "package.json" file. Copy the below code to the created "package.json".
We will create a simple database in the lambda directory which has flight information for some cities. Create a file called "flight-data.json" and copy the below code. To keep it simple, we will only search for departure and arrival city without actual dates.
Let's create a flightSearch.js file in lambda folder for a simple search function to query our flight-data.json database.
Copied to clipboard.
constFLIGHT_DATA=require('./flight-data.json');// query to the flight databasemodule.exports.getFlightData=(cityFrom,cityTo)=>{if(cityFrom&&FLIGHT_DATA[cityFrom]&&cityTo&&FLIGHT_DATA[cityFrom][cityTo]){returnFLIGHT_DATA[cityFrom][cityTo]}return{"cost":"","time":"","airline":""};};
Now, let's work on utility functions that we will use in our beckend; create a "util.js" file in lambda folder to keep common functions that we will use in the backend. Copy the below code to the "util.js":
Copied to clipboard.
constAlexa=require('ask-sdk-core');constAWS=require('aws-sdk');// check the api requestmodule.exports.isApiRequest=(handlerInput,apiName)=>{try{returnAlexa.getRequestType(handlerInput.requestEnvelope)==='Dialog.API.Invoked'&&handlerInput.requestEnvelope.request.apiRequest.name===apiName;}catch(e){console.log('Error occurred: ',e);returnfalse;}}// return the slot resolutionmodule.exports.getApiSlot=function(handlerInput,slot){constfilledSlots=handlerInput.requestEnvelope.request.apiRequest.slots;letid=null;letheardAs='';letresolved='';letconfirmationStatus='';leterStatus='';if(filledSlots[slot]){if(filledSlots[slot].resolutions?.resolutionsPerAuthority[0]?.status?.code){switch(filledSlots[slot].resolutions.resolutionsPerAuthority[0].status.code){case'ER_SUCCESS_MATCH':id=filledSlots[slot].resolutions.resolutionsPerAuthority[0].values[0].value.id;heardAs=filledSlots[slot].value;resolved=filledSlots[slot].resolutions.resolutionsPerAuthority[0].values[0].value.name;confirmationStatus=filledSlots[slot].confirmationStatus;erStatus='ER_SUCCESS_MATCH';break;case'ER_SUCCESS_NO_MATCH':heardAs=filledSlots[slot].value;confirmationStatus=filledSlots[slot].confirmationStatus;erStatus='ER_SUCCESS_NO_MATCH';break;default:heardAs=filledSlots[slot].value;confirmationStatus=filledSlots[slot].confirmationStatus;break;}}else{heardAs=filledSlots[slot].value;resolved=filledSlots[slot].value;confirmationStatus=filledSlots[slot].confirmationStatus;}}return{id:null,heardAs:heardAs,resolved:resolved,confirmationStatus:confirmationStatus,erStatus:erStatus};}// return resolved value, if not, return filled slot valuemodule.exports.getApiSlotBestValue=function(handlerInput,slot){constslotValues=module.exports.getApiSlot(handlerInput,slot);if(slotValues.resolved===''){returnslotValues.heardAs;}else{returnslotValues.resolved;}}// return a string with capitalized first lettersmodule.exports.capitalizeFirstLetter=function(str){if(typeofstr!=='string'){return'';}else{returnstr.toLowerCase().split(' ').map(word=>{returnword.charAt(0).toUpperCase()+word.slice(1)}).join(' ');}}
We are ready to create our lambda handler now. In our lambda handler, we will have our API handler for flight search, session ended request handler and error handler. Let's create a file called "index.js" and copy the below code.
Copied to clipboard.
/* *
* This sample demonstrates handling intents from an Alexa skill using the Alexa Skills Kit SDK (v2).
* Please visit https://alexa.design/cookbook for additional examples on implementing slots, dialog management,
* session persistence, api calls, and more.
* */constAlexa=require('ask-sdk-core');constutil=require('./util');constflightSearch=require('./flightSearch')/* *
* FlightFinderHandler searches for the flight for given departure and arrival city and returns the response to the skill.
* This handler will be triggered when three slots are collected: arrivalCity, departureCity and date
* Response contains the json which maps FlightDetails type in ACDL and display for APL template
* */constFlightFinderHandler={canHandle(handlerInput){returnutil.isApiRequest(handlerInput,'com.flightsearch.FlightFinder');//this needs to be your namespace and api name},handle(handlerInput){console.log(`flight finder handler: ~~~ ${JSON.stringify(handlerInput)}`);constdeparture=util.getApiSlotBestValue(handlerInput,"departureCity");//name of the U.S. city given in the api for departureCity slot (API definition)constarrival=util.getApiSlotBestValue(handlerInput,"arrivalCity");//name of the U.S. city given in the api for arrivalCity slot (API definition)constdate=util.getApiSlotBestValue(handlerInput,"date");//date in the api for date slot (API definition)constflightData=flightSearch.getFlightData(departure,arrival);// response maps to FlightDetails type in ACDL// arrivalCity, departureCity, date, time, cost and airline// display is used in APL templateconstresponse={arrivalCity:arrival,departureCity:departure,date:date,time:flightData.time,cost:flightData.cost,airline:flightData.airline};console.log("response: ",response);returnhandlerInput.responseBuilder.withApiResponse(response).withShouldEndSession(false)// Setting this to false keeps the mic on after Alexa responds.getResponse();}};/* *
* SessionEndedRequest notifies that a session was ended. This handler will be triggered when a currently open
* session is closed for one of the following reasons: 1) The user says "exit" or "quit". 2) The user does not
* respond or says something that does not match an intent defined in your voice model. 3) An error occurs
* */constSessionEndedRequestHandler={canHandle(handlerInput){returnAlexa.getRequestType(handlerInput.requestEnvelope)==='SessionEndedRequest';},handle(handlerInput){console.log(`~~~~ Session ended: ${JSON.stringify(handlerInput.requestEnvelope)}`);// Any cleanup logic goes here.returnhandlerInput.responseBuilder.getResponse();// notice we send an empty response}};/**
* Generic error handling to capture any syntax or routing errors. If you receive an error
* stating the request handler chain is not found, you have not implemented a handler for
* the intent being invoked or included it in the skill builder below
* */constErrorHandler={canHandle(){returntrue;},handle(handlerInput,error){constspeakOutput='Sorry, I had trouble doing what you asked. Please try again.';console.log(`~~~~ Error handled: ${JSON.stringify(error)}`);returnhandlerInput.responseBuilder.speak(speakOutput).reprompt(speakOutput).getResponse();}};/**
* This handler acts as the entry point for your skill, routing all request and response
* payloads to the handlers above. Make sure any new handlers or interceptors you've
* defined are included below. The order matters - they're processed top to bottom
* */exports.handler=Alexa.SkillBuilders.custom().addRequestHandlers(FlightFinderHandler,SessionEndedRequestHandler).addErrorHandlers(ErrorHandler).withCustomUserAgent('sample/hello-world/v1.2').lambda();
You are ready to deploy your skill now! You can run the following command in your skill directory to start the deployment process. It will take around 20-30 mins.
Copied to clipboard.
askx deploy
Note: If you created a new profile, you should run askx deploy -p tutorial ("tutorial" is an example profile, replace it with your profile name).
Note: Each time when you run askx deploy you will receive the following default ACDL message: "Skills with ACDL are not yet compatible with the https://developer.amazon.com, hence this skill will be disabled on the Developer Console. Would you like to proceed?". You should enter "y" (Yes) in order to continue on to deployment.
Step 8: Test your skill
Once the deployment is completed, you are ready to test your skill! You have two options to test:
You can use your echo devices that are linked to your account and invoke your skill by saying "Alexa, open flight search"
You can test your skill in developer console. If you prefer this option: