はじめに
この記事はQiita Engineer Festa 2022「Twilioを使うためのコツ、Tipsやおもしろ実装など、Twilioのことなら何でも共有しよう!」というテーマにそって投稿しています。
電話を好きな人の気持ちがわからない。
突如割り込んできて、準備もなく会話をしなければいけない、非人道的・前時代的な同期型コミュニケーションだと思ってます。電話は嫌いです。
そんな面倒な電話に対して、テキストチャットで受け答えできて、相手先には音声通話として変換できたら、電話好きな人と電話嫌いな人もどちらもハッピーなのでは?と思い、Twilioを使ってシンプルなサービスを作ってみました。
誰かが自分に電話をかけると、話しかけた内容がリアルタイムでSlackに自動投稿されます。そのチャットにテキストで返答すると、その内容を音声にして電話相手に返答してくれるサービスです。
電話嫌いからすれば、電話応対がすべてチャットで完結するし、電話をかけてきた方も音声で要件を伝えて、音声で返事がもらえるので、Win-Winではないでしょうか?特に、電話が嫌いなエンジニアと電話で仕事をする営業の橋渡しとして、どちらの要件も妥協せずにツールで解決できれば、大幅に業務の効率化が進むかと思います。
作ったサービスを実際に使ってみたデモ動画がこちら。動画はチャット画面のみですが、電話する方は普通に電話をかけて話してるだけです。
どうやって動いているのか
システム概要
上図の通り、中身はTwilioのサービスにSlackとGoogle Cloud Functionを連携させています。各コンポーネントの中身はこちら。
Twilio
Twilioは電話の発信・着信などをプログラムで実行できるようにするクラウドコミュニケーションプラットフォームサービス (CPaaS) の一つです。そのProgrammable Voiceというサービスで、電話に対してプログラムで対応できるようなAPIを提供してくれています。
今回は電話番号をTwilioで用意し、音声認識・合成をすべてTwilioプラットフォームで行ってくれています。電話の発着信をプログラム管理するだけでなく、電話音声を音声認識・合成で高度に分析・作成できるのは、非常に強力です。
掛かってきた電話の内容をTwilioプラットフォームで自動で音声認識し、認識したテキストデータを設定したCloud Functionのプログラム宛にPOSTしてくれるのでかなり楽ちんです。
一方で、Cloud Function側で返答のテキストデータを、Twilio側にテキストで渡すと、Twilioで音声合成を行われ、相手の通話先で再生させることができます。
今回のサービスのほとんどはTwilioのデフォルトの機能でできてしまうのが驚きです。
連携サービス
Slack
おなじみチャットサービス。今回はシンプルにSlack APIを使ってメッセージの送受信をプログラムから行っています。
Google Cloud Function
Google Cloud Functionは、Google Cloudから提供されているFunction as a Service(FaaS)で、呼び出し時にのみプログラムが実行されます。Twilioからの電話の内容をテキストデータで受け取り、Slack APIを呼び出してチャット投稿したり、Slackからのチャット返答をTwilio SDKを用いて返したりと橋渡しの役割をしています。
TwilioのProgrammable Voiceに対応付けるプログラムは、電話がかかってきたときのみ起動されれば十分で、常に立ち上がってる必要がない、というまさにFaaSが適した要件なので、今回はCloud Functionを使いました。今回のサービスで呼び出したのはテスト含めても20回ほどで、料金は無料枠で十分です。
一つ注意点としては、Twilioからプログラムを呼び出したときに、ある一定時間プログラムから応答がないと、タイムアウトで切断される仕様になっています。Cloud Functionは0からインスタンスを立ち上げる場合、3sec程度かかり、最初のコールは必ずタイムアウトでエラーが起きます。これを避けるには、Cloud Functionの最小インスタンス数を1にするか、Hot-standbyモードにしておけば避けることができます。
処理の流れ
最初のデモで行った処理の流れを、システムの面から見てみると、こんな感じになります。
- (電話したい人)Twilioの電話番号に電話する
- (Twilio)音声認識して、テキストデータに変換
- (Twilio)設定で紐付いたWebhook URL(プログラム)を呼びに行く
- (Cloud Function)Slack APIを呼び出し、受け取ったテキストデータをそのままチャットに投稿をする
- (電話したくない人)Slackでチャットを見て、返答をSlackで返す
- (Cloud Function)返答されたSlackチャットのテキストデータを取得し、それをTwilioに返答
- (Twilio)受け取ったテキストデータを音声合成し、電話先で再生する
一部コード紹介
電話を受けたときの処理
Twilioで音声認識した結果がGETで受け取れるので、それをSlack APIを使ってチャット投稿する流れになります。
### Twilioから音声認識の結果をGETで受ける
speech_recog_result = request.form.get("SpeechResult","")
### Slack APIにそのままテキストデータを投げる
slack_res = requests.post(
"https://hooks.slack.com/services/<your_Slack_URL>",
json.dumps({"text":speech_recog_result}),
headers={"Content-type": "application/json"}
)
チャットで返信したときの処理
Slack APIでチャット返答の内容を取得し、それをrespond_and_wait_new_voice関数でTwilioに返し、次の会話内容を待ちます。
この関数は、音声合成用のモデルや読み上げるテキストをセットし、Twilioに返してます。
### Slackに電話内容をチャットした時以後のチャット内容を取得
callmessage_timestamp = datetime.datetime.now().timestamp()
chat_response_message = get_new_Slack_message(callmessage_timestamp)
### チャット内容の文字列を、respond_and_wait_new_voiceというTwilioのAPI関数で返答
response = respond_and_wait_new_voice(chat_response_message)
def respond_and_wait_new_voice(response_message):
### 音声合成のモデルと読み上げ内容をセット
gather = Gather(input='speech', action='', language='ja-JP', speechTimeout='auto')
gather.say(response_message, language='ja-JP', voice='Polly.Takumi')
### Twilioの返答用インスタンスに先程の設定をセット
response = VoiceResponse()
response.append(gather)
return response
おわりに
以上のように、電話にチャットで返信できるサービスを実装してみました。本番運用するには、日本の電話番号を取得したり、Cloud Functionをホットスタンバイさせる等が必要になりそうですね。
作ってみての感想として、Twilio側で音声認識・合成があることで、作るのが本当に楽になってます。音声ストリームの処理はかなり面倒ですから、これを気にしないでいい、REST APIで完結する、というのはアプリ開発のハードルを劇的に下げてます。
また、音声認識・合成についてはAWSのサービスを呼び出しているため、言語セットや自由度が高く、日本語もかなりの精度で認識してくれます。
Twilio自体の料金についても安価で、米国の番号を使った場合で1分約1円で着信運用、電話番号保持に1ドル(2021年12月現在)しかかかりません。
さらに、アプリのメイン機能はCloud Function側にあるので、機能追加が簡単です。例えば追加機能として、英語の電話を自動翻訳して、日本語メッセージを英語音声にする機能を追加したい場合も、Cloud Function側での実装で事足りそうです。
最後に、こういったサービスほしかった!という方は本記事のコメント欄か、jot(あっとまーく)senbishi.comまでメールいただければ嬉しいです!