TwilioのProgrammable VoiceとAITalkのWeb APIを使って表現力豊かな自動音声で着信電話に応答してみた

Twilioが提供するProgrammable Voiceと株式会社エーアイが提供する日本語に特化した音声合成サービス、AITalk® Web APIを連携し、感情表現豊かな音声を動的に生成し、電話に応答する方法をご紹介します。
2020.03.30

以前の記事ではTwilioが提供するProgrammable VoiceとGoogleが提供するCloud Text-to-Speechを組み合わせて標準以外の音声合成エンジンを利用する方法を紹介しました。

今回は似たようなアプローチで株式会社エーアイが提供する日本語に特化した音声合成サービス、AITalk® Web API(以下 AITalk Web API)と連携してみました。 AITalk Web APIは 感情表現や自然な音声、豊富な話者ラインナップ を提供する音声合成エンジン AITalk® をSaaS型で利用できるサービスです。サーバーを自前で構築する必要がないため、他のSaaSやクラウドサービスと素早く連携できます。これまでは評価版の申し込みから利用開始まで少し時間がかかっていましたが、最近Webから評価用アカウントを即時に取得し、利用できるようになりました。

必要なもの

  • Node.jsおよびnpm
  • AITalk Web API評価アカウント
  • TwilioアカウントとTwilio電話番号

今回の記事で解説するサンプルプロジェクトはこちらのGitHubリポジトリから取得できます。

GitHub - Neri78: Twilio-Voice-AiTalkWebAPI

AITalk Web API評価アカウントの作成方法

冒頭でも記載しましたが、AITalk Web APIでは評価を目的としてサービスを2週間利用できる評価アカウントをWebサイトから申し込めるようになりました。AITalk WebAPI 評価アカウント申込みページで必要な情報を入力し利用規約に同意すると、評価用APIのエンドポイントや接続情報、APIの仕様に関する情報がメールで送られてきます。

AITalk WebAPI 評価アカウント申し込み画面

Twilioアカウントのサインアップと電話番号の取得

Twilioアカウントのサインアップと電話番号の取得方法についてはこちらで確認できます。TwilioQuest 3というゲームチュートリアルを題材にしていますが、サインアップと電話番号の購入方法は通常のフローと同じです。

AITalk Web APIを用いた音声応答の構築

サンプルでは、下記の図のようにTwilio電話番号に着信した際に、Webhookを経由し、AITalk Web API にリクエストを送信し、合成された音声データをレスポンスとして受け取ります。

Twilio Programmable VoiceとAITalk Web APIの連携

サンプルプロジェクトをローカル開発環境に展開し、必要なパッケージをインストールします。

npm install

次に、.env.sampleファイルをコピーし、.envとリネームします。このファイルにはアカウント登録時に送られてきたAITalk Web APIのエンドポイント、ユーザー名、パスワードを保存します。これらの情報はサンプルコード内で利用されます。

AI_TALK_BASE_URL=https://api.example.com
AI_TALK_USERNAME=username
AI_TALK_PASSWORD=password

着信時の応答を返すAPIを実装

次にindex.jsを開いてください。必要なパッケージの宣言と、生成した音声ファイルを保存しておくフォルダを設定しています。

'use strict';
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const path = require('path');
const fsp = require('fs').promises;
const got = require('got');
//着信に対して応答を返すVoice用TwiMLを作成するクラス
const VoiceResponse = require('twilio').twiml.VoiceResponse;

const app = express();
const VOICE_FOLDER_NAME = 'voice_files';
app.use(bodyParser.urlencoded({ extended: true }));
// 生成したファイルを保存しておくフォルダを設定
app.use(express.static(path.join(__dirname, VOICE_FOLDER_NAME)));

Twilioで取得した電話番号に着信があった際に呼び出すWebhookはこちらになります。内部で非同期メソッドを使用するためasyncキーワードを指定しています。

// Twilioから着信リクエストを受け取るWebhook
app.post('/incoming', async (req, res, next) => {
    //処理を実装
});

AITalk Web APIへのリクエストを送信し生成されたファイルを保存

さて、/incoming内部の実装は次のようになっています。

応答テキストと AITalk Web API に送信するリクエストパラメーターを構築します。さまざまな設定が可能ですが、最低限、ユーザー名、合成文字列、話者名が必要になります。このサンプルでは感情対応話者である「のぞみ」さん('speaker_name' : 'nozomi_emo')、最大限の喜びを('style' : { 'j': '1.0'})抑揚('range' : 2.00)をつけてmp3で出力('ext' : 'mp3')するように設定しました。

