Serverless e Alexa - personalizando sua assistente pessoal
Tags: node, serverless, lambda, tutorial
Published: 2022-06-26 08:01:18

 _"Alexa o que iremos fazer hoje?"_
 **_"-Vamos desenvolver nossa própria skill usando Nodejs e AWS Lambda!"_**


As assistentes pessoais já fazem parte dos nossos dias, com suas inúmeras utilidades e possibilidades a [Alexa](https://www.amazon.com.br/b?ie=UTF8&node=19949683011) tem se tornado a queridinha das assistentes.

Quer aprender como desenvolver sua própria [Skill](https://www.amazon.com.br/b?ie=UTF8&node=17938238011) e ter a Alexa um pouquinho mais com a sua cara?
Então vem comigo, que vou mostrar um pouco como foi minha experiência desenvolvendo com a Alexa.😉

## Antes de começarmos ...

Todo comando que fazemos para Alexa é dividido em pequenas partes.


![diagrama exibindo o fluxo de comando da alexa](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ll2neuza8itsh66jtfxi.jpg)

**_intent_**: Comando de voz.

**_utterances_**: Enunciado que identifica a sua intenção.

**_slots_**: Valor do comando que será enviado para API.

A Alexa funciona dentro de uma arquitetura serverless, então para termos nossa própria _skill_ vamos precisar de uma _function_ [AWS Lambda](https://aws.amazon.com/pt/lambda/) e podemos aproveitar do [Free Tier](https://aws.amazon.com/pt/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=*all&awsf.Free%20Tier%20Categories=*all) da AWS para desenvolver e hospedar a nossa funçãozinha. 😎

![alexa cloud system](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/be4hbstefmrnhhln6gpw.jpg)

O diagrama acima exemplifica bem como vai funcionar todo o fluxo saindo do usuário até a execução da _lambda_.


![jin carry digitando super rápido](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/skhuuct28xcu4x95qsrd.gif)

## Show me the code

Chega de conversa e vamos para o código, para esse tutorial vamos desenvolver uma _skill_ que nos responda com um ensinamento aleatório do nosso querido Mestre Yoda. 

O código dessa solução está disponível no Github, clique no [link do repositório](https://github.com/pgsilva/function-ensinamentos-yoda)

![Mestre Yoda - Star Wars](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1hzw8lwkeikntta0di5.gif)

Vamos usar o _framework_ [Serverles](https://www.serverless.com) para nos ajudar nessa missão.
Com ele vamos conseguir atualizar e fazer o _deploy_ da _lambda_ que será executada sempre que falarmos com a Alexa.

### Ingredientes

- Nodejs 12.x
- NPM
- [AWS account](https://aws.amazon.com/pt/console/)
- Alexa Echo Dot
- [Alexa developer Amazon account](https://developer.amazon.com)

Com todos os ingredientes no balcão e as contas devidamente criadas vamos ao modo de preparo.

### Criando o projeto

Vamos começar fazendo o _download_ das ferramentas que vamos utilizar.

```console
$ npm install -g serverless
```

Depois de instalado vamos criar uma pasta e rodar o seguinte comando.

```console
$ serverless create --name ensinamentos-yoda --template aws-nodejs
```

> Vale lembrar que o serverless utiliza as configurações e o usuário configurado no [AWS CLI](https://aws.amazon.com/pt/cli/), caso não tenha baixado é super fácil existem vários exemplos na documentação oficial ou na nossa comunidade.

O _framework_ Serverless tem uma variedade de _plugins_ para nos ajudar durante o desenvolvimento, nessa missão vamos precisar do [serverless-alexa-skills](https://www.serverless.com/plugins/serverless-alexa-skills).

```console
$ serverless plugin install --name serverless-alexa-skills
```

**Pronto!** temos nosso projeto, repare na estrutura criada, temos um arquivo chamado `serverless.yml` e outro `handler.js` esses carinhas merecem uma atenção especial por que são os responsáveis por toda a execução da nossa _lambda_.

**handler.js** - contém a função _main_ que será chamada sempre que a _lambda_ for disparada. 

**serverless.yml** - arquivo de configuração responsável por toda estrutura, _plugins_, _deploy_ e publicação da _lambda_.

Para ver a _lambda_ funcionando em ambiente local é só executar o comando a seguir na pasta do projeto.

```
$ serverless invoke local -f {$FUNCTION_NAME}
```

> Normalmente a função criada por padrão tem o nome de `hello`, mas durante o projeto vamos precisar renomear.


Com a função criada vamos adicionar mais alguns detalhes para conseguirmos acessar a SDK. 🚀

Usando o *NPM* conseguimos instalar a _lib_ da SDK que vamos utilizar para programar a Alexa. Na mesma pasta execute `npm install ask-sdk-core`. 

### serverless.yml

Vamos especificar algumas configurações logo de início.

```yml
service: ensinamentos-yoda #nome do projeo
frameworkVersion: "3"

provider:
  name: aws
  runtime: nodejs12.x 
  stage: dev 
  region: us-east-2

functions:
  yoda:
    handler: handler.yoda #nome da função que será disparada

plugins:
  - serverless-alexa-skills

custom:
  alexa: []

```
### handler.js

```javascript
'use strict';

const ensinamentos = [
  'Muito a aprender você ainda tem, jovem Padawan.',
  'Faça. Ou não faça. Não existe a tentativa',
  'O medo leva a raiva, a raiva leva ao ódio, o ódio leva ao sofrimento',
  'Paciência você deve ter meu jovem Padawan',
  'Um Jedi usa a Força para o conhecimento e defesa, nunca para o ataque.',
  'Raiva, medo, agressão. Ao lado sombrio elas pertencem'
]

module.exports.yoda = async (event) => {
  console.log(`[INFO] ${Date(Date.now())} Lambda disparada `)

  return 'Lembre-se do que o mestre Yoda disse: ' +
    ensinamentos[Math.floor(Math.random() * ensinamentos.length)]
};

```

> Durante o desenvolvimento podemos usar o comando `serverless invoke local -f yoda` para testar localmente sem provisionamento de cloud


![mestre Yoda dançando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tg08b0733ukp0bshfjp1.gif)

## Round 1 
**Primeira etapa concluída!** temos nossa regra de negócio pronta, sempre que executada a _lambda_ responde com algum ensinamento aleatório da lista.

``` console
$ serverless invoke local -f yoda
[INFO] Sun Jun 26 2022 01:48:21 GMT-0300 (Horário Padrão de Brasília) Lambda disparada 
"Lembre-se do que o mestre Yoda disse: Muito a aprender você ainda tem, jovem Padawan."

```
## Round 2 - Criando nossa skill

Vamos nos autenticar com a API da Alexa, nosso amigo serverless nos ajudará muito nessa missão.

```console
$ serverless alexa auth
```
Após ser redirecionado para a página do _console_ vamos fazer o _login_ com nossa conta Amazon, de preferência com a mesma conta que configuramos nosso **Amazon Echo**.

Depois de efetuar o login com sucesso receberemos a mensagem `"Thank you for using Serverless Alexa Skills Plugin!!"`.

Agora autenticados podemos criar nossa _skill_ na plataforma da Amazon.

``` console
$ serverless alexa create --name ensinamentos-yoda --locale pt-BR --type custom
```
Se tudo der certo teremos o ID da nossa _skill_ exibido no console ao fim da execução.

Acessando a página [Amazon Alexa Console](https://developer.amazon.com/alexa/console/ask) conseguimos visualizar nossa _skill_ criada e prontinha para o show. 😎


![screenshot exibindo o painel do console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rvae6vouv50kwgtnba7d.png)

Fique à vontade para explorar o painel, repare que também podemos editar e configurar nossa _skill_ pela plataforma, mas vamos seguir usando o _plugin_ do serverless para o tutorial.

Com a _skill_ criada vamos adicionar mais algumas configurações no `serverless.yml`.

```yml
#---resto do código---
functions:
  yoda:
    handler: handler.yoda #nome da função que será disparada

plugins:
  - serverless-alexa-skills #plugin alexa skill

custom:
  alexa:
    skills:
      - id: ${env:ALEXA_SKILL_ID} #ID informado no comando create
        manifest:
          apis:
            custom: {}
          publishingInformation:
            locales:
              pt-BR:
                name: ensinamentos-yoda
          manifestVersion: '1.0'
        models:
          pt-BR:
            interactionModel:
              languageModel:
                invocationName: ensinamentos yoda #Nome usado para invocar a skill 'Alexa, abrir ensinamentos Yoda'
                intents: 
                  - name: AMAZON.CancelIntent
                    samples: []
                  - name: AMAZON.HelpIntent
                    samples: []
                  - name: AMAZON.StopIntent
                    samples: []
                  - name: PedirEnsinamentoIntent #Intenção que criaremos para executar nossa regra
                    samples: #Frases que usaremos para pedir um ensinamento, Seja Criativo!
                      - 'O que o yoda diria'
                      - 'me fale um ensinamento'
                      - 'qual o pensamento do dia'

```
> Lembra das _intent_ que comentamos lá em cima? A API por padrão define algumas padrões para qualquer _skill_: **AMAZON.CancelIntent**, **AMAZON.HelpIntent**, **AMAZON.StopIntent** 

Depois de editado o arquivo vamos executar mais dois comandinhos, eles serão nossos companheiros durante todo o desenvolvimento.

```console
$ serverless alexa update
$ serverless alexa build
```
Sempre que um comando é enviado para a API da Alexa precisaremos de um `handler` próprio para resolve-lo.

Para esse projeto teremos os seguintes _handlers_:

- `LaunchRequestHandler` Quando o usuário invoca sua _skill_ sem uma intenção específica, a Alexa envia para sua _skill_ um _payload_ de LaunchRequest.
- `PedirEnsinamentoIntent` Executada sempre que o usuário pedir um ensinamento do mestre Yoda.
- `HelpIntentHandler` Executada sempre que o usuário enviar um `AMAZON.HelpIntent`.
- `CancelAndStopIntentHandler` Executada sempre que o usuário enviar um `AMAZON.CancelIntent` ou `AMAZON.StopIntent`.
- `SessionEndedRequestHandler` Executada sempre que o usuário desejar encerrar a _skill_


``` javascript
'use strict';
const Alexa = require('ask-sdk-core')

const ensinamento = () => {
  const ensinamentos = [
    'Muito a aprender você ainda tem, jovem Padawan.',
    'Faça. Ou não faça. Não existe a tentativa',
    'O medo leva a raiva, a raiva leva ao ódio, o ódio leva ao sofrimento',
    'Paciência você deve ter meu jovem Padawan',
    'Um Jedi usa a Força para o conhecimento e defesa, nunca para o ataque.',
    'Raiva, medo, agressão. Ao lado sombrio elas pertencem'
  ]

  return 'Lembre-se do que o mestre Yoda disse: ' +
    ensinamentos[Math.floor(Math.random() * ensinamentos.length)]
}

const LaunchRequestHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'LaunchRequest'
  },
  handle(handlerInput) {
    const speechText = 'Olá, que a força esteja com você, me peça um ensinamento do mestre Yoda'

    return handlerInput.responseBuilder
      .speak(speechText)
      .reprompt(speechText)
      .withSimpleCard('Olá, que a força esteja com você, me peça um ensinamento do mestre Yoda', speechText)
      .getResponse()
  }
}


const PedirEnsinamentoIntent = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && Alexa.getIntentName(handlerInput.requestEnvelope) === 'PedirEnsinamentoIntent'
  },
  async handle(handlerInput) {

    const speechText = ensinamento() //chamada a nossa função

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Mestre Yoda.', speechText)
      .getResponse()
  }
}

const CancelAndStopIntentHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'IntentRequest'
      && (Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.CancelIntent'
        || Alexa.getIntentName(handlerInput.requestEnvelope) === 'AMAZON.StopIntent')
  },
  handle(handlerInput) {
    const speechText = 'Que a força esteja com você!'

    return handlerInput.responseBuilder
      .speak(speechText)
      .withSimpleCard('Que a força esteja com você!', speechText)
      .withShouldEndSession(true)
      .getResponse()
  }
}

const SessionEndedRequestHandler = {
  canHandle(handlerInput) {
    return Alexa.getRequestType(handlerInput.requestEnvelope) === 'SessionEndedRequest'
  },
  handle(handlerInput) {
    // Any clean-up logic goes here.
    return handlerInput.responseBuilder.getResponse()
  }
}


const ErrorHandler = {
  canHandle() {
    return true;
  },
  handle(handlerInput, error) {
    console.log(`Error handled: ${error.message}`);

    return handlerInput.responseBuilder
      .speak('Encontrei um probleminha, vamos tentar de novo ?')
      .reprompt('Encontrei um probleminha, vamos tentar de novo ?')
      .getResponse();
  }
}


let skill

module.exports.yoda = async (event) => {
  console.log(`[INFO] ${Date(Date.now())} Lambda disparada `)

  if (!skill) {
    skill = Alexa.SkillBuilders.custom()
      .addRequestHandlers(
        LaunchRequestHandler, //responsável por abrir nossa skill
        PedirEnsinamentoIntent,
        CancelAndStopIntentHandler,
        SessionEndedRequestHandler //responsável para encerrar nossa skill
      )
      .addErrorHandlers(ErrorHandler)//mensagem para caso de erros
      .create()
  }

  return await skill.invoke(event)
}

```
Rolou muitas mudanças dessa vez, recomendo fortemente dar uma olhada na [documentação oficial](https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/set-up-the-sdk.html) onde tem vários exemplos e explicações sobre as etapas e funções desse código.

Nesse ponto da nossa aventura temos uma _lambda_ capaz de receber um comando de voz da Alexa e responder com um ensinamento do mestre Yoda.

Não esqueça dos nossos amigos.

```console
$ serverless alexa update
$ serverless alexa build
```
![Todo poderoso tomando café enquanto usa o computador](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bin5czgx9yfs9c9631qy.gif)

### Estamos quase lá 

Já temos nosso código e nossa _skill_ agora vamos fazer todas as peças se encaixarem?

Partiu!

## Deploy

O nosso amigo serverless também nos ajudará na etapa do _deploy_, vamos precisar publicar nossa _lambda_ para _cloud_ e adicionar o gatilho de execução pela Alexa.

```console
$ serverless deploy
```
**Hora da mágica!🧙‍♂️** esse comando vai executar todas as etapas necessária de publicação e os recursos necessários para termos o serviço na nossa conta AWS.

![painel AWS Lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u4lwn0dyy67tqb2wemuw.png)

**Prontinho!** temos nosso _backend_ agora só precisamos adicionar um gatilho para a função.

1. Clique em **Visão geral da função**.
2. Clique em **Adicionar gatilho**.
3. Selecione **Alexa**.
4. Para **Skill ID verification**, selecione **Disable**.

![detalhes da função no painel AWS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6o9532sekirj0dvjb1j.png)

Depois de criada vamos editar mais uma vez o arquivo `serverless.yml` e adicionar o **ARN** da função para ser chamada pela Alexa.

```yml
#---resto do código---
 apis:
  custom: 
   endpoint:
    uri: ${env:AWS_LAMBDA_ARN}
#---resto do código---

```
> Lembre-se de atualizar e fazer o _build_ da _skill_ mais uma vez.

##Testando

**Essa jornada foi longa mas chegamos ao final.**

Para testarmos nossa _skill_ podemos usar o aplicativo, Amazon Echo ou a página de teste do Alexa Developer Console.

###Usando o Amazon Echo Dot

1. Abra o aplicativo **Amazon Alexa**.
2. No menu **Mais** acesse **Skills & Games**.
3. Selecione a aba **Suas Skills**.
4. Clique no card **Desenv.**.
5. Clique em **ensinamentos-yoda**.
6. Clique em **Ativar Skill**.
7. Diga **"Alexa abrir ensinamentos yoda"**
8. Diga alguma das frases que configuramos, por exemplo **"O que o Yoda diria"**

![Michael Scott dançando](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oshr0oqxvdqalgj8en5y.gif)

##Conclusão

Desenvolver com a Alexa é uma aventura muito divertida, existem inúmeras opções e funcionalidades que podemos explorar.

O objetivo desse tutorial foi tentar mostrar os primeiros passos para conseguir usar a API da Amazon e sua integração com o serviço AWS Lambda.

Espero que este post tenha inspirado você a explorar o Alexa Skill Kit e criar algo para ela.😊

Recomendo a leitura das documentações oficiais e os links que deixarei abaixo para definições mais técnicas e detalhadas.


##Links

- [Projeto pessoal criando uma skill que diz em que plataforma de streaming filmes e séries estão disponíveis](https://github.com/pgsilva/function-moovies)
- [Tutorial oficial da API Amazon](https://developer.amazon.com/en-US/docs/alexa/alexa-skills-kit-sdk-for-nodejs/develop-your-first-skill.html)
- [Blog serverless](https://www.serverless.com/blog/how-to-manage-your-alexa-skills-with-serverless/)


![isso é tudo pessoal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mupsj3eodpc2rxfgawz8.gif)

Happy Coding!