もがもがしいブログ

もがもがしく生きる

Amazon Connect+Transcribe+KVS+SNS で留守番電話をテキストに起こしてSMSで通知するシステムを作った

f:id:moga_shi:20191202204443p:plain

みなさん、突発的にかかってくる電話は好きですか?

自分は、"それまでの思考や作業が中断され""同期的なコミュニケーションを強いられる上に""だいたいの場合においてこっちにメリットがない" という3点で大嫌いです

留守電にしても、ヘッダー情報が電話番号くらいしかないメッセージを開くのが手間に思えます。*1


留守番電話をテキストにして通知してくれればいいのに・・・*2

f:id:moga_shi:20191202203502p:plain



というわけでAWS上に置いたシステムがこちらです。

f:id:moga_shi:20191202203608p:plain

コンポーネントを簡単に説明していきます。


(1) Calls +81-50-xxxx-xxxx

まずは電話番号の取得から。Amazon Connect でインスタンスを作成し、出来上がったポータルに admin でログインして申請します。*3

f:id:moga_shi:20191202202847p:plain

f:id:moga_shi:20191202202851p:plain


(2) Invokes Lambda

Contact flow を作成し、Lambda を Invoke できるようにします。

f:id:moga_shi:20191202205206p:plain

途中の Play prompt (録音待ち) の部分は SSML の break time を使用したのですが、10秒以上の待ち時間を指定するとうまく待ってくれない事象が起きたため、2つの要素に分割しました。*4

<speak>
<break time="10s"/><p/>
<break time="10s"/><p/>
承りました<p/>
</speak>

(3) Records audio to KVS (raw data format)

(3’) Stores session information in S3

(4) Converts KVS audio to WAV

こちらに関しては、クラスメソッド平内さんのソースを パク 借用しただけなので割愛させていただきます。Rawデータ変換の部分は本当に助かりました。

dev.classmethod.jp

なお、const ebml = require('ebml'); が "errorType":"Runtime.ImportModuleError","errorMessage":"Error: Cannot find module 'ebml'" でコケたので、こちらを参考に ebml の Layer を追加して対応しました。

xp-cloud.jp

(5) Transcribes text from WAV

WAVから文字起こしの処理です。S3 bucket が input/output で分かれているのは、Amazon Transcribe で S3 の出力先フォルダを指定する方法が見つからなかったため、管理の都合上やむを得ず分割しました。

InvokeTranscription

const AWS = require("aws-sdk");
const region = 'ap-northeast-1';
const inBucket = 'connect-voicemail';
const outBucket = 'connect-voicemail-text';

/**
 * exports.handler
 */
exports.handler = async(event) => {

  const transcribe = new TranscribeService(AWS, region);
  
  for (let record of event.Records) {
    const key = record.s3.object.key;
    const jobName = key.split('/')[1];
    const uri = 'https://' + inBucket + '.s3-' + region + '.amazonaws.com/' + key;
    var res = await transcribe.start(jobName, uri);
    console.log('res: ' + JSON.stringify(res));
  }
  return {};
  
};

/**
 * Class TranscribeService
 */
class TranscribeService {
  
  constructor(AWS, region) {
    this._transcribe = new AWS.TranscribeService({ apiVersion: '2017-10-26', region: region });
  }
  
  async start(jobName, uri) {
    const params = {
      'TranscriptionJobName': jobName,
      'LanguageCode': 'ja-JP',
      'Media': {
        'MediaFileUri': uri
      },
      'MediaFormat': 'wav',
      'MediaSampleRateHertz': 8000,
      'OutputBucketName': outBucket,
    };
    
    return await this._transcribe.startTranscriptionJob(params).promise();
  }
}

(6) Sends SMS to my phone (+81-80-xxxx-xxxx)

SendTranscriptionBySMS

import boto3
import logging
import json

s3 = boto3.client('s3')
sns = boto3.client('sns')
logger = logging.getLogger()

topicArn = 'arn:aws:sns:ap-northeast-1:<Account ID>:TranscriptionSNS'

def handler(event, context):

  # Get transcription from S3 object (JSON)
  bucket = event['Records'][0]['s3']['bucket']['name']
  key = event['Records'][0]['s3']['object']['key']
  s3Object = s3.get_object(
    Bucket = bucket,
    Key = key
  )
  content = json.loads(s3Object['Body'].read().decode('utf-8'))  

  for transcript in content['results']['transcripts']:
    
    logger.warn(transcript)
    
    # TODO Shorten message if the length is larger than 160 bytes
    message = transcript['transcript']
    
    res = sns.publish(
      TopicArn = topicArn,
      Message = message
    )
    #logger.warn(res)

  return {};

SMSのテスト中に "No quota left for account" というエラーに引っ掛かり、調べたところ、デフォルトの Quota が 1 USD / month (about 15 SMS messages per month) となっているとのことだったので サポートケースをオープンして上限を引き上げました。

aws.amazon.com


4時間ほどで返信あり。

Hello,

Your new SMS monthly spending limit of $10 USD was implemented. This may take up to one hour to reflect in your console.

Before you can send messages, you must update your account spend limit using the Amazon PINPOINT console or API ( https://docs.aws.amazon.com/pinpoint/latest/userguide/channels-sms-setup.html ).

When you complete these procedures, you may see a message stating that your default limit is $1.00. You can disregard this message.

We recommend monitoring metrics for Amazon Pinpoint using CloudWatch ( https://docs.aws.amazon.com/pinpoint/latest/userguide/monitoring.html ).

As you get started with Amazon Pinpoint, we recommend that you:
Thank you for choosing Amazon Web Services.

Best regards,


Amazon PINPOINT で Limit を Update せよとのことでしたが、それはせず普通に [Amazon SNS] -> [Text messaging (SMS)] -> [Text messaging preferences] -> [Account spend limit] の設定を変えれば即座に反映されました。

f:id:moga_shi:20191202202900p:plain

ためしに迷惑電話っぽい電話を自分にかけてテストしました。ところどころうまくいってないのはたぶん滑舌のせい。(だから電話はイヤなのだ)

追記:

本記事公開のわずか2日後に発表された Contact Lens for Amazon Connect (Preview) に Sign up すると、音声の文字起こし部分を AWS-Managed で利用できるようです。
dev.classmethod.jp
aws.amazon.com

*1:イヤなものは全部イヤに見えてくるのだ

*2:キャリアSIMだとあるらしいですね

*3:日本の電話番号は 050 のみ。1 インスタンスあたり 10 番号まで申請できます。 https://docs.aws.amazon.com/connect/latest/adminguide/connect-tokyo-region.html https://docs.aws.amazon.com/connect/latest/adminguide/amazon-connect-service-limits.html

*4:今の実装だと、留守電を途中(「承りました」の前)で切られた際にその後の Invoke が効かなくなるという不具合があり・・・ break time を短くする以外の Workaround があるといいのですが。