https://a.storyblok.com/f/270183/572742/f26e2c1e2f/pager-hack-the-planet.png

Amazon Transcribeでサーバーレスのページング・アプリケーションを構築する

最終更新日 May 4, 2021

所要時間:1 分

90年代には素晴らしいテクノロジーがあった:ミニディスク、たまごっち、PAGERS!

しかし、ポケベルにはある種のノスタルジーがある。

また、オンコールで午前2時に必死で助けを求める電話で起こされた経験のある人なら誰でもわかるように、ポケベルには電話では失われた利点があった。

ポケベルのことを覚えていない若い人たちのために説明すると、相手のポケベル番号に電話をかけると、オペレーターが電話に出て、そのオペレーターがメッセージを取り、それをコンソールに入力すると、しばらくしてポケベルの画面にメッセージが表示される。

このチュートリアルでは、NexmoとAWSを使ってメッセージング・サービスを再現する方法を紹介する。

AIは最近大きく進歩し、高価なページング局オペレーターをAPIで置き換えることができるようになった。この例では、Amazon Transcribeを使おうと思う。Nexmoを使って着信を受け、それに応答してメッセージを録音し、その録音をTranscribeに渡し、テキストが返ってきたらNexmo SMSを使ってあなたの携帯に送ります。

各パーツを接着するために、Chaliceフレームワークを使ってPythonアプリケーションを構築し、AWS LambdaとS3上で全体をデプロイして実行できるようにする。

このアーキテクチャーの利点は、ホスティングコストが非常に少なくて済むことだ。実際、個人的な使用目的であれば、AWSの無料ティアの範囲内に収まるでしょうし、成長させたいのであれば、サーバーレス・スタックは大量にスケールするでしょう。

前提条件

このチュートリアルには以下のものが必要です:

  • AWSアカウント(無料ティアで実行可能)

  • AWSの AWS CLIツールChaliceがインストールされ、設定されていること

  • Nexmoアカウントで ネクスモCLIツールインストールされ、設定された

セットアップ

機能を実装する前に、いくつかのセットアップを行う必要がある。

Nexmoボイスアプリ

nexmoダッシュボード内またはコマンドラインツールを使用して、新しいVoiceアプリケーションを作成する必要があります。

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

返されたアプリケーションIDをメモしておくこと。これで秘密鍵もファイルに保存される。

今のところ、ウェブフックにはダミーの値を使うことにする。 ngrokを使ってください。

S3バケット

AWSのCLIを使用してこれを行うことができます。AWSのus-east-1リージョンにリソースを作成する。

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

聖杯申請

まず最初に、新しい聖杯プロジェクトを作ります:

chalice new-project

プロンプトが表示されたら、プロジェクト名を入力してください。 paging-service.これで、プロジェクト名のフォルダに聖杯アプリケーションの基本テンプレートが作成されます。

そのフォルダーの中に3つの重要なファイルがある:

  • app.pyアプリケーションのメインコード

  • requirements.txt: 使用している python モジュールをリストアップします。

  • .chalice\config.jsonプロジェクトに関する様々な設定が含まれています。

インポートモジュール

テンプレートには、すでにchaliceモジュールがインポートされています。また、S3とTranscribeに接続するためのboto3モジュールと、録音を取得してSMSを送信するためのNexmoモジュールも必要です。環境変数にアクセスするためのosモジュールもインポートします。

import boto3
import nexmo
import json
import os

また、boto3とnexmoを requirements.txtアプリケーションをデプロイするときに、lambdaがこれらをインストールすることを認識できるようにするためだ。

セットアップ変数

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'

私たちはほとんどの変数を環境変数から設定しています。githubのREADMEからchaliceでこれらの変数を設定する方法を見ることができます。

クライアントの初期化

アプリケーションの一部として、3つの外部サービスに接続する予定だ:AWS S3、Amazon Transcribe、Nexmoだ。これらの接続をここで作成する:

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

Lambdaへのデプロイ時にbotoとchaliceが自動的にやってくれるので、Amazonサービスの認証情報を提供する必要はない。

ハンドラーコードを書く

このアプリケーションでは、ボイスメッセージを書き起こすために3つの段階を経る必要があります。

1つ目は、Nexmoからの着信要求に応答し、NCCO(Nexmo Call Control Object)を返すWebhookハンドラで、これは、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

デコレーターは @app.routeデコレータは、このハンドラが応答するパスを定義します。受信リクエストのパラメータを req辞書オブジェクトに変換します。そして、NCCO レスポンスを作成します。

最初のアクションは talkこれは発信者が聞く最初の挨拶で、挨拶をパーソナライズするためにここでNAMEパラメータをテンプレート化しています。

次に recordアクションがあり、ここで呼び出し元からのメッセージを受け取ります。私は endOnSilenceを3秒に設定し、話し終わったら次に進むようにしました。 endonKeyを使って#を押すことができます。 beepStartをTrueに設定することで、発信者がいつ話し始めるかを知ることができます。

この eventUrlパラメータは少し複雑に見えるが、私がここでやっているのは、APIゲートウェイで使用される受信ウェブフックと同じホストとプロトコルになるURLを構築するだけなので、ハードコードする必要はない。パスは /api/recordingここで apiがAPI gatewayのデフォルトだ。最後に fromをクエリパラメータとしてタグ付けし、録音ウェブフックが来たときに、そのコールの元の発信者IDがわかるようにしています(nexmoは録音イベントでこれを標準では渡さないため)。

NCCOの最後は、シンプルなアクションで締めくくる。 talk発信者はメッセージが録音されたことを知り、電話を切ることができる。録音がまだアクティブな状態で電話を切っても、メッセージは配信されます。

次のハンドラでは、Nexmoから受信した録音イベントを受け取り、録音をフェッチしてS3に保存し、それから文字起こしアクションを開始する。

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

同じ @app.routeしかし、POST リクエストを処理するように指定しています (chalice のデフォルトは GET です)。

クエリ文字列のパラメータを取得しています。 fromという名前のディクショナリに渡しています。 qparamsというオブジェクトに入れ、ウェブフックのJSONボディを data.

作成した NEXMOオブジェクトをNexmoへの接続として使用して録音を取得し、会話のUUIDをキーとしてS3バケットに保存する。また、オブジェクトに対していくつかのメタデータを設定する。from)と録音時間である。

最後に、S3にある新しい録音を指すトランスクリプションジョブを開始する。ジョブの名前(ここでも会話のUUIDを使用する)、音声の言語(ここではEnglish - British)、メディアフォーマット、S3のファイルのURIを指定する必要がある。このフォーマットは、バケットを作成したリージョンによって異なります。最後に、書き起こし結果の出力先バケットを指定します。ここでは録音と同じバケットを使用しています。

最後のハンドラでは、少し違ったトリガーを使っている。今回はウェブフックではなく、S3バケットにトランスクリプションの結果が到着したことをトリガーにして、コードを呼び出している。

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

デコレーターの形式が違うことに気づくだろう: on_s3_event.また、録画オブジェクトがバケツに追加されたときにトリガーされないように、対象のバケツ、イベントの種類、新しいオブジェクトが作成されたとき、そしてそれらのオブジェクトのサフィックスを JSON で指定しています。 .mp3オブジェクトがバケツに追加されたときにトリガがかからないようにするためです。

そして、私たちのトランスクリプションのJSONレスポンスである新しいオブジェクトをフェッチし、それを data.mp3の録音ファイルを一般に読めるようにしてから、通知メッセージを作り始めます。文字起こしのテキストを大文字で表示するのは、レトロなポケベル・サービスのようで好きです。また、メッセージの最初にオリジナルの発信者番号を追加し、最後にメッセージの最後に録音音声のURLを追加します。

最後に、先ほど作成したNEXMOクライアント・オブジェクトを使ってSMSを送信します。

配備

アプリケーションをビルドしたので、あとはこれをAWSにデプロイするだけだ:

chalice deploy

これはLambda関数を作成し、APIゲートウェイのルールを設定する。また、S3バケットのイベントを作成し、関連するIAMセキュリティポリシーを自動的に設定してくれる。

すると、APIゲートウェイのURLを含む出力が得られるはずだ:

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

このURLをベースとして、Nexmoアプリケーションを更新し、アンサーウェブフックをデプロイしたアプリケーションに設定します。これにはアプリケーションIDが必要です:

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

最後にNexmo番号がアプリケーションにリンクされていることを確認し、電話をかけてみてください。あなたの挨拶が聞こえ、メッセージを残すことができます。その後すぐに、テキストメッセージと音声ファイルへのリンクが届きます:

text message screenshottext message

次のステップ

このアプリケーションを拡張する方法はいろいろあります。例えば、着信番号と通知番号、挨拶のマッピングを作成することで複数のユーザーをサポートしたり、SMS通知をEメールに置き換えたりすることができます。

加えて、MP3ファイルと書き起こしファイルはS3バケットに永久に保存されるので、一定期間後に削除または期限切れにする方法を調べておくとよいでしょう。

Amazon Transcribeのサービスは、特に短い音声クリップの場合、最速ではないということも言っておく価値がある。私のテストでは、短いVoiceメッセージを書き写すのに1-2分の遅れがありました。

アプリケーションのすべてのソースコードとchaliceの設定は GitHubリポジトリ.

シェア:

https://a.storyblok.com/f/270183/384x384/7fbbc7293b/sammachin.png
Sam Machinヴォネージの卒業生