Fetching Entity Information

While testing the previous example, if you inspected the intent request closely enough, you may have spotted something very interesting about the resolved slot value. Specifically, not only did the value have a name, it also had an id property whose value is a URL.

For example, if the city spoken in place of the city slot were Paris, the request might look a little like this:

 "city"​: {
 "name"​: ​"city"​,
 "value"​: ​"Paris"​,
 "resolutions"​: {
 "resolutionsPerAuthority"​: [
  {
 "authority"​: ​"AlexaEntities"​,
 "status"​: {
 "code"​: ​"ER_SUCCESS_MATCH"
  },
 "values"​: [
  {
 "value"​: {
 "name"​: ​"Paris"​,
»"id"​: ​"https://ld.amazonalexa.com/entities/v1/1z1ky..."
  }
  }
  ]
  }
  ]
  },
 "confirmationStatus"​: ​"NONE"​,
 "source"​: ​"USER"​,
 "slotValue"​: {
 "type"​: ​"Simple"​,
 "value"​: ​"Paris"​,
 "resolutions"​: {
 "resolutionsPerAuthority"​: [
  {
 "authority"​: ​"AlexaEntities"​,
 "status"​: {
 "code"​: ​"ER_SUCCESS_MATCH"
  },
 "values"​: [
  {
 "value"​: {
 "name"​: ​"Paris"​,
»"id"​: ​"https://ld.amazonalexa.com/entities/v1/1z1ky..."
  }
  }
  ]
  }
  ]
  }
  }
 }

As it turns out, the URL in the id property can be fetched with an HTTP GET request to lookup additional information about the resolved entity. This is a relatively new feature called Alexa Entities and at this time is currently in Beta for skills deployed in the following locales:

  • English (AU)
  • English (CA)
  • English (IN)
  • English (UK)
  • English (US)
  • French (FR)
  • German (DE)
  • Italian (IT)
  • Spanish (ES)

(We’ll talk more about locales in Chapter 8, Localizing Responses.)

In the case of a slot whose type is AMAZON.City, that includes details such as the average elevation, which larger government boundaries the city is contained within (for example, metroplex, state, country), and the human population of the city. While not all skills will need this extra information, it can come in very handy for skills that do.

For example, suppose that we were building a skill with an intent that provided population information for a city. Such an intent might be defined like this:

 { "name": "CityPopulationIntent", "slots": [ { "name": "city", "type": "AMAZON.City" } ], "samples": [ "what is the population of {city}", "tell me about {city}", "how many people live in {city}", "how big is {city}" ] },

The expectation is that if the user were to ask, “What is the population of Paris?” then Alexa would respond with the number of people living in Paris.

Without Alexa Entities, you’d have to maintain a database of city population data or perhaps delegate out to some API that provides such information. But with Alexa Entities, the information is readily available to your skill, just for the asking.

The way to ask for entity data is to make an HTTP GET request to the URL in the id property, providing an API access token in the Authorization header of the request. The API access token is made available in the intent’s request envelope and can be easily be obtained with Alexa.getApiAccessToken() like this:

 const​ apiAccessToken =
  Alexa.getApiAccessToken(handlerInput.requestEnvelope);

You’ll also need the request’s locale, which is just as readily available from the request envelope:

 const​ locale = Alexa.getLocale(handlerInput.requestEnvelope);

With the entity URL, locale, and an access token in hand, making the request for entity information can be done using any JavaScript client library that you like. For our project, we’ll use the Axios client library. You can install it by issuing the following command from the project’s root directory:

 $ ​​npm​​ ​​install​​ ​​--prefix=lambda​​ ​​axios

With Axios installed, the following snippet shows how to request entity information:

 const​ resolvedEntity = resolutions.values[0].value.id;
 const​ headers = {
 'Authorization'​: ​`Bearer ​${apiAccessToken}​`​,
 'Accept-Language'​:locale
 };
 
 const​ response =
 await​ axios.​get​(resolvedEntity, { headers: headers });

Here, the first resolution is chosen and its ID is assigned to a constant named resolvedEntity. The value of resolvedEntity is not just a simple ID, but also the URL of the entity to be fetched. Therefore, it is passed in as the URL parameter to axios.get() to retrieve entity details.

Assuming that the request is successful, the response will include a JSON document with several properties that further define the resolved entity.

As an example, here’s a sample of what you’ll get if the entity is the city of Paris:

 {
 "@context"​: {
  ...
  },
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/1z1kyo7XwxYGAKcx5F3TCf"​,
 "@type"​: [ ​"City"​ ],
 "averageElevation"​: [{ ​"@type"​: ​"unit:Meter"​, ​"@value"​: ​"28"​ }],
 "capitalOf"​: [
  {
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/LGYtKPDONTW..."​,
 "@type"​: [ ​"Country"​ ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"France"​ }]
  }
  ],
 "countryOfOrigin"​: {
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/LGYtKPDONTWCtt6..."​,
 "@type"​: [ ​"Country"​ ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"France"​ }]
  },
