
Construire une application de pagination sans serveur avec Amazon Transcribe.
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'applicationrequirements.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 osNous 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 nccoLe 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/recordingoù api 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 :
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
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.
