Enabling Flexibility with Synonyms

If you were to ask anyone who is even vaguely familiar with astronomy to list the planets in our solar system, they would probably give you the names of the nine planets that we’ve provided as the values in our custom type. But some of those planets have nicknames. Mars, for example, is colloquially known as “the red planet.” Some might refer to Saturn as “the ringed planet.” Even Earth has a few nicknames, including “the third rock from the sun.”

In order to make conversations with Alexa feel more natural, it helps if she can understand these nicknames and interpret them to mean the more formal name. This can be done by adding synonyms to the values in the custom type. For example, have a look at the following snippet from skill-package/interactionModels/custom/en-US.json, showing the first four entries from the PLANETS type, each with a new synonyms property:

 "types": [ { "name": "PLANETS", "values": [ { "id": "MERCURY", "name": { "value": "Mercury", "synonyms": [ "the one closest to the sun", "the swift planet" ] } }, { "id": "VENUS", "name": { "value": "Venus", "synonyms": [ "the morning star", "the evening star" ] } }, { "id": "EARTH", "name": { "value": "Earth", "synonyms": [ "the blue planet", "the big blue marble", "the third rock from the sun" ] } }, { "id": "MARS", "name": { "value": "Mars", "synonyms": [ "the red planet" ] } }, ... ] } ]

As you can see, each of the entries is given an array of alternate names in the synonyms property. Mercury, for instance may be referred to as “the one closest to the sun” or “the swift planet,” in addition to its official name, “Mercury.” This makes it possible for a user to plan a trip to Mercury by saying something like “Schedule a trip to the swift planet leaving June 9th and returning June 17th.” To prove that it works, the following test can be added to the schedule-trip.test.yml file:

 - test: ​Plan a trip (synonym)
 - ​"​​Schedule​ ​a​ ​trip​ ​to​ ​the​ ​swift​ ​planet​ ​leaving​ ​June​ ​9th​ ​and
 returning​ ​June​ ​17th"​:
  - prompt: ​I've got you down for a trip to
 Mercury, leaving on June 9th and returning June 17th.

Let’s see what happens when we run the test:

 $ ​​bst​​ ​​test​​ ​​--jest.collectCoverage=false​​ ​​schedule-trip.test.yml
 
 BST: v2.6.0 Node: v17.6.0
 Pro tip! Each of our tools have elements you can customize, like user ID
 or the locale. Learn more by using your favorite Bespoken tool and the
 --help option.
 
  FAIL test/schedule-trip.test.yml
  en-US
  Schedule a trip
  ✓ Schedule a trip to Mercury leaving Friday and returning
  next Thursday
  Plan a trip
  ✓ Plan a trip between June 9th and June 17th to visit Mars
  Plan a trip (synonym)
  ✕ Schedule a trip to the swift planet leaving June 9th and
  returning June 17th
 
  ● en-US › Plan a trip (synonym) › Schedule a trip to the swift planet
  leaving June 9th and returning June 17th
 
  Expected value at [prompt] to ==
  I've got you down for a trip to Mercury, leaving on June 9th
  and returning June 17th.
  Received:
  <speak>I've got you down for a trip to the swift planet, leaving on
  June 9th and returning June 17th.</speak>
 
  28 | - "Schedule a trip to the swift planet leaving June 9th and
  returning June 17th":
  >​​ ​​29​​ ​​|​​ ​​-​​ ​​prompt:​​ ​​I​​'​​ve​​ ​​got​​ ​​you​​ ​​down​​ ​​for​​ ​​a
  trip to Mercury, leaving on June 9th and returning June 17th.
 
  at test/schedule-trip.test.yml:30:0
  Timestamp:
  2019-05-27T21:55:25.020
 
 Test Suites: 1 failed, 1 total
 Tests: 1 failed, 2 passed, 3 total
 Snapshots: 0 total
 Time: 1.038s
 Ran all test suites.

Oh no! The test fails! It expects that the response’s output speech to be “I’ve got you down for a trip to Mercury, leaving on June 9th and returning June 17th” but receives “I’ve got you down for a trip to the swift planet, leaving on June 9th and returning June 17th.” Something is still not quite right. Let’s dig deeper into the request to see why.

When given an utterance that says, “Schedule a trip to the swift planet leaving June 9th and returning June 17th,” then the “destination” slot will appear like this in the request:

 "destination"​: {
 "name"​: ​"destination"​,
 "value"​: ​"the swift planet"​,
 "resolutions"​: {
 "resolutionsPerAuthority"​: [
  {
 "authority"​: ​"amzn1.er-authority.echo-sdk.{SKILL ID}.PLANETS"​,
 "status"​: {
 "code"​: ​"ER_SUCCESS_MATCH"
  },
 "values"​: [
  {
 "value"​: {
 "name"​: ​"Mercury"​,
 "id"​: ​"MERCURY"
  }
  }
  ]
  }
  ]
  },
 "confirmationStatus"​: ​"NONE"​,
 "source"​: ​"USER"
 }

As you can see, the destination property’s value property is “the swift planet,” exactly as uttered in the request. And, you might recall that the intent handler extracts the value of the destination slot from the request with this line:

 const​ destination =
  Alexa.getSlotValue(handlerInput.requestEnvelope, ​'destination'​);

The getSlotValue() function will return the value property of the slot, “the swift planet” in this particular case. This phrase is then used to produce the response’s speech text. The response is, “I’ve got you down for a trip to the swift planet…” instead of, “I’ve got you down for a trip to Mercury…” which is what our test expects.

Looking further in the destination property, you see that the resolutions property carries additional information about the slot’s value that comes from resolving the slot’s entity, including a name property with “Mercury” as its value. This is precisely what we want. There’s a lot to take in when looking at the resolutions property, so let’s break it down bit by bit.

resolutionsPerAuthority is an array of entity resolutions that matched what Alexa heard for the slot. Although it is an array, it almost always has a single entry which aligns with the slot type, in this case, PLANETS, as shown in the authority property.

The status property indicates, via the code sub-property, whether or not entity resolution successfully found a match. In this case, the status code is “ER_SUCCESS_MATCH”, which means that “the swift planet” successfully resolved to a value. Had it not successfully matched one of the entities in PLANETS, the status code would have been “ER_SUCCESS_NO_MATCH” and there would be no values property in the resolution object.

The values property is an array containing an entry for every entity that matches what Alexa heard for that slot. Each entry will contain both the ID and the name of the matching entity. There is usually only a single matching value, but if there is any ambiguity, the values array may contain two or more entries. If there are multiple matches, the first entry is the closest match and is usually the best one to use.

Now that we know what to expect in the resolutions property, we can create a helper module to extract the resolved entity value from the slot:

 const​ Alexa = require(​'ask-sdk-core'​);
 
 const​ getResolvedSlotValue = (requestEnvelope, slotName) => {
 const​ slotResolution = Alexa.getSlot(requestEnvelope, slotName)
  .resolutions.resolutionsPerAuthority[0];
 
 return​ slotResolution.status.code === ​'ER_SUCCESS_MATCH'​ ?
  slotResolution.values[0].value.name :
  Alexa.getSlotValue(requestEnvelope, slotName);
 };
 
 module.exports = getResolvedSlotValue;

The function in this handy module extracts the slot resolution from the given request envelope, then considers its status code to decide whether or not to return the values[0].value.name property. If the status is “ER_SUCCESS_MATCH”, then the resolved value will be returned. Otherwise, the unresolved slot value (from Alexa.getSlotValue()) will be returned. We could alternatively pivot on ER_SUCCESS_NO_MATCH and respond to the user indicating that we don’t know anything of the slot value that they are asking about. Instead, we’ll leverage Alexa’s support for automatic validation in the next chapter.

Now we can change the intent handler to import the helper module and use its getResolvedSlotValue() function to extract the destination from the intent request:

 const​ getResolvedSlotValue = require(​'./Helpers'​);
 
  ...
 
 const​ destination =
  getResolvedSlotValue(handlerInput.requestEnvelope, ​'destination'​);

With this change to the intent handler, we can try our test again:

 $ ​​bst​​ ​​test​​ ​​--jest.collectCoverage=false​​ ​​schedule-trip.test.yml
 
 BST: v2.6.0 Node: v17.6.0
 Use bst speak to interact directly with Alexa.
 
  PASS test/schedule-trip.test.yml
  en-US
  Schedule a trip
  ✓ Schedule a trip to Mercury leaving Friday and returning next
  Thursday
  Plan a trip
  ✓ Plan a trip between June 9th and June 17th to visit Mars
  Plan a trip (synonym)
  ✓ Schedule a trip to the swift planet leaving June 9th and
  returning June 17th
 
 Test Suites: 1 passed, 1 total
 Tests: 3 passed, 3 total
 Snapshots: 0 total
 Time: 1.031s
 Ran all test suites.

It passes! When the user asks to schedule a trip to “the swift planet,” Alexa automatically interprets that as the entity we defined as Mercury. From there the intent handler is able to fetch the resolved entity’s name rather than the nickname for the planet. Entity resolution has saved the day!

With all tests are passing, now’s a good time to deploy the skill (by using git to push the code to AWS CodeCommit) and try it out vocally with an actual device or manually with ask dialog. If you were to use ask dialog to test the skill, the session might look like this:

 $ ​​ask​​ ​​dialog​​ ​​--locale​​ ​​en-US
 User > open star port seventy five
 Alexa > Welcome to Star Port 75 Travel. How can I help you?
 User > plan a trip to the red planet leaving July 17th and
  returning July 31st
 Alexa > I've got you down for a trip to Mars, leaving on
  2019-07-17 and returning 2019-07-31.

After asking for a trip to “the red planet,” a synonym for Mars, Alexa responds by saying that she has reserved a trip to Mars. For fun, feel free to try scheduling a trip to any of the other planets using the synonyms declared in the interaction model.

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

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