https://d226lax1qjow5r.cloudfront.net/blog/blogposts/serverless-paging-amazon-transcribe-dr/Pager-Hack-the-Planet.png

Construire une application de pagination sans serveur avec Amazon Transcribe.

Publié le May 4, 2021

Temps de lecture : 9 minutes

Les années 90 ont été marquées par de grandes technologies : MiniDisc, Tamagotchi, et PAGERS !

D'accord, pour la plupart des choses, nos smartphones ont beaucoup évolué au cours des 20 dernières années, mais les pagers suscitent une certaine nostalgie.

Et pour tous ceux qui ont été de garde et ont été réveillés à 2 heures du matin par un appel téléphonique demandant frénétiquement de l'aide, les pagers avaient un avantage que nous avons perdu avec les téléphones : on recevait le message puis on rappelait la personne.

Pour ceux qui sont trop jeunes pour se souvenir des pagers, vous appeliez le numéro de pager de la personne et l'appel était pris par un opérateur qui prenait alors un message, le tapait dans sa console et le message apparaissait sur l'écran de votre pager quelques instants plus tard.

Dans ce tutoriel, je vais vous montrer comment recréer ce service de messagerie en utilisant Nexmo et AWS.

L'IA a beaucoup évolué ces derniers temps et nous pouvons désormais remplacer le coûteux opérateur de radiomessagerie par une API ; pour cet exemple, je vais utiliser Amazon Transcribe. Nous utiliserons Nexmo pour recevoir l'appel entrant, y répondre et enregistrer un message, puis nous transmettrons cet enregistrement à Transcribe et lorsque nous aurons récupéré le texte, nous l'enverrons sur votre téléphone à l'aide de Nexmo SMS.

Pour coller les pièces ensemble, nous allons construire une application python en utilisant le framework Chalice qui nous permettra ensuite de déployer et d'exécuter le tout sur AWS Lambda et S3.

Les avantages de cette architecture sont que nos coûts d'hébergement seront très faibles. En fait, si vous l'exécutez juste pour votre usage personnel, vous resterez probablement dans les niveaux gratuits d'AWS et si vous voulez le développer, alors la pile sans serveur s'adaptera à des volumes massifs.

Conditions préalables

Pour ce tutoriel, vous aurez besoin des éléments suivants :

  • Un Account AWS (vous pouvez utiliser le niveau gratuit)

  • L'outil Outil AWS CLI et Chalice installés et configurés sur votre machine

  • Un Account Nexmo avec l'outil outil CLI nexmo installé et configuré

Vonage API Account

To complete this tutorial, you will need a Vonage API account. If you don’t have one already, you can sign up today and start building with free credit. Once you have an account, you can find your API Key and API Secret at the top of the Vonage API Dashboard.

This tutorial also uses a virtual phone number. To purchase one, go to Numbers > Buy Numbers and search for one that meets your needs.

Mise en place

Avant de mettre en œuvre notre fonctionnalité, nous devons procéder à quelques réglages.

Nexmo Voice Applicaiton

Nous devons créer une nouvelle application Voice soit dans le tableau de bord de Nexmo, soit en utilisant l'outil de ligne de commande.

nexmo app:create “Paging Service” http://example.com/answer http://example.com/event --keyfile private.key

Notez l'identifiant de l'application qui est renvoyé ; cela permettra également d'enregistrer la clé privée dans un fichier.

Pour l'instant, nous utiliserons des valeurs fictives pour les webhooks, ou si vous voulez tester localement, vous pouvez utiliser ngrok pour votre hôte.

Seau S3

Nous devons également créer un godet S3 dans lequel nous placerons les enregistrements pour la transcription ; nous pouvons le faire à l'aide de la CLI AWS. Nous créerons nos ressources dans la région AWS us-east-1.

aws s3api create-bucket --bucket pagingservice --region us-east-1

Applications du calice

Tout d'abord, nous allons créer un nouveau projet de calice :

chalice new-project

Lorsque vous y êtes invité, donnez un nom à votre projet, par exemple paging-service. Vous disposez maintenant d'un modèle de base pour une application de calice créée dans le dossier portant le nom de votre projet.

Ce dossier contient trois fichiers importants :

  • app.py: le code principal de l'application

  • requirements.txt: liste les modules python que vous utilisez

  • .chalice\config.jsoncontient divers paramètres relatifs à votre projet

Modules d'importation

Le modèle aura déjà importé le module chalice. Nous avons également besoin du module boto3 pour se connecter à S3 et Transcribe, et du module Nexmo pour récupérer l'enregistrement et envoyer le SMS. Nous importons également le module os pour accéder aux variables d'environnement.

import boto3
import nexmo
import json
import os

Nous devons également ajouter boto3 et nexmo au fichier requirements.txt afin que lambda sache qu'il faut les installer lorsque l'application est déployée.

Variables de configuration

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'

Nous définissons la plupart de nos variables à partir des variables d'environnement, vous pouvez voir comment les définir avec Chalice à partir du README sur github ou vous pouvez simplement définir vos propres valeurs ici.

Initialiser les clients

Nous allons nous connecter à trois services externes dans le cadre de notre application : AWS S3, Amazon Transcribe et Nexmo. Nous allons créer ces connexions ici :

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',
)

Nous n'avons pas besoin de fournir des informations d'identification pour les services Amazon, car boto et chalice le feront automatiquement lors du déploiement vers Lambda.

Écrire le code du gestionnaire

Pour cette application, nous devons passer par trois étapes afin de transcrire un message vocal ; celles-ci s'aligneront parfaitement sur trois gestionnaires distincts dans le code de notre application.

Le premier est un gestionnaire de webhook qui répond à la demande d'appel entrant de Nexmo et renvoie un NCCO (Nexmo Call Control Object), qui est une liste d'actions à effectuer sur l'appel, représentée sous la forme d'un objet 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 ncco

Le décorateur @app.route définit le chemin auquel ce gestionnaire répondra. Nous convertissons les paramètres de la requête entrante en un objet de type req car nous voudrons utiliser certaines de ces données plus tard. Nous créons ensuite notre réponse NCCO.

La première action est le talk qui est le message d'accueil initial que les appelants entendent, nous introduisons ici le paramètre NAME pour personnaliser le message d'accueil.

Nous avons ensuite une record où nous capturons le message de l'appelant. J'ai fixé endOnSilence à trois secondes, de sorte que lorsque l'appelant a fini de parler, le message passe à l'action ou l'appelant peut utiliser la touche endonKey pour appuyer sur #. beepStart est réglée sur True pour que l'appelant sache quand il doit commencer à parler.

Le paramètre eventUrl semble un peu compliqué mais tout ce que je fais ici est de construire l'URL qui sera le même hôte et le même protocole que le webhook entrant utilisé sur la passerelle API, nous n'avons donc pas besoin de le coder en dur. Le chemin est /api/recordingapi est la valeur par défaut de la passerelle API. Enfin, nous ajoutons l'élément from comme paramètre de requête afin que lorsque le webhook d'enregistrement arrive, nous connaissions l'ID original de l'appelant (car nexmo ne le transmet pas en standard dans les événements d'enregistrement).

Nous terminons notre NCCO par une action simple talk afin que l'appelant sache que son message a été enregistré et qu'il peut raccrocher. S'il raccroche alors que l'enregistrement est encore actif, le message sera tout de même délivré.

Dans notre prochain gestionnaire, nous recevrons l'événement d'enregistrement entrant de Nexmo, nous récupérerons et stockerons l'enregistrement dans S3, puis nous lancerons l'action de transcription.

@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"

Nous avons le même décorateur @app.route mais nous spécifions également qu'il gérera une requête POST (chalice utilise par défaut GET).

Nous récupérons les paramètres de la chaîne de requête où nous avons passé les détails from dans un dictionnaire nommé qparams puis nous plaçons le corps JSON du webhook dans un objet appelé data.

Nous utiliserons l'objet NEXMO que nous avons créé comme connexion à Nexmo pour récupérer l'enregistrement, puis le stocker dans le seau S3 en utilisant l'UUID de la conversation comme clé. Nous définissons également quelques métadonnées pour l'objet, à savoir l'identifiant original de l'appelant (from) et l'heure de l'enregistrement.

Enfin, nous lançons une tâche de transcription pointant vers notre nouvel enregistrement dans S3. Nous devons fournir un nom pour la tâche (à nouveau, nous utiliserons l'UUID de la conversation), la langue de l'audio (dans ce cas, l'anglais - britannique), le format du média et l'URI pour le fichier dans S3. Ce format dépendra de la région dans laquelle vous avez créé votre panier, l'exemple ici est pour us-east-1. Enfin, nous spécifions le seau de sortie dans lequel la transcription résultante doit être écrite : nous utilisons le même seau que les enregistrements.

Pour le dernier gestionnaire, le déclenchement est un peu différent ; cette fois, ce n'est pas un webhook mais l'arrivée du résultat de la transcription dans notre bucket S3 qui invoque notre code.

@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})

Vous remarquerez que le décorateur a un format différent : on_s3_event. Nous spécifions également le seau qui nous intéresse, le type d'événement, lorsqu'un nouvel objet est créé, et le suffixe de ces objets en JSON afin de ne pas déclencher l'événement lorsque les objets d'enregistrement .mp3 sont ajoutés au seau.

Nous récupérons ensuite le nouvel objet qui est une réponse JSON de notre transcription et le conservons dans le fichier data. Nous rendons le fichier d'enregistrement mp3 lisible par le public et commençons à construire notre message de notification. J'aime voir le texte de la transcription en MAJUSCULES car cela ressemble un peu plus au service de téléavertisseur rétro. Nous ajoutons également l'identifiant original de l'appelant au début du message et, enfin, l'URL de l'enregistrement audio à la fin du message, juste au cas où la transcription ne serait pas parfaite et que vous voudriez entendre ce que l'appelant a dit à l'origine.

Enfin, nous envoyons le SMS à l'aide de l'objet client NEXMO créé précédemment.

Déploiement

Maintenant que nous avons construit l'application, il nous reste à déployer celle-ci sur AWS en exécutant :

chalice deploy

Cela crée une fonction Lambda et configure les règles de la passerelle API. Il crée également les événements du seau S3 et configure les politiques de sécurité IAM associées pour nous automatiquement.

Vous devriez alors obtenir un résultat contenant l'URL de la passerelle API, par exemple :

Rest API URL: https://3u9ucalu05.execute-api.us-east-1.amazonaws.com/api/

En utilisant cette URL comme base, mettez à jour votre application Nexmo pour définir le webhook de réponse afin qu'il pointe vers votre application déployée. Vous aurez besoin de l'identifiant de votre application pour cela :

nexmo app:update [APPLICATION UUID] “Paging Service” `chalice url`answer `chalice url`event

Enfin, assurez-vous que votre numéro Nexmo est lié à l'application et appelez-la. Vous devriez entendre votre message d'accueil et pouvoir laisser un message. Peu après, vous recevrez un message texte contenant votre transcription et un lien vers le fichier audio :

text message screenshottext message

Prochaines étapes

Il existe plusieurs façons d'étendre cette application. Vous pouvez par exemple envisager de prendre en charge plusieurs utilisateurs en créant une correspondance entre le numéro d'appel entrant, le numéro de notification et le message d'accueil, ou de remplacer la notification par SMS par un message électronique si cela convient mieux à votre cas d'utilisation.

En outre, les fichiers MP3 et de transcription seront stockés de manière permanente dans votre panier S3, et vous voudrez peut-être envisager un moyen de les supprimer ou de les faire expirer après une certaine période.

Il convient également de mentionner que le service Amazon Transcribe n'est pas le plus rapide, en particulier pour les clips audio courts. Lors de mes tests, j'ai constaté un délai de 1 à 2 minutes pour la transcription d'un court message vocal, alors gardez cela à l'esprit si vous envisagez de l'utiliser pour des notifications urgentes.

Vous pouvez trouver tout le code source de l'application ainsi que la configuration de chalice dans le Dépôt GitHub.

Partager:

https://a.storyblok.com/f/270183/384x384/7fbbc7293b/sammachin.png
Sam MachinAnciens de Vonage