»"humanPopulation"​: [{ ​"@type"​: ​"xsd:integer"​, ​"@value"​: ​"2140000"​ }],
 "locatedWithin"​: [
  {
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/DAy2cvRGvSB..."​,
 "@type"​: [ ​"Place"​ ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"Paris"​ }]
  },
  {
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/1NlBgtwDmHb..."​,
 "@type"​: [ ​"Place"​ ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"Île-de-France"​ }]
  },
  {
 "@id"​: ​"https://ld.amazonalexa.com/entities/v1/LGYtKPDONTW...."​,
 "@type"​: [ ​"Country"​ ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"France"​ }]
  }
  ],
 "name"​: [{ ​"@language"​: ​"en"​, ​"@value"​: ​"Paris"​ }]
 }

Without looking any further, your skill can use any of this information as it sees fit, including reporting the population of the city. But also notice that some of the properties include their own URLs in @id properties. So, for example, if you wanted your skill to dig even deeper into the country that Paris is the capital of, you could make another request, following the URL in the @id property from the countryOfOrigin property.

All we need for a simple city population skill, however, is the value from the humanPopulation property. The following fetchPopulation() function shows how we might fetch the population for a given set of resolutions and API access token:

 const​ fetchPopulation = ​async​ (resolutions, locale, apiAccessToken) => {
 const​ resolvedEntity = resolutions.values[0].value.id;
 const​ headers = {
 'Authorization'​: ​`Bearer ​${apiAccessToken}​`​,
 'Accept-Language'​:locale
  };
 
 const​ response =
 await​ axios.​get​(resolvedEntity, { headers: headers });
 if​ (response.status === 200) {
 const​ entity = response.data;
 if​ (​'name'​ ​in​ entity && ​'humanPopulation'​ ​in​ entity) {
 const​ cityName = entity.name[0][​'@value'​];
 const​ population = entity.humanPopulation[0][​'@value'​];
 const​ popInfo = {
  cityName: cityName,
  population: population
  };
 return​ popInfo;
  }
  } ​else​ {
 return​ ​null​;
  }
 };

After sending the GET request for the entity, if the response is an HTTP 200 (OK) response, then it extracts the value of the humanPopulation property from the response. We’ll also need to know the fully resolved entity name for our intent’s response, so while fetching the population, we also fetch the value of the name property. Both are packed up in an object and returned to the caller.

As for how fetchPopulation() is used, here’s the intent handler which asks for the population and uses the city name and population from the returned object to produce a response to the user:

 const​ CityPopulationIntentHandler = {
  canHandle(handlerInput) {
 return​ Alexa.getRequestType(
  handlerInput.requestEnvelope) === ​'IntentRequest'
  && Alexa.getIntentName(
  handlerInput.requestEnvelope) === ​'CityPopulationIntent'​;
  },
 async​ handle(handlerInput) {
 const​ apiAccessToken =
  Alexa.getApiAccessToken(handlerInput.requestEnvelope);
 const​ slot =
  Alexa.getSlot(handlerInput.requestEnvelope, ​'city'​);
 const​ resolutions = getSlotResolutions(slot);
 const​ locale = Alexa.getLocale(handlerInput.requestEnvelope);
 
 if​ (resolutions) {
 const​ popInfo =
 await​ fetchPopulation(resolutions, locale, apiAccessToken);
 
 if​ (popInfo !== ​null​) {
 const​ speechResponse =
 `​${popInfo.cityName}​'s population is ​${popInfo.population}​.`
 return​ handlerInput.responseBuilder
  .speak(speechResponse)
  .getResponse();
  }
  }
 
 const​ reprompt = ​'What city do you want to know about?'​;
 const​ speakOutput =
 "I don't know what city you're talking about. Try again. "
  + reprompt;
 return​ handlerInput.responseBuilder
  .speak(speakOutput)
  .reprompt(reprompt)
  .getResponse();
  }
 };

This handler leans on a couple of helper functions to extract the slot resolutions from the given slot:

 const​ getSlotResolutions = (slot) => {
 return​ slot.resolutions
  && slot.resolutions.resolutionsPerAuthority
  && slot.resolutions.resolutionsPerAuthority.find(resolutionMatch);
 };
 
 const​ resolutionMatch = (resolution) => {
 return​ resolution.authority === ​'AlexaEntities'
  && resolution.status.code === ​'ER_SUCCESS_MATCH'​;
 };

With all of this in place, if the user were to ask for the population of Paris, Alexa will respond by saying, “Paris’s population is 2,140,000.”

Not all built-in slot types support Alexa entities. Several slot types do, however, including:

  • AMAZON.Person
  • AMAZON.Movie
  • AMAZON.Animal
  • AMAZON.City
  • AMAZON.Country
  • AMAZON.Book
  • AMAZON.Author
  • AMAZON.TVSeries
  • AMAZON.Actor
  • AMAZON.Director
  • AMAZON.Food
  • AMAZON.MusicGroup
  • AMAZON.Musician
  • AMAZON.MusicRecording
  • AMAZON.MusicAlbum

Of course, each slot type will have information relevant to that type. AMAZON.Movie, for example, won’t have a humanPopulation property, but it will have a property named entertainment:castMember that is an array of actors who were in the movie. Each entry in the entertainment:castMember array is itself a reference to a person with an @id that you can use to look up additional information about the actor, such as their birthday.

Now let’s take our skill beyond the confines of Earth and create a custom type that represents planetary destinations instead of relying on the built-in AMAZON.City type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset