[Amazon Connect] 留守番電話形式で無人受付のサポートデスクを作ってみました 〜録音されたメッセージは、チャットに投稿されます〜

2018.11.09

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

1 はじめに

Amazon Connect(以下、Connect)を使用して、留守番電話にメッセージを残すイメージで、サポートの受付を行う仕組みを作ってみました。

Connectでは、留守番電話のような処理がデフォルトで提供されていないという事で、ちょっと諦めていたのですが、これを実現する方法が紹介する記事がありましたので、これを参考にさせて頂きました。

Creating a voicemail system with Amazon Connect 1/3

最初に、利用しているイメージです。(音声をテキストに変換する処理には、5秒程度必要です)

2 構成

全体の構成は以下のような感じです。

  • Connectで受けたユーザーからのメッセージはS3に保存されます。
  • Connectで受け付けた際に、発信者の電話番号をDBに格納します。
  • S3に保存された音声ファイルは、テキストに変換され、電話番号と共にチャットに投稿されます。

3 メッセージの録音

Connectでは、顧客やエージェントの通話内容を記録する機能があります。

しかし、この機能は、顧客とエージェントの会話が始まってからしか使用できないので、無人で対応して顧客のメッセージを録音するようなフローが作成できません。

今回の仕組みでは、2つの問い合わせフローが使用されています。 1つ目のフローでユーザーからの電話を受け付け、録音を有効にして直ちにキューに格納します。

このシステム専用に作成されたエージェントが、待ち時間無しでこれを処理します。このとき、エージェントの利用電話番号が2つ目のフローに繋がるようになっています。

2つ目のフローでは、「メッセージを残すように促す」アナウンスと、数秒の無音のオーディオが再生されます。

1つ目のフローで有効にした録音設定で、エージェントが対応してから切断されるまでの内容が録音されS3に保存されます。

4 問い合わせフロー

2つのフローは、以下のようになっています。

最初のフロー

エージェント接続後のフロー

5 エージェント

自動応答用のエージェントは、使用する電話をデスクフォンとし、2つ目のフローに繋がる電話番号が設定されています。また、ACWを短くし、切断後のすぐに次の電話に対応できるようにしています。

6 テキスト化

録音されたメッセージをテキスト化する処理は、Watson Speech to Textを利用させて頂きました。

https://speech-to-text-demo.ng.bluemix.net/

10秒程度のオーディオであれば、APIキーの発行を受けてRsstAPIを使用することで簡単にテキストを得ることができます。

なお、Connectで保存されるWAVファイルでは、サンプリングレートが未対応で、そのまま変換できなかったので、ffmpegで変換しています。 ffmpegは、Lambdaの実行環境で利用できるよう、スタティックリンクされたffmpegをindex.jsと同じところに置いています。

スタティクリンクされたffmpeg

x86_64 build: ffmpeg-release-64bit-static.tar.xz - md5

詳しくは、次のLambdaのコードをご参照下さい。

7 Lambda

今回実装したLambdaのコードです。このコードでは、1つ目のフローから電話番号の保存のために呼ばれる処理と、S3へWAVファイルがPutされた時に呼び出される処理が含まれています。

const speech = require('@google-cloud/speech');
const aws = require('aws-sdk');
const s3 = new aws.S3();
const rp = require('request-promise');
const fs = require('fs');

exports.handler = async (event, context) => {
if(event.Records && event.Records[0] && event.Records[0].s3.bucket){
await put_s3(event); // S3にWAVファイルがアップされた時の処理
} else if (event.Details && event.Details.Parameters) {
await connect_func(event); // 着信時の処理
}
return {};
};

// 着信時の処理
async function connect_func(event) {
const phoneNumber = event.Details.ContactData.CustomerEndpoint.Address;
await savePhoneNumber(phoneNumber);
}

const region = 'ap-southeast-2';
const tableName = 'connect-voice-mail';
const value = '001';

// 電話番号の保存
async function savePhoneNumber(phoneNumber) {
const client = new aws.DynamoDB.DocumentClient({region: region});
const item = {
'id': value,
"phoneNumber": phoneNumber
}
const result = await client.put( {
"TableName": tableName,
"Item": item
}).promise();
console.log(result);
}

// 電話番号の読み込み
async function readPhoneNumber() {
const client = new aws.DynamoDB.DocumentClient({region: region});
const param = {
TableName : tableName,
KeyConditionExpression : "#k = :val",
ExpressionAttributeValues : {":val" : value},
ExpressionAttributeNames : {"#k" : 'id'}
};
const result = await client.query(param).promise();
if(result && result.Items && result.Items.length > 0){
return result.Items[0].phoneNumber;
}
return 'NotFound';
}

// S3にWAVファイルがアップされた時の処理
async function put_s3(event){
//******************************************************* */
// putされたファイルを/tmpにダウンロードする
//******************************************************* */
const bucket = event.Records[0].s3.bucket.name;
const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
const params = {
Bucket: bucket,
Key: key,
};
const srcPath= '/tmp/src.wav';
const obj = await s3.getObject(params).promise();
fs.writeFileSync(srcPath, obj.Body);

//******************************************************* */
// ffmpegでサンプリングレートの変更
//******************************************************* */
const dstPath = '/tmp/dst.wav';
const cmdline = "./ffmpeg -y -i " + srcPath + " -ar 44100 " + dstPath;
const execSync = require('child_process').execSync;
const result = execSync(cmdline);

//******************************************************* */
// Watson Speech to Text でテキストへの変換
//******************************************************* */
const data = fs.readFileSync(dstPath);
const apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxx";
const auth = "Basic " + new Buffer("apikey:" + apiKey).toString("base64");
const options = {
method: 'POST',
uri: 'https://gateway-tok.watsonplatform.net/speech-to-text/api/v1/recognize?model=ja-JP_BroadbandModel',
headers : {
"Content-Type": "audio/wav",
"Authorization" : auth
},
body: data
};
const response = await rp(options);
const json = JSON.parse(response);
let message = '';
json.results.forEach(result => {
result.alternatives.forEach( alternative =>{
if(alternative.transcript){
message += alternative.transcript;
}
})
});

//******************************************************* */
// 電話番号の取得
//******************************************************* */
const phoneNumber = await readPhoneNumber();

//******************************************************* */
// Chatworkへの送信
//******************************************************* */
const chatwork_token = 'xxxxxxxxxxxxx';
options = {
url: 'https://api.chatwork.com/v2/rooms/xxxxxxxx/messages',
method: 'POST',
headers : { 'X-ChatWorkToken': chatwork_token },
json: true,
form: {'body':'[info][title]' + phoneNumber + 'からメッセージを受け付けました[/title]' + message + '[/info]'}
};
await rp(options);
}

8 最後に

このような留守番電話の仕組みは、別のシステムを使用すれば、もっと簡単にできるのかも知れませんが、Connectでも実現可能だったということで紹介させて頂きました。

なお、このシステムを利用する上では、対応するエージェントがキューを処理できるように、ログインにして状態がAvailableになっている必要があることにご注意下さい。

今後、音声のテキスト化や、機械学習による内容解釈、また、Lexとの連携により、このような仕組みは、色々面白い展開になりそうな予感がします。

9 参考にさせて頂いたリンク

Creating a voicemail system with Amazon Connect 1/3

Creating a voicemail system with Amazon Connect 2/3

Creating a voicemail system with Amazon Connect 3/3