Arquitetura e modelo de interações da “Skill” Alexa que reproduz os Drops da EximiaCo

Implementar um skill para Amazon Alexa é uma atividade relativamente simples. Tudo começa com o desenvolvimento de um modelo de interações, conforme conceitos que destacamos no post anterior. Na skill da EximiaCo, construímos este modelo diretamente no console de desenvolvimento da Amazon Alexa. Entretanto, também seria possível utilizar o CLI do SDK da Alexa, o ASK, para criar e publicar a skill.

Para o backend, há duas opções de endpoint  disponíveis: AWS Lambda (recomendada) e HTTPS. Optamos por desenvolver uma Lambda em Python utilizando o AWS SAM.

Em conjunto com a Lambda incluímos os demais recursos de infraestrutura existentes na arquitetura da skill, como tabelas do DynamoDB, um tópico do SNS, e outra Lambda que possui a função de Crawler, extraindo os dados dos episódios do feed do SoundCloud.  Essa arquitetura permite que, de forma simples, possamos publicar a aplicação em qualquer conta da AWS, com apenas alguns comandos (IaC).

A construção do handler da Lambda é feita selecionando um dos Builders (Pattern) fornecidos pelo ASK. Cada builder tem características e suportes diferentes. Em nossa skill utilizamos o StandardSkillBuilder, que possui suporte a armazenamento persistente de estado utilizando uma tabela no DynamoDB. Desta forma, tivemos a possibilidade de armazenar o estado do playback, permitindo que a reprodução continuasse de onde parou, quando o usuário retomasse a reprodução.

import os

from ask_sdk.standard import StandardSkillBuilder

from command_handlers import (LaunchRequestHandler, StartLatestEpisodeHandler, StopEpisodeHandler, SearchEpisodeHandler,
                              ShuffleOnEpisodeHandler, ShuffleOffEpisodeHandler, StartOverEpisodeHandler,
                              RepeatEpisodeHandler,ResumeEpisodeHandler, HelpHandler, FallbackHandler, SessionEndedRequestHandler,
                              PreviousEpisodeHandler, NextEpisodeHandler, LoopOnEpisodeHandler, LoopOffEpisodeHandler)
from event_handlers import (PlaybackStoppedHandler, PlaybackFinishedHandler, PlaybackStartedHandler,
                            PlaybackFailedHandler, PlaybackNearlyFinishedHandler)
from interceptors import (SaveStateResponseInterceptor, LoadStateRequestInterceptor, RequestLogger, ResponseLogger,
                          CatchAllExceptionHandler)

streaming_table_name = os.getenv("STREAMING_TABLE_NAME")
skill_builder = StandardSkillBuilder(
    table_name=streaming_table_name,
    auto_create_table=False)

# Launch Request
skill_builder.add_request_handler(LaunchRequestHandler())

# Command Handlers
skill_builder.add_request_handler(StartLatestEpisodeHandler())
skill_builder.add_request_handler(SessionEndedRequestHandler())
skill_builder.add_request_handler(StopEpisodeHandler())
skill_builder.add_request_handler(ResumeEpisodeHandler())
skill_builder.add_request_handler(SearchEpisodeHandler())
skill_builder.add_request_handler(ShuffleOnEpisodeHandler())
skill_builder.add_request_handler(ShuffleOffEpisodeHandler())
skill_builder.add_request_handler(StartOverEpisodeHandler())
skill_builder.add_request_handler(RepeatEpisodeHandler())
skill_builder.add_request_handler(PreviousEpisodeHandler())
skill_builder.add_request_handler(NextEpisodeHandler())
skill_builder.add_request_handler(LoopOnEpisodeHandler())
skill_builder.add_request_handler(LoopOffEpisodeHandler())
skill_builder.add_request_handler(HelpHandler())
skill_builder.add_request_handler(FallbackHandler())

# Event Handlers
skill_builder.add_request_handler(PlaybackNearlyFinishedHandler())
skill_builder.add_request_handler(PlaybackFailedHandler())
skill_builder.add_request_handler(PlaybackStartedHandler())
skill_builder.add_request_handler(PlaybackStoppedHandler())
skill_builder.add_request_handler(PlaybackFinishedHandler())

# Request Interceptors
skill_builder.add_global_request_interceptor(RequestLogger())
skill_builder.add_global_request_interceptor(LoadStateRequestInterceptor())

# Response Interceptors
skill_builder.add_global_response_interceptor(ResponseLogger())
skill_builder.add_global_response_interceptor(SaveStateResponseInterceptor())

# Exception Handler
skill_builder.add_exception_handler(CatchAllExceptionHandler())

lambda_handler = skill_builder.lambda_handler()

A invocação dos intents é tratada no backend através de  RequestHandlers. Quando a skill é ativada com o comando de invocação, é enviada uma requisição para o backend com o tipo LaunchRequest. A boa prática é que, no handler encarregado por esta requisição, a skill responda ao usuário uma mensagem de boas-vindas, e instruções de utilização da skill.

class LaunchRequestHandler(AbstractRequestHandler):
    def can_handle(self, handler_input):
        return ask_utils.is_request_type("LaunchRequest")(handler_input)

    def handle(self, handler_input):

        player = Player(handler_input)
        player.reset()

        first_time = handler_input.attributes_manager.persistent_attributes.get("playback").get("firstTime")

        if first_time:
            speak_out = 'Olá, seja bem-vindo aos Drops da Exímia<emphasis level="strong">Cô</emphasis>. ' 
                        'Para começar você pode escolher o episódio mais recente dizendo: O mais recente, ' 
                        'ou ainda pesquisar sobre algum tema específico iniciando a frase com:  ' 
                        'O episódio sobre. <break time="500ms"/> ' 
                        'Para mais informações, basta me pedir Ajuda. ' 
                        '<break time="500ms"/>Qual episódio você gostaria de ouvir?'
        else:
            speak_out = 'Olá, seja bem-vindo de volta aos Drops da Exímia<emphasis level="strong">Cô</emphasis>. ' 
                        'Para qualquer dúvida, basta me pedir Ajuda. ' 
                        '<break time="500ms"/>Qual episódio você gostaria de ouvir?'

        handler_input.response_builder.speak(speak_out).ask(speak_out)

        return handler_input.response_builder.response

Outros elementos importantes existentes no ASK são os RequestInterceptors e os ResponseInterceptors.  Eles interceptam momentos antes e depois da execução dos handlers, respectivamente, possibilitando qualquer pré-processamento ou pós-processamento da requisição. Em nosso cenário, utilizamos estes interceptors para logging,  para carregar o estado persistente de execução do playback antes, e salvar as suas atualizações depois da invocação.

class SaveStateResponseInterceptor(AbstractResponseInterceptor):

    def process(self, handler_input, response):
        try:
            handler_input.attributes_manager.save_persistent_attributes()
        except:
            print("Ocorreu um erro ao persistir os dados da sessão")

Não menos importante, o ASK oferece um handler específico para capturar exceções não tratadas durante a execução da skill. É extremamente importante que a skill retorne sempre uma mensagem amigável ao usuário e que o problema seja tratado.

class CatchAllExceptionHandler(AbstractExceptionHandler):
    def can_handle(self, handler_input, exception):
        return True

    def handle(self, handler_input, exception):
        logger.error(exception, exc_info=True)
        notify_error(exception)

        speech = "Desculpe. Tive um problema para obter os dados do drops. Tente novamente mais tarde!"
        handler_input.response_builder.speak(speech)
        # .ask(speech)

        return handler_input.response_builder.response

Nos próximos posts da série, serão apresentadas as funções do player,  crawler, além do processo de publicação da skill. Para aqueles que gostariam de conhecer ou contribuir com o projeto, o código será disponibilizado publicamente no Github em breve. Por fim, para aqueles que não conhecem o nosso Drops,  ele está disponível nas principais plataformas de podcast do mercado, e agora também na Alexa!

Compartilhe este insight:

Comentários

Participe deixando seu comentário sobre este artigo a seguir:

Subscribe
Notify of
guest
0 Comentários
Inline Feedbacks
View all comments

AUTOR

Douglas Picolotto
Mais de 15 anos de experiência como Engenheiro de nuvem, arquiteto de software, desenvolvedor e especialista em AWS e DevOps.

SOLUÇÕES EXIMIACO

ESTRATÉGIA & EXECUÇÃO EM TI

Simplificamos, potencializamos 
aceleramos resultados usando a tecnologia do jeito certo.

COMO PODEMOS LHE AJUDAR?

Vamos marcar uma conversa para que possamos entender melhor sua situação e juntos avaliar de que forma a tecnologia pode trazer mais resultados para o seu negócio.

COMO PODEMOS LHE AJUDAR?

Vamos marcar uma conversa para que possamos entender melhor sua situação e juntos avaliar de que forma a tecnologia pode trazer mais resultados para o seu negócio.

+55 51 3049-7890 |  contato@eximia.co

0
Queremos saber a sua opinião, deixe seu comentáriox
()
x

Tenho interesse em conversar

Se você está querendo gerar resultados através da tecnologia, preencha este formulário que um de nossos consultores entrará em contato com você:

O seu insight foi excluído com sucesso!

O seu insight foi excluído e não está mais disponível.

O seu insight foi salvo com sucesso!

Ele está na fila de espera, aguardando ser revisado para ter sua publicação programada.