
Criação de um aplicativo de paging sem servidor com o Amazon Transcribe
Tempo de leitura: 9 minutos
Os anos 90 trouxeram algumas tecnologias incríveis: o MiniDisc, o Tamagotchi e os PAGERS!
Tudo bem, em muitos aspectos nossos smartphones trouxeram avanços significativos nos últimos 20 anos, mas há uma certa nostalgia nos pagers.
E, como sabe qualquer pessoa que já esteve de plantão e foi acordada às 2 da manhã por um telefonema pedindo ajuda freneticamente, os pagers tinham uma vantagem que perdemos com os telefones: você recebia a mensagem e depois ligava de volta para a pessoa.
Para quem é jovem demais para se lembrar dos pagers, você ligava para o número do pager da pessoa e a ligação era atendida por um operador, que anotava a mensagem, digitava-a no console e, momentos depois, a mensagem aparecia na tela do seu pager.
Neste tutorial, vou mostrar a vocês como recriar esse serviço de mensagens usando o Nexmo e a AWS.
A IA evoluiu bastante nos últimos tempos e agora podemos substituir o dispendioso operador de central de paging por uma API; para este exemplo, vou usar o Amazon Transcribe. Usaremos o Nexmo para receber a chamada, atendê-la e gravar uma mensagem; em seguida, enviaremos essa gravação para o Transcribe e, quando recebermos o texto, o enviaremos para o seu celular usando o SMS do Nexmo.
Para integrar as partes, vamos criar um aplicativo em Python usando o framework Chalice, o que nos permitirá, em seguida, implantar e executar tudo no AWS Lambda e no S3.
As vantagens dessa arquitetura são que nossos custos de hospedagem serão muito baixos. Na verdade, se você estiver utilizando isso apenas para uso pessoal, provavelmente ficará dentro dos planos gratuitos da AWS e, caso queira expandir o projeto, a pilha sem servidor será capaz de se adaptar a volumes enormes.
Pré-requisitos
Para este tutorial, você precisará do seguinte:
Um Account na AWS (você pode executar isso no plano gratuito)
A ferramenta AWS CLI e Chalice devem estar instalados e configurados no seu computador
Um account Nexmo com a ferramenta CLI da Nexmo instalada e configurada
Configuração
Antes de implementarmos nossa funcionalidade, precisamos fazer algumas configurações.
Aplicativo Nexmo Voice
Precisamos criar um novo aplicativo de voz, seja no painel do Nexmo, seja usando a ferramenta de linha de comando
nexmo app:create “Paging Service” http://example.com/answer http://example.com/event --keyfile private.key
Anote o ID do aplicativo que for retornado; isso também salvará a chave privada em um arquivo.
Por enquanto, vamos usar valores fictícios para os webhooks; ou, se você quiser testar localmente, pode usar o ngrok como seu host.
Bucket do S3
Também precisamos criar um bucket do S3 para armazenar as gravações destinadas à transcrição; podemos fazer isso usando a CLI da AWS. Criaremos nossos recursos na região us-east-1 da AWS.
aws s3api create-bucket --bucket pagingservice --region us-east-1
Inscrição para o Chalice
Primeiramente, vamos criar um novo projeto no Chalice:
chalice new-project
Quando for solicitado, dê um nome ao seu projeto, por exemplo: paging-service. Agora você terá um modelo básico para um aplicativo Chalice criado na pasta com o nome do seu projeto.
Nessa pasta há três arquivos importantes:
app.py: o código principal do aplicativorequirements.txt: lista os módulos do Python que você está usando.chalice\config.json: contém várias configurações relacionadas ao seu projeto
Importar módulos
O modelo já terá importado o módulo chalice. Também precisamos do módulo boto3 para nos conectarmos ao S3 e ao Transcribe, e do módulo Nexmo para buscar a gravação e enviar o SMS. Além disso, importamos o módulo os para acessar as variáveis de ambiente.
import boto3
import nexmo
import json
import osTambém precisamos adicionar o boto3 e o nexmo ao requirements.txt arquivo para que o Lambda saiba que deve instalá-los quando a aplicação for implantada.
Variáveis de configuração
APPLICATION_ID = os.environ['APPLICATION_ID']
API_KEY = os.environ['API_KEY']
API_SECRET = os.environ['API_SECRET']
NAME = os.environ['NAME']
NUMBER = os.environ['NUMBER']
NEXMO_NUMBER = os.environ['NEXMO_NUMBER']
S3_BUCKET = 'pagingdemo'Estamos definindo a maioria das nossas variáveis a partir de variáveis de ambiente; você pode ver como defini-las com o Chalice no arquivo README no GitHub; como alternativa, você pode simplesmente definir seus próprios valores aqui.
Inicializar clientes
Vamos nos conectar a três serviços externos como parte do nosso aplicativo: AWS S3, Amazon Transcribe e Nexmo. Criaremos essas conexões aqui:
S3 = boto3.client('s3')
TRANSCRIBE = boto3.client('transcribe')
NEXMO = nexmo.Client(
key=API_KEY,
secret=API_SECRET,
application_id=APPLICATION_ID,
private_key='chalicelib/private.key',
)Não precisamos fornecer credenciais para os serviços da Amazon, pois o boto e o chalice farão isso automaticamente quando fizermos a implantação no Lambda.
Escrevendo o código do manipulador
Nesta aplicação, há três etapas pelas quais precisamos passar para transcrever uma mensagem de voz; elas se alinham perfeitamente com três manipuladores distintos no código da nossa aplicação.
O primeiro é um manipulador de webhook para responder à solicitação de chamada recebida da Nexmo e retornar um NCCO (Nexmo Call Control Object), que é uma lista de ações a serem realizadas na chamada, representada como um objeto JSON.
@app.route('/answer')
def answer():
req = app.current_request.to_dict()
ncco =[
{
'action': 'talk',
'text': "Welcome to {}s messaging service, please leave a short message after the tone".format(NAME),
},
{
'action': 'record',
'endOnSilence': 3,
'endOnKey': '#',
'beepStart' : True,
'eventUrl' : [req['headers']['x-forwarded-proto'] + "://" + req['headers']['host'] + "/api/recording?from=" +req['query_params']['from']]
},
{
'action': 'talk',
'text': "thankyou, your message has been forwarded"
}
]
return nccoO @app.route decorador define o caminho ao qual esse manipulador responderá. Convertemos os parâmetros da solicitação recebida em um req objeto de dicionário, pois vamos querer usar alguns desses dados mais tarde. Em seguida, criamos nossa resposta NCCO.
A primeira ação é a talk que é a saudação inicial que os chamadores ouvem; estamos inserindo o parâmetro NAME aqui para personalizar a saudação.
Em seguida, temos uma record ação, que é onde capturamos a mensagem do autor da chamada. Eu configurei endOnSilence três segundos para que, quando a pessoa terminar de falar, o sistema siga em frente ou ela possa usar a tecla endonKey para pressionar #. beepStart está definido como True para que o chamador saiba quando começar a falar.
O eventUrl parâmetro parece um pouco complicado, mas tudo o que estou fazendo aqui é construir a URL, que terá o mesmo host e protocolo do webhook de entrada usado no API Gateway, para que não precisemos codificá-los explicitamente. O caminho é /api/recording, onde api é o padrão para o API Gateway. Por fim, estamos adicionando o from como um parâmetro de consulta para que, quando o webhook de gravação chegar, saibamos o ID original do chamador (já que o Nexmo não passa essa informação por padrão em eventos de gravação).
Encerramos nosso NCCO com uma ação simples talk ação, para que o chamador saiba que sua mensagem foi capturada e possa desligar. Se ele desligar enquanto a gravação ainda estiver ativa, a mensagem ainda será entregue.
Em nosso próximo handler, receberemos o evento de gravação proveniente da Nexmo, buscaremos e armazenaremos a gravação no S3 e, em seguida, iniciaremos a ação de transcrição.
@app.route('/recording', methods=['POST'])
def recording():
qparams= app.current_request.query_params
data = app.current_request.json_body
recfile = NEXMO.get_recording(data['recording_url'])
S3.put_object(
Bucket=S3_BUCKET,
Key=data['conversation_uuid']+".mp3",
Body=recfile,
ContentType='audio/mp3',
Metadata={
'callerid': qparams['from'],
'time' : data['end_time']
}
)
response = TRANSCRIBE.start_transcription_job(
TranscriptionJobName=data['conversation_uuid'],
LanguageCode='en-GB',
MediaFormat='mp3',
Media={
'MediaFileUri': 'https://s3.amazonaws.com/{}/{}'.format(S3_BUCKET, data['conversation_uuid']+".mp3")
},
OutputBucketName=S3_BUCKET,
)
return "ok"Temos o mesmo @app.route decorador; no entanto, também estamos especificando que ele irá lidar com uma solicitação POST (o padrão do Chalice é GET).
Estamos extraindo os parâmetros da string de consulta onde passamos os from detalhes em um dicionário chamado qparams e, em seguida, colocamos o corpo JSON do webhook em um objeto chamado data.
Usaremos o NEXMO objeto que criamos como conexão com o Nexmo para buscar a gravação e, em seguida, armazená-la no bucket do S3 usando o UUID da conversa como nossa chave. Também definiremos alguns metadados para o objeto, a saber, o ID original do chamador (from) e a hora da gravação.
Por fim, iniciamos uma tarefa de transcrição indicando nossa nova gravação no S3. Precisamos fornecer um nome para a tarefa (novamente, usaremos o UUID da conversa), o idioma do áudio (neste caso, inglês britânico), o formato de mídia e o URI do arquivo no S3. Esse formato dependerá da região em que você criou seu bucket; o exemplo aqui é para us-east-1. Por fim, especificamos o bucket de saída onde a transcrição resultante deve ser gravada: estamos usando o mesmo bucket das gravações.
Para o handler final, estamos acionando de uma maneira um pouco diferente; desta vez, não é um webhook, mas a chegada do resultado da transcrição em nosso bucket do S3 que aciona nosso código.
@app.on_s3_event(bucket=S3_BUCKET, events=['s3:ObjectCreated:*'], suffix='.json')
def transcribed(event):
# Get transcription from S3
obj = S3.get_object( Bucket=S3_BUCKET, Key=event.key)
data = json.loads(obj['Body'].read())
# Make recording public
S3.put_object_acl(ACL='public-read', Bucket=S3_BUCKET, Key= data['jobName']+".mp3")
#Build SMS
text = data['results']['transcripts'][0]['transcript'].upper()
obj = S3.get_object( Bucket=S3_BUCKET, Key=data['jobName']+".mp3")
callerid = obj['ResponseMetadata']['HTTPHeaders']['x-amz-meta-callerid']
url = 'https://s3.amazonaws.com/{}/{}'.format(S3_BUCKET, data['jobName']+".mp3")
message = "[From: +{}]\n\n{}\n\n{}".format(callerid, text, url)
#Send SMS
NEXMO.send_message({'from': NEXMO_NUMBER, 'to': NUMBER, 'text': message})Você vai notar que o decorador tem um formato diferente: on_s3_event. Também estamos especificando o bucket de nosso interesse, o tipo de evento — quando um novo objeto é criado — e o sufixo desses objetos como JSON, para que o evento não seja acionado quando os objetos de gravação .mp3 são adicionados ao bucket.
Em seguida, buscamos o novo objeto, que é uma resposta JSON da nossa transcrição, e o armazenamos em data. Tornamos o arquivo de gravação em MP3 publicamente acessível e, em seguida, começamos a criar nossa mensagem de notificação. Gosto de ver o texto da transcrição em MAIÚSCULAS, pois isso lembra um pouco mais o serviço retrô de pager. Também estamos adicionando o identificador de chamada original no início da mensagem e, por fim, estamos adicionando a URL da gravação de áudio no final da mensagem, para o caso de a transcrição não estar perfeita e você querer ouvir o que a pessoa que ligou disse originalmente.
Por fim, enviamos a mensagem SMS usando o objeto cliente NEXMO criado anteriormente.
Implantação
Agora que criamos o aplicativo, basta implantá-lo na AWS executando o seguinte comando:
chalice deploy
Isso cria uma função Lambda e configura as regras do API Gateway. Além disso, cria os eventos do bucket do S3 e configura automaticamente as políticas de segurança do IAM associadas para nós.
Você deverá então obter um resultado que contenha a URL do API Gateway, por exemplo:
Rest API URL: https://3u9ucalu05.execute-api.us-east-1.amazonaws.com/api/
Usando essa URL como base, atualize seu aplicativo Nexmo para configurar o webhook de resposta de forma que ele aponte para o seu aplicativo implantado. Para isso, você precisará do ID do seu aplicativo:
Por fim, certifique-se de que seu número Nexmo esteja vinculado ao aplicativo e, em seguida, ligue para ele. Você deverá ouvir sua saudação e, em seguida, poderá deixar uma mensagem. Logo em seguida, você receberá uma mensagem de texto com a transcrição e um link para o arquivo de áudio:
text message
Próximos passos
Existem várias maneiras de expandir esse aplicativo. Algumas ideias que você poderia considerar são adicionar suporte para vários usuários, criando um mapeamento entre o número de origem e o número de notificação e a mensagem de saudação, ou substituir a notificação por SMS por e-mail, caso isso se adapte melhor ao seu caso de uso.
Além disso, os arquivos MP3 e de transcrição ficarão armazenados permanentemente no seu bucket do S3; portanto, talvez seja interessante procurar uma maneira de removê-los ou definir uma data de validade para eles após um determinado período.
Também vale a pena mencionar que o serviço Amazon Transcribe não é o mais rápido — especialmente para trechos curtos de áudio. Nos meus testes, observei um atraso de 1 a 2 minutos na transcrição de uma mensagem de voz curta; portanto, tenha isso em mente se estiver pensando em usá-lo para notificações em que o tempo é essencial.
Você pode encontrar todo o código-fonte do aplicativo, juntamente com a configuração do Chalice, no repositório do GitHub.