2023年5月1日を持ちまして、株式会社KDDIウェブコミュニケーションズのTwilioリセール事業が終了したため、本記事に記載されている内容は正確ではないことを予めご了承ください。
はじめに
みなさん、こんにちは。
KDDIウェブコミュニケーションズの Twilioエバンジェリストの高橋です。
こちらは、今年の8月頃に投稿した記事を、Twilio Flex Advent Calendar 2021 12日目向けに少しリライトしたものとなります。
前回同様、Twilio FlexのACD機能で少し複雑なTipsのご紹介となります。
シナリオの確認
今回のシナリオは以下のとおりです。
- 着信時に、対応できるオペレータが誰もいない場合に、特定のメッセージを流して通話を切断したい。
このようなニーズの場合、大きく2つのアプローチがあります。
- 着信時にオペレータを確認して、対応できるオペレータがいない場合はメッセージを流して切断。
- 一度待ち呼を生成して、一定時間でオペレータが対応できなかった場合にメッセージを流して切断。
前者のケースでは、TaskRouterに流す前に判定してしまうのがよいです。一方、後者のケースでは一度TaskRouterに流して、誰も対応できなかったことを検知して対応します。
具体的な対応方法
ではこれらの対応策について具体的に見ていきましょう。
TaskRouterに流す前に判定する
こちらは、Studioの中でFunctionsを呼び出すことで対応します。
呼び出すFunctionsには、例えば以下のようなコードを書きます。
/**
* 特定のスキルを持ったワーカーが着信可能かを確認する
* @params {String} skill 検索対象のスキル(例:support)
*/
exports.handler = function(context, event, callback) {
const WORKSPACES_SID = context.WORKSPACES_SID; // 環境変数からワークスペースのSIDを読み込む
const skill = event.skill.toLowerCase() || '';
const client = context.getTwilioClient();
client.taskrouter.workspaces(WORKSPACES_SID).workers.list({
targetWorkersExpression: `routing.skills HAS '${skill}'`,
available: true
})
.then(workers => {
callback(null, { available: workers.length > 0 ? true : false });
})
.catch(err => {
callback(err);
});
};
この例ではFunctionsの呼び出し時にskill
パラメータを渡していますが、この辺りは自由に変えてください。
いずれにしろ、このFunctionsの戻り値がtrue
であれば対応できるオペレータがいることになります。
Studio内のフローは以下のような感じになります。
- 最初に営業宛なのかサポート宛なのかを確認し、その内容に沿って変数
skill
に値をセットします。 - その値をパラメーターとして、先程のFunctionを呼び出し、対応できるオペレーターの有無をチェックします。
- オペレーターが存在していればTaskRouterに引き渡し、存在しない場合は不在メッセージを流して切断します。
いずれにしろ、この方法はTaskRouterに入る前に処理をしてしまうものです。
待ち呼を作ってから処理を行う
こちらのシナリオでは、着信時にはオペレータの確認をするのではなく、一度TaskRouterにコールを送ります。
その後、TaskRouterの中の機能をつかって誰も出なかったことを検知します。
まずは、Twilio FlexにおけるTaskRouterのコールの処理フローについて簡単に説明します。
- Studio内で、SendFlexウィジェットにコールをつなぎます。このときに、TaskRouter内に定義してあるワークフローのどれを使うか指定します。
- 内部的には、この時点で「タスク」が生成されます。そして、このタスクが「ワークフロー」に渡されます。
- タスクを受け取ったワークフローは、このタスクを処理できる「キュー」を見つけます。キューは待ち呼を処理するためのしくみで、対応する「ワーカー」(オペレータのことです)のアサインを受け持ちます。
- アサインされたワーカーには着信の通知が届きます。この時点でタスクは「予約済み」となり、ワーカーがタスクを承諾するか、拒否するか、タイムアウトになるのを待ちます。
- ワーカーがタスクを承諾することで、ようやく着信呼がオペレータに繋がります。
さて、ここからが本題です。
今回のシナリオでは、誰も取らなかった場合にメッセージを流してコールを切断したいというものですから、そもそも誰も取らなかったということを検知しなくてはいけません。誰も取らなかったというのも正確には2つのシナリオがあります。
- オペレータのアサインはできて、呼び出しをしたけれどオペレータが反応しなかった場合
- そもそもオペレータがアサインできなかった場合
まず、前者の場合はタスク自体は「予約済み」になっているので、タイムアウトを待つことになります。タイムアウト値はデフォルトで120秒です。この値はワークフローのプロパティで変更ができます。
タイムアウトになると、ワークフローはそのオペレータのアクティビティをオフライン(デフォルト)にし、別のオペレータをアサインしにいきます。その後も誰もアサインできなかった場合は、後者と同じ動きとなります。
ワークフローの設計
ここでもう一つ、ワークフローの設計についても確認しましょう。
この図は、以下のシナリオに基づいて作られたワークフローの定義例です。
「営業への問い合わせを処理するため、まずは営業担当者を呼び出す。営業担当者が不在、もしくは対応中の場合は、待ち呼にはせず他の担当者に回す。他の担当者も不在、もしくは対応中の場合は、10秒間だけ待ち呼にする。」
①で、タスクのアトリビュートにあるskill
がSales
の場合という条件を指定しています。この条件に合致するタスクは、その下で指定しているように、まずは「sales」というタスクキューに入れられます。
ただし、このときに②の条件が参照されます。ここでは、workers.available == 0
という条件が指定されているので、この条件(salesに対応できるワーカーが不在)に合致するとキューには入れられずに、次に進みます。
次は、「Everyone」というタスクキューです。ここでは、③に条件が指定されていないので、着信呼は④で指定した時間だけ、待ち呼としてキューに滞留します。この間にオペレータが開放されれば、自動的にそのオペレータにアサインされます。
④の時間内にオペレータがアサインできないと、ワークフローは一番下のタスクキュー(⑤)を利用します。
ここではタイムアウトが設定できないので、⑤にタスクキューが指定されていると誰かオペレータがアサインされるまで待ち呼の状態が続きます。
ただし、上記のように⑤が指定されていない(None)場合、ここではじめてワークフローは「タイムアウトイベント」を発行します。
よって、このようにワークフローを設計し、誰も取らなかった場合はタイムアウトを起こさせることで、誰も取らなかったことを検知します。
タイムアウトイベントの取得とその後の処理
では、ワークフローのイベントはどのように取得すればよいでしょうか。
答えは、管理コンソールのワークスペース(ワークフローではないです)のSettingページに設定があります。
このURLに対して、TaskRouterの各イベントが通知されるので、ここで、workflow.timeout
イベントを取得すればよいです。
取得したあとはどうする
タイムアウトイベントには、現在キューに入っているコールのCallSidがタスクの属性(TaskAttributes)に入っているので、それを使ってコールのUpdateを行います。すなわち、別のTwiMLにリダイレクトするわけです。
例えばTwilio Functionsで実装すると以下のようになります。
exports.handler = async function (context, event, callback) {
console.log(`🐞 event callback.`);
console.dir(event);
if (event.EventType === "workflow.timeout") {
// キューに入っているコールをリダイレクト
const client = context.getTwilioClient();
const call = await client
.calls(JSON.parse(event.TaskAttributes).call_sid)
.update({
method: "POST",
url: `https://${context.DOMAIN_NAME}/no-worker`, // ここでリダイレクト先のTwiMLを返す
});
console.log(`🐞 call update. ${call.sid}`);
callback(null, {});
} else {
callback(null, {});
}
};
リダイレクト先のFunctionsはこんな感じになります。
exports.handler = async function (context, event, callback) {
const client = context.getTwilioClient();
const twiml = new Twilio.twiml.VoiceResponse();
// 不在のメッセージを流す
twiml.say(
{
language: "ja-JP",
voice: "Polly.Mizuki",
loop: "1",
},
"申し訳ございませんが、ただいま対応できるオペレータが不在です。お手数ですが、しばらくしてからおかけ直しください。"
);
callback(null, twiml);
};
今回はメッセージを流して切断されますが、たとえばDial
などをつかって別の電話に転送させたり、Record
を使って録音させたりすることもできます。
まとめ
Twilio Flexをカスタマイズする際には、TaskRouterは避けては通れない機能となります。今回のように、ちょっと違う使い方をする場合などは、TaskRouterのイベントコールバックは色々と役に立ちます。
workflow.timeout
以外にも様々なイベントが通知されるので、TaskRouterのRestAPIや、VoiceのRestAPIなどをうまく利用することで、色々な処理を組み込むことができます。
★次の記事
Twilio Flexの始め方(プラグイン準備編)
Twilio(トゥイリオ)とは
https://cloudapi.kddi-web.com
Twilio は音声通話、メッセージング(SMS /チャット)、ビデオなどの 様々なコミュニケーション手段をアプリケーションやビジネスへ容易に組み込むことのできるクラウド API サービスです。初期費用不要な従量課金制で、各種開発言語に対応しているため、多くのハッカソンイベントやスタートアップなどにも、ご利用いただいております。