// 音声合成するテキストを設定。実際はAIエンジンなどから生成される。
const text = '今日はお電話ありがとう!' + 
             'エーアイトークとTwilioを連携した音声合成デモだよ!' +
             'あなたの電話番号は' + req.body.From + 'だよね?' +
             'ぜひ、両方のサービスを試してみてね。';

// AITalk Web API用のパラメーターを作成
const aiTalkOptions = {
    'username' : process.env.AI_TALK_USERNAME,
    'password' : process.env.AI_TALK_PASSWORD,
    'text' : text,
    'speaker_name' : 'nozomi_emo',
    'style' : { 'j': '1.0'},
    'range' : 2.00,
    'ext' : 'mp3'
}

AITalk Web APIのパラメータを含んだリクエストを送信し、レスポンスに含まれるバイナリデータをmp3ファイルとして保存します。今回のサンプルではNode.js用のHTTPライブラリーgotを利用しています。

try {
    // リクエストオプション
    const options = {
        method: 'POST',
        form: aiTalkOptions,
        encoding: 'binary'
    };

    //AITalk Web APIにリクエスト
    const response = await got(process.env.AI_TALK_API_URL, options);

    if (response.statusCode === 200 ) {
        //現在のCallSidから出力ファイル名を生成
        const outputFileName = `${req.body.CallSid}.mp3`;
        // mp3ファイルを保存
        await fsp.writeFile(
            path.join(VOICE_FOLDER_NAME, outputFileName), 
                      response.body, 'binary');

        // TwiMLを作成(次のコードブロック)
    }
} catch (error) {
        console.error(error);
        next(error);
}

保存したmp3ファイルを再生するTwiMLを作成

Twilio Programming Voiceは電話への着信時の応答を TwiML と呼ばれているマークアップ言語でプログラミングできます。Play句を使用し、先ほど保存したmp3ファイルを再生するというTwiMLを生成します。このTwiMLをレスポンスとして返しています。

// TwiMLを初期化
const twiml = new VoiceResponse();
// 生成した音声を再生させる。
twiml.play(path.join(outputFileName));
//作成したTwiMLをレスポンスとして送信
res.status(200).send(twiml.toString());

(追加)通話が終了した段階でmp3ファイルを削除

さて、このままでは通話終了後も生成された音声ファイルが残ってしまいます。不要なファイルを削除するため、通話状態を取得できるWebhookを実装します。 /statuschangedでは、リクエストのbodyに含まれているCallStatusで現在の通話状態を確認できます。今回のサンプルでは、通話が完了したcompletedとなった時点で、通話IDを表すCallSidを基にmp3ファイルを削除しています。

app.post('/statuschanged', async(req, res, next) => {
    try {
        // 通話が完了したかどうかを確認
        if (req.body.CallStatus === "completed")
        {
            // CallSidからファイルパスを構築し、削除
            const filePath = 
                path.join(VOICE_FOLDER_NAME, `${req.body.CallSid}.mp3`);
            await fsp.unlink(filePath);
            console.info(`${filePath}を削除しました`);
        }
        res.sendStatus(200);
    } catch (error) {
        console.error(error);
        next (error);
    }
});

全ての実装を終え、Port3000でサーバーを起動します。

app.listen(3000, () => console.log('Listening on port 3000'));

これで実装は完了です。

ローカル環境でサンプルを実行する場合

ローカル環境でサンプルを実行する場合は、localhostのポート3000番に外部からアクセスできるようにします。 その場合は、ngrokなどのツールが利用できます。 ngrokをインストール後、別のターミナルを開き、下記のコマンドを実行します。

ngrok http 3000

ngrok

ここで生成されたURLをメモしておきます。

Twilioコンソールで着信応答と、通話ステータスのWebhookをそれぞれ設定

最後にTwilioコンソールで取得した電話番号の設定画面を表示し、作成したNode.jsアプリケーションのUrlを着信応答と通話ステータスのWebhookをそれぞれ指定します。 この例ではhttps://2c2a973d.ngrok.ioなので下記のスクリーンショットのようになります。

Twilio コンソール

ターミナルからアプリケーションを起動し動作を確認

ローカル環境の場合は、ターミナルからアプリケーションを起動します。

npm start

Twilioから取得した番号に電話をかけてみてください。前回同様に電話番号を数字として解釈してしまっているので少し変ですが、動的にリアルタイムで感情と抑揚が設定された 合成音声を応答として利用することを確認できます。

まとめ

今回はTwilio Programmable VoiceとAITalk Web APIを連携してみました。AITalk Web APIでは、感情表現対応話者だけでなく、関西弁 対応話者も設定されているので話者を('speaker_name' : 'miyabi_west')に変更して違いをくらべてみるのも面白いかもしれません。