BBQ Chicken with Bell Peppers, Quinoa, and Potatoes: I’m sure I could stretch a metaphor, but nothing to do with Lex really…it just tasted great.

Lex: Your Alexa Skills Lexicon for Alternative-Driven Data

William Raiford
Jul 22, 2017 · 6 min read

Hello. Howdy. Hey. Well hello. Hello there. Well hello there. Good day. Good day to you. Greetings. Greetings and salutations. Yo. Ahoy. Wassup. Sup. Bon jour. Cheerio. Cheers. Guten Tag. Hallo. Grüß Gott. Grüß dich. Hi.

There are a lot of ways to say “hi”, in a lot of different languages. In this blog post, I’ll introduce you how the Lex class in ask-gib can help you leverage these alternatives when writing Alexa Skills for the Amazon Echo.

If you don’t know what ask-gib is (and you probably don’t), you can check out my previous post Easy & Powerful Alexa Skills with TypeScript and ask-gib.


tl;dr

Lex is a powerful and efficient mechanism for dynamically composing speech to use with Alexa Skills (in TypeScript):

  • Alternatives, e.g. hi = hi, hello, hey, etc.
  • Ssml
  • SpeechCons
  • and much more (including i18n and others to be covered in future posts).

Check out the more advanced example at the end of this post to see it all in action.


Alternative-Driven Data

Using Lex boils down to two basic steps:

  1. Define your data with alternatives.
  2. Compose your data when you want it.
// Define your Lexicon data
const data: LexData = {
'hi': [
{ texts: [ "Hi" ] },
{ texts: [ "Hello" ] },
{ texts: [ "Howdy" ] }
]
}
// Compose your speech
let lex = new Lex(data);
let hi = lex._('hi');
hi.text // this property contains "Hi", "Hello", or "Howdy"

The data object will contain all of your speech data for your skill. We’ve started very simply with just a single entry for “hi”. This entry has three alternatives (each with the interface LexDatum, but ignore the shape of the entries for now).

When we go to consume the data, we instantiate the Lex class with our data, which loads it up. Lex only has one single function, and so we call it via lex._() (anything else would be noise). Lex will then build up a LexResultObj, that among other things includes a text property on it that contains plain text. That text will be resolved to a single text entry out of the three alternatives.

Now when you want Alexa to output speech that says “hi”, you get much more variety. But this is only the beginning…


Adding SSML

// Define your Lexicon data
const data: LexData = {
'hi': [
{
texts: [ "Hi" ],
ssmls: [ `${Ssml.prosody("Hi", {rate: "slow"})}` ]
},
{ texts: [ "Hello" ] },
{ texts: [ "Howdy" ] },

]
}
// Compose your speech
let lex = new Lex(data);
let hi = lex._('hi');
hi.ssml
// "<prosody rate="slow">Hi</prosody>", "Hello", or "Howdy"
// Can be used with e.g. OutputSpeech
hi.text
// "Hi", "Hello", "Howdy"
// Can be used with e.g. CardContent for display

Here there are still only three alternatives, but the “Hi” data entry has both texts and ssmls properties and its result hi object (of type LexResultObj) has the corresponding properties text and ssml. Getting the reference to this result object gives you this ability to conveniently use either version. In more advanced scenarios, you can use this result object to get access to both the chosen raw LexDatum alternative, as well as all of the LexDatum alternatives that match your filtering criteria. I’ll post more on these types of advanced scenarios in future posts, but for now just be aware of them. For now, I just want to get to one more fundamental technique in composing dynamic speech for Alexa.


Template References

There are two kinds of templates when using Lex: template references and template variables. In this post, I’ll focus on template references and we’ll cover variables in the future.

const data: LexData = {
'hi': [
{ texts: [ "Hi" ] },
{ texts: [ "Hello" ] },
{ texts: [ "Howdy" ] }
],
'greeting': [
{
texts: [ "$(hi) and welcome to our awesome skill!" ]
}
]
}
lex = new Lex(data);
lex._('greeting').text
// "Hi and welcome to our awesome skill!" or...
// "Hello and welcome to our awesome skill!" or...
// "Howdy and welcome to our awesome skill!"

In the above example, notice the $(hi) in the greeting entry. This is a key to dynamically composing speech. When your text (or ssml) includes the $() , it will extract the text inside of it and recursively resolve that LexData entry.

This makes for extremely dynamic speech, as you can have multiple references in the same template. This enables your output to vary by all the permutations of each template piece.


More Advanced Example

This will show you those permutations, with some additional features I haven’t covered yet:

const data: LexData = {
'hi': [
{ texts: [ "Hi" ] },
{ texts: [ "Hello" ] },
{ texts: [ "Howdy" ] },
{ ssmls: [ Ssml.speech(Con.bonjour) ] },
],
'awesome': [
{ texts: [ "awesome" ] },
{ texts: [ "radical" ] },
{ texts: [ "meh" ], weighting: 0.1 },
],
'greeting': [
{
ssmls: [
`$(hi|{"capitalize": "uppereach"}) and welcome to our $(awesome) skill!`
]
}
]
}
let lex = new Lex(data);let result = [];
// This is only because it's an example of generating a bunch of
// permutations. You wouldn't actually loop like this in code.
for (let i = 0; i < 1500; i++) {
let greeting = lex._('greeting');
if (!result.some(x => x.text === greeting.text) &&
!result.some(x => x.ssml === greeting.ssml)) {
result.push(greeting)
}
}
result.forEach(x => {
console.log(`text: ${x.text}`)
console.log(`ssml: ${x.ssml}`)
})
console.log(`result.length: ${result.length}`)
// Output (with some manual sorting on my part):// text: Hi and welcome to our awesome skill!
// ssml: Hi and welcome to our awesome skill!
// ssml: Hi and welcome to our radical skill!
// text: Hi and welcome to our radical skill!
// text: Hi and welcome to our meh skill!
// ssml: Hi and welcome to our meh skill!
// text: Hello and welcome to our awesome skill!
// ssml: Hello and welcome to our awesome skill!
// text: Hello and welcome to our radical skill!
// ssml: Hello and welcome to our radical skill!
// text: Hello and welcome to our meh skill!
// ssml: Hello and welcome to our meh skill!
// text: Howdy and welcome to our awesome skill!
// ssml: Howdy and welcome to our awesome skill!
// text: Howdy and welcome to our radical skill!
// ssml: Howdy and welcome to our radical skill!
// text: Howdy and welcome to our meh skill!
// ssml: Howdy and welcome to our meh skill!
// text: Bonjour and welcome to our awesome skill!
// ssml: <say-as interpret-as="interjection">Bonjour</say-as> and welcome to our awesome skill!
// text: Bonjour and welcome to our radical skill!
// ssml: <say-as interpret-as="interjection">Bonjour</say-as> and welcome to our radical skill!
// text: Bonjour and welcome to our meh skill!
// ssml: <say-as interpret-as="interjection">Bonjour</say-as> and welcome to our meh skill!

Here I threw in a located in the Ssml class. SpeechCons are now implemented as a pseudo-enum in TS (for auto-completion!) in English (US), English (UK), and German (DE).

And note the template reference $(hi|{“capitalize”: “uppereach”}). In this case, I needed to be sure to capitalize it, in order to have the SpeechCon “bonjour” capitalized. Each template reference can include full options, just as if you were callinglex._(..., options) directly.

And finally, I included a silly Easter egg to showcase the of alternatives. Each alternative has a default weighting of 1, so the probability of getting meh is 0.1/2.1 = 1/21 ~ 4.8% chance.

You can check this code out in this test on ask-gib’s GitHub repo if you want to run it yourself. Just fork/clone ask-gib. There is a vscode build task set to F5 to execute the tests. Be sure to star the repo if you like it, and let me know if you use it.

In future posts, I’ll cover more advanced options, including internationalization (i18n) / localization, multi-line concatenations, template variables, and more.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade