0.10.1 • Published 5 years ago

lex-gib v0.10.1

Weekly downloads
-
License
MIT
Repository
gitlab
Last release
5 years ago

lex-gib

Variety for Voice UI

See also ask-gib for using all of the ibGib Alexa Skills technologies together, including Ssml, Lex, and more.

npm

With Voice UI, Variety is indeed the spice of life. Lex is all about saying 'hi':

'hi' => 'hi', 'hello', 'hey', 'howdy', 'cheers', 'greetings', 'guten Tag', 'hallo', 'grüß dich', 'grüß Gott', and more.

And that's only for the simple act of saying 'hi'.

Voice AI interaction is inherently different than previous programming: Lex tackles this with Alternative-Driven Data, which comes in two flavors:

  1. Language alternatives (i18n)
  2. Idiomatic alternatives

Language alternatives are i18n. Idiomatic alternatives are equivalent alternative expressions for the same concept. This is a really difficult problem to approach, and the permutations become ridiculous quickly. This is where Lex comes in.

Take the following line of code:

let hi = lex._('hi);

Ordinary i18n might give you something like this:

  • US: 'hi'
  • UK: 'hi'
  • DE: 'hi'

Nothing overly exciting here, since all three have the same word for 'hi'. But with Alexa Skills, and voice interaction in general, you may actually want variety. Here is what Lex can give you:

  • US: 'hi', 'hello', 'hey', 'howdy', ...
  • UK: 'hi', 'hello', 'hey', 'cheers' ...
  • DE: 'hi', 'hallo', 'guten Tag', 'grüß dich', ...

So with a single line of code, you've not only addressed i18n (language alternatives), but you've also gained idiomatic alternatives as well.

There are additional configuration options for, e.g., alternative probability weighting, template references, capitalization, line concatenation, and much more. See Usage for more details.

Installation

Install with npm:

npm install --save lex-gib

Import ES6 style:

// When defining your data
import { LexData } from 'lex-gib';

// When consuming your data
import { Lex } from 'lex-gib';

Usage

All usage of Lex follows two basic steps:

  1. Define the LexData
  2. Retrieve your data via Lex.

Note if you're using the full ask-gib lib: In these examples, I am defining a new Lex instance every time. But in practice, you will most likely want to use the FuncyAlexaSkill.lex property to take advantage of i18n adjustments per incoming request.locale.

Simple

  1. LexData
    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ]}
        ]
    }
  2. Lex
    let lex = new Lex(data);
    lex._('hi').text
    // "Hi"

Ssml

Ssml is just as easy. In this, I show you that you can just get the hi object back and use the ssml/text at will. This is useful if you want to use the ssml for speech and plain text for cards.

Note: This uses the Ssml class from ssml-gib (npm) 1. LexData

```typescript
const data: LexData = {
    'hi': [
        { 
            texts: [ "Hi" ], 
            ssmls: [ `${Ssml.prosody("Hi", {rate: "slow"})}` ] 
        }
    ]
}
```
  1. Lex
    let lex = new Lex(data);
    let hi = lex._('hi')
    hi.ssml
    // '<prosody rate="slow">Hi</prosody>'
    hi.text
    // "Hi"

Alternatives

But there are a LOT of ways to say "hi", and this is the primary reason for using Lex: Alternatives. With Lex, multiple items with the same id are considered alternatives.

  1. LexData
    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ] },
            { texts: [ "Hello" ] },
            { texts: [ "Howdy" ] }
        ]
    }
  2. Lex

    let lex = new Lex(data);
    lex._('hi').text
    // "Hi", "Hello", _or_ "Howdy" randomly with equal probability

So by using the same calling code, you could get any one of these texts as alternatives for the "hi" lex datum. This is a huge difference between natural voice interaction and computer UI as we have known it up to now.

If you want to get really fancy (looking forward to AI/ML), you can weight the various alternatives, for example if you want to only say "Howdy" a small percentage of the time. You could define this as follows:

  1. LexData
    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ] },    // implicit weighting of 1
            { texts: [ "Hello" ] }, // implicit weighting of 1
            { texts: [ "Howdy" ], weighting: 0.2 }
        ]
    }
  2. Lex
    let lex = new Lex(data);
    lex._('hi').text
    // "Hi", "Hello", _or rarely_ "Howdy"

Again, there is no change to the calling code. This really allows for a wonderful layer of dynamicism, and is easy to do.

Templates - Template References

Oftentimes, you want to compose chunks of speech but still utilize alternative-driven data. For example, if you have an entry 'hi' as above with three alternatives, you don't want to create another entry that hard-codes "hello", e.g. "Hello and welcome to our Awesome Skill!" Instead, you can utilize all of your existing "hi" alternatives and embed it using a template reference:

  1. LexData
    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ] },
            { texts: [ "Hello" ] },
            { texts: [ "Howdy" ] }
        ],
        'greeting': [
            { texts: [ "$(hi) and welcome to our Awesome Skill!" ] } // $(hi) is the ref
        ]
    }
  2. Lex
    let lex = new Lex(data);
    lex._('greeting').text
    // "Hi and welcome to our Awesome Skill!"
    // "Hello and welcome to our Awesome Skill!"
    // "Howdy and welcome to our Awesome Skill!"

Using templates for composing Lex data enables you to have tremendously powerful alternative-driven speech.

NB: You're good-to-go with nested templates, but you can't have any circular references.

Templates - Template Variables

You can also have dynamic variable replacement inside lex data. That sounds complicated, but it's pretty much simple search & replace.

Notice in the above example that the greeting has a reference to "hi", indicated by the id inside $(). You can indicate a variable replacement with a $ followed by direct characters without the parentheses, like this:

  1. LexData
    const data: LexData = {
        'welcome': [
            { texts: [ "Welcome back, $user!" ] },
        ],
    }
  2. Lex
    let lex = new Lex(data);
    let user = "Cotter";
    lex._('welcome', { vars: {user} }).text
    // "Welcome back, Cotter!"

The only thing to point out here is that the {user} bit is utilizing shorthand for {user: user}. You can also have, e.g., {user: "Cotter"} if you don't want to define an external variable with the same name as the template placeholder, but using the same variable names makes the calling code more terse. But this does couple your code to your data, which you may want to avoid.

You can also replace template variables differently for texts vs. ssmls. This is useful when you want to create a text for a card with a variable replacement, but you also want to have a replacement that includes SSML tags, such as the following.

  1. LexData
    const data: LexData = {
        'clue': [
            { 
                texts: [ `Your clue is '$clue'.` ],
                ssmls: [ `Your clue is '$clue'.` ] 
            },
        ],
    }
  2. Lex

    let lex = new Lex(data);
    let clueText = "backwards swirl-father";
    let clueSsml = `backwards swirl ${Ssml.break({ms: 200})} father`;
    let clue = lex._('clue', { 
        vars: { clue: clueText }, 
        ssmlVars: { clue: clueSsml }, 
    });
    
    clue.text
    // "Your clue is 'backwards swirl-father'."
    clue.ssml
    // "Your clue is 'backwards swirl <break time=\"200ms\"/> father'."

Internationalization (i18n)

So far, we've only shown examples of idiomatic alternatives. What about i18n, i.e. language alternatives?

Well it turns out we can grow our data naturally with our first language and then easily add translations when the time comes. This is because our defaultLanguage so far has been en-US. Here is how we could add on other translations:

  1. LexData

    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ] },  // defaultLanguage is en-US
            { texts: [ "Hello" ] },
            { texts: [ "Howdy" ], weighting: 0.2 },
    
            { texts: [ "Hi" ], language: "en-GB" },
            { texts: [ "Hello" ], language: "en-GB" },
            { texts: [ "Cheers" ], language: "en-GB" },
    
            { texts: [ "Hi" ], language: "de-DE" },
            { texts: [ "Hallo" ] , language: "de-DE" },
            { texts: [ "Guten Tag" ], language: "de-DE" }
        ]
    }
  2. Lex

    let lex = new Lex(data, "en-US"); // data defaultLanguage is en-US
    
    // The following must be set per request (automatically handled if you're using askGib)
    lex.requestLanguage = <LanguageCode>request.locale; // incoming request is de-DE
    
    lex._('hi').text
    // "Guten Tag"

    OR

    let lex = new Lex(data, "en-US");
    lex._('hi', { language: "de-DE" }).text; // choose language explicitly when calling
    // "Guten Tag"

Note there are a couple different places here where we're choosing a language. Each of these has a different function.

First notice that the en-US is not explicitly stated in the data. This is because it is set as our defaultLanguage. In step 2's consuming code, the "en-US" in the constructor configures lex.defaultLanguage to match up with how we've defined our LexData. (We didn't need it before, because it defaults to en-US.)

If, for example, you are a DE developer and you want to first develop your data only auf Deutsch, you could just as easily define your data as follows:

const data: LexData = {
    'hi': [
        { texts: [ "Hi" ] },  // defaultLanguage is de-DE
        { texts: [ "Hallo" ] },
        { texts: [ "Guten Tag" ] }
    ]
}

// Let Lex know our data's implied defaultLanguage is de-DE on instantiation.
let lex = new Lex(data, "de-DE");
lex.requestLanguage = "de-DE";

And then add en-US and en-GB as needed, explicitly stating the language in each entry:

const data: LexData = {
    'hi': [
        { texts: [ "Hi" ], language: "en-US" },    // now en-US is explicit
        { texts: [ "Hello" ], language: "en-US" },
        { texts: [ "Howdy" ], language: "en-US", weighting: 0.2 },

        { texts: [ "Hi" ], language: "en-GB" },    // also explicit
        { texts: [ "Hello" ], language: "en-GB" },
        { texts: [ "Cheers" ], language: "en-GB" },

        { texts: [ "Hi" ] },  // implicit
        { texts: [ "Hallo" ] },
        { texts: [ "Guten Tag" ] }
    ]
}

// Let Lex know our data's implied defaultLanguage is de-DE on instantiation.
let lex = new Lex(data, "de-DE");
lex.requestLanguage = <LanguageCode>request.locale; // as noted above, set per request

So not only is i18n possible, but it is transparent when you first are developing your skill which enables an easy path for growth.

Combined with Ssml helper from ssml-gib (npm)

As I showed above, Lex naturally makes use of Ssml from ssml-gib. I just wanted to give you another example but with SpeechCons, because they really liven up your Alexa Skill:

  1. LexData
    const data: LexData = {
        'hi': [
            { texts: [ "Hi" ] },
            { texts: [ "Hello" ] },
            { 
                texts: [ "Aloha" ] 
                ssmls: [ `${Ssml.speech(Con.aloha)}` ] // SpeechCons!
            },
            { 
                texts: [ "Great Scott! Hello" ] 
                ssmls: [ `${Ssml.speech(Con.great_scott)! Hello}` ] // SpeechCons!
            },
        ]
    }
  2. Lex

    let lex = new Lex(data);
    
    lex._('hi').text
    // Hi
    // Hello
    // Aloha
    // Great Scott! Hello
    
    lex._('hi).ssml
    // Hi
    // Hello
    // <say-as interpret-as="interjection">aloha</say-as> 
    // <say-as interpret-as="interjection">great scott</say-as>! Hello 

The Ssml class generates all the current SSML tags supported for Alexa Skills , and I encourage you to check it out if you're not already using the full askGib library!

Note: SpeechCons are extremely powerful when combined with templates. See below with modalGib's data.

But Wait, There's More!

You can also include other options in your data and then filter on those, including:

  • keywords
    • use KeywordMode to "all", "some", or "none" to control how the filtering works.
  • specifier
    • Basically a unique id if you don't want alternatives.
  • props
    • Attach an entire property graph to your data and then filter on those properties using lambda functions.
    • Is basically a key/value alternative to keywords for filtering.
  • For more information, see the unit tests and JSDocs in code. If you have any questions at all, or if you need the current source, don't hesitate to ping me at my email address associated with npm and I'll be glad to do what I can to help. And if you're using this with a non-ask-gib skill, I'd love to hear your feedback!

Thanks

  • Amazon for creating such good documentation and a good product.
  • @lazerwalker on GH for feedback and giving me some motivation for breaking out ask-gib into multiple repos and packages.
  • Up.
0.10.1

5 years ago

0.10.0

5 years ago

0.9.8

5 years ago

0.9.7

6 years ago

0.9.6

6 years ago

0.9.5

6 years ago

0.9.4

6 years ago

0.9.3

6 years ago

0.9.2

6 years ago

0.9.1

6 years ago

0.9.0

6 years ago

0.8.3

6 years ago

0.8.2

6 years ago

0.8.1

6 years ago

0.8.0

7 years ago

0.7.0

7 years ago