twilio
駅すぱあと

Twilioと駅すぱあとを使って、音声で経路探索を行う

はじめに

経路探索で有名なヴァル研究所の「駅すぱあと」と、コミュニケーションAPIの「Twilio」を組み合わせることで、音声を使って経路探索を行い、その結果をSMSを受け取る仕組みを作成します。
完成動画

なお、本記事は2018年9月1日現在の情報に基づいて作られています。

準備

本ハンズオンには、以下のアカウントが必要です。

  • 駅すぱあとアカウント
    • こちらのリンクから、フリープラン(無料)のAPIキーを申請してください。
    • 申請後、一両日中にフリープランのAPIキーがメールで届きます。
  • Twilioアカウント
  • Google Cloud platformアカウント
    • こちらのリンクから、無料トライアルアカウントを作成してください。作成には、クレジットカードが必要です。
    • アカウントができたら、プロジェクトを作成して、Cloud Natural Language APIを有効にします。
    • さらに、このAPIを利用するためのAPIキーを生成してください。こちらのAPIキーを後ほど利用します。

全体のフロー

今回作成するコールフローは以下のようなものです。
スクリーンショット 2018-08-31 11.43.33.png

  1. 電話が着信します。
  2. 応答メッセージが流れるので、どこからどこに行きたいかを音声で話してもらいます。
  3. 音声認識した結果を、Googleの自然言語分析にかけて、駅名にあたる部分を抽出します。
  4. 駅すぱあと WebAPIを利用して、経路を探索します。
  5. 探索結果のURLを短縮します(URLが長いとSMSで送れないため)。
  6. 短縮URLをSMSで送信します。

ハンズオン

ログイン&電話番号の購入

手順1. Twilioの管理コンソールにログインします。

スクリーンショット 2018-05-04 10.13.08.png

今回のハンズオンでは、ログインIDはすべて共通となります。パスワードを間違えると他の方がロックアウトされてしまうので、間違えないようにしてください。
また、二要素認証を有効にしないように注意してください。

  • 右上の歯車アイコンをクリックし、Subaccountsを選択します。
    スクリーンショット 2018-05-04 10.14.46.png

  • サブアカウントの一覧が表示されたら、ご自分のユーザ名(userXX)の右側にあるサブアカウントを表示をクリックします。

  • 右上にご自分のユーザ名が表示されることを確認します。

  • ブラウザを閉じてしまった場合やログアウトしてしまった場合は、再度サブアカウントにログインしてください。

手順2. 着信用電話番号を購入します。

  • 管理コンソールの左側にあるボタンアイコンを押すと、スライドメニューが表示されます。

002 (1).png

  • スライドメニューの一覧から、電話番号を選択します。

005 (1).png
- Phone Numbersメニューの中のBuy a Numberを選択します。
- 国のプルダウンから「Japan(+81)」を選択し、検索ボタンを押します。

006.png

  • 一覧表示されたリストの中から、TYPEがローカルになっている(108円)の番号を一つ選び、購入ボタンを押します。
  • この番号を購入しますか?というダイアログが出たら、この番号を購入する。を押します。
  • Congratulationのダイアログが表示されたら、購入完了です。閉じるボタンを押します。
  • Phone Numbersの中のManage Numbersを選択し、今購入した電話番号が表示されることを確認します。購入した電話番号は後ほど使いますので、メモ帳などに控えておきます。

手順3. SMS送信用電話番号を購入します。

  • Phone Numbersメニューの中のBuy a Numberを選択します。
  • 国のプルダウンから「United States(+1)」を選択し、さらに、SMSのチェックを入れてから検索ボタンを押します。
  • 一覧表示されたリストの中から、TYPEがローカルになっている(150円)の番号を一つ選び、購入ボタンを押します。
  • この番号を購入しますか?というダイアログが出たら、この番号を購入する。を押します。
  • Congratulationのダイアログが表示されたら、購入完了です。閉じるボタンを押します。
  • Phone Numbersの中のManage Numbersを選択し、今購入した電話番号が表示されることを確認します。こちらの電話番号も後ほど使いますので、メモ帳などに控えておきます。
電話番号の表記方法について
Twilioは、世界100カ国と接続されていて、それぞれの国に直接電話をかけることができます。
そのため、発信・着信の電話番号は、全世界で利用可能な「E.164形式」と呼ばれる表記方法を使います。
E.164形式とは、先頭が+から始まる国番号と電話番号の組み合わせです。
例えば、日本は国番号が+81となっており、その後の電話番号を続けて記述します。
※電話番号の先頭の0は削除します。
「09012345678」は、E.164形式だと「+819012345678」となります。

自然言語解析で出発点と到着点を抽出する

Twilioには、会話中に音声を文字に変えるためのSpeech Recognition機能があり、それを利用することで例えば「銀座から渋谷まで」といった文字列に変換することができます。
ただ、この文字列から「銀座」とか「渋谷」を抜き出すことはできないため、今回はGoogleのNatural Language APIを利用して、自然言語の中から場所を抜き出します。

手順1. Google APIキーの設定

このAPIを利用するためには、予めGoogle Cloudのアカウントと、Cloud Natural Languageのサービス有効化、さらにはAPIキーが必要になります。
このあたりの手順については、こちらのドキュメントを参照してください。

  • スライドメニューから、Runtimeを選択します。

003 (1).png

  • Runtimeメニューの中からFunctionsを選択し、さらにConfigureを選択します。

004 (1).png

  • Enable ACCOUNT_SID and AUTH_TOKENのチェックボックスをONにします。

スクリーンショット 2018-07-03 10.06.47.png

  • Environment Variablesの赤いプラスアイコンを2回押して、以下の変数を設定します。
KEY VALUE
GOOGLE_API_KEY 取得したGoogleAPIキーを入力してください
NEAR_STATION 自分の自宅、もしくは会社の最寄り駅を入れてください(例:外苑前)
  • Dependencies項目の赤いプラスボタンを1回押して、以下の値を設定します。
NAME VERSION
request-promise 4.2.2
  • さらに、twilioのバージョンを「3.19.1」に変更します。
  • 設定が完了したら、Saveボタンを押します。

駅すぱあと Dependencies.png

手順2. Functionの作成

  • 管理コンソールにスライドメニューを開き、Runtimeを選択します。

007 (1).png
- Runtimeメニューの中から、Functionsを選択します。
- Create a Functionボタンを押して、新規にFunctionを作成します。
- New Functionダイアログが表示されますので、Blankを選択して、Createボタンを押します。
- FUNCTION NAMEに、「GoogleNLApi」と入力します。
- PATHに、「/google-nl-api」(すべて小文字)と入力します。
- ACCESS CONTROLのチェックはOFFにします。
- EVENTはそのままでOKです。
- CODEに書かれているデフォルトのコードを削除し、以下のコードを貼り付けます。

const request = require('request-promise');

exports.handler = function(context, event, callback) {
    console.log(`event.text: ${event.text}`);  // 元の文字列
    var options = { 
        method: 'POST',
        url: 'https://language.googleapis.com/v1/documents:analyzeEntities',
        qs: { key: context.GOOGLE_API_KEY },
        headers: { 
            'cache-control': 'no-cache',
            'content-type': 'application/json' 
        },
        json: { 
            document: { 
                type: 'PLAIN_TEXT', 
                content: event.text || '', 
                language: 'ja-JP' },
            encodingType: 'UTF8' },
    };

    request(options)
    .then((parsedBody) => {
        console.log(parsedBody);
        console.log(`${parsedBody.entities.length} entities.`);
        for (const entity in parsedBody.entities) {
            console.log(`${parsedBody.entities[entity].name}`);
        }
        if (parsedBody.entities.length == 1) { // 場所が1つしか抽出できなかった場合は、出発駅をNEAR_STATIONにする
            callback(null, {from:context.NEAR_STATION,to:parsedBody.entities[0].name});
        } else if (parsedBody.entities.length > 1) {  // 2つ以上の場所が抽出できた場合は、最初を出発駅、次の到着駅にする
            callback(null, {from:parsedBody.entities[0].name,to:parsedBody.entities[1].name});
        } else {
            callback(null, {from:'',to:''});
        }
    })
    .catch((err) => {
        console.error(`problem with request: ${err.message}`);
        callback(err.message);
    });

};
  • 「Save」ボタンを押して、保存とデプロイを行います。

手順3. テスト

  • PATH欄の右側にあるコピーアイコンを押して、URLをコピーします。
  • ブラウザの別のタブを開き、今コピーしたURLを貼り付けます。
  • URLの最後に?text=銀座から渋谷までと付加して実行します。
  • 次のようなjsonが戻れば成功です。

スクリーンショット 2018-08-31 10.03.29.png

駅すぱあとWebサービスAPIを使って経路探索する

「駅すぱあとWebサービス」とは、株式会社ヴァル研究所が提供する「駅すぱあと」経路検索の機能を、Webサイトやモバイルアプリなどに組み込み可能なクラウド型APIです。
メンテナンス不要で、常に最新情報をAPIで提供し、保守費用を軽減します。また、「駅情報」や「路線情報」などは無料のフリープランで商用利用可能です。
フリープランで利用できるAPIは以下の通りです。

分類 API 説明
探索 駅すぱあと for web URL生成 探索結果を表示するためのURLを生成します。
駅の情報 駅情報 指定された条件に当てはまる駅の情報を返します。
駅簡易情報 このAPIは主に候補駅の確定を想定しているため、必要最低限の簡易データのみ返します。
付加情報 駅付加情報 指定した駅の付加情報を取得します。
路線の情報 運行路線情報 「駅すぱあと」の保持する運行路線の情報(鉄道のみ)を取得します。
平均路線情報 「駅すぱあと」の保持する平均路線の情報を取得します。
会社の情報 会社情報 交通機関の会社情報を取得します。

フリープランのドキュメントは、こちらを参照してください。

今回は、この中の「駅すぱあと for web URL生成」のAPIを利用します。

リクエストURL

GET /v1/{format}/search/course/light

パラメータ

Name Type Required Description
format string レスポンスのデータ形式('xml' or 'json')の指定。
key string アクセスキー。
from string --- 出発駅(地)。駅コード、駅の名称を指定可能です。
to string --- 到着駅(地)。駅コード、駅の名称を指定可能です。
via string --- 経由駅(地)。駅コード、駅の名称を指定可能です。省略可。省略時は指定なしとなります。
date int --- 探索日付。省略可。
searchType string --- 探索種別。省略可。
plane string --- 飛行機。省略可。
shinkansen string --- 新幹線(のぞみ含む)。省略可。
limitedExpress string --- 特急。省略可。
redirect string --- 結果をリダイレクトするかどうか。省略可。
contentsMode string --- 生成するURLの種類。省略可。

手順1. APIキーの設定

  • Runtimeメニューの中からFunctionsを選択し、さらにConfigureを選択します。
  • Environment Variablesの赤いプラスアイコンを1回押して、以下の変数を設定します。
KEY VALUE
EKISPERT_API_KEY 取得した駅すぱあとWebサービスAPIキーを入力してください
  • 設定が完了したら、Saveボタンを押します。

手順2. Functionの作成

  • Runtimeメニューの中から、Functionsを選択します。
  • Create a Functionボタンを押して、新規にFunctionを作成します。
  • New Functionダイアログが表示されますので、Blankを選択して、Createボタンを押します。
  • FUNCTION NAMEに、「EkispertApi」と入力します。
  • PATHに、「/ekispert-api」(すべて小文字)と入力します。
  • ACCESS CONTROLのチェックはOFFにします。
  • EVENTはそのままでOKです。
  • CODEに書かれているデフォルトのコードを削除し、以下のコードを貼り付けます。
var request = require("request-promise");

exports.handler = function(context, event, callback) {
    const from = event.from || '';
    const to = event.to || '';
    console.log(`from: ${from} to:${to}`);
    var options = { 
        method: 'GET',
        url: 'https://api.ekispert.jp/v1/json/search/course/light',
        qs: { key: context.EKISPERT_API_KEY, from: from, to: to },
        headers: { 
            'cache-control': 'no-cache' } 
    };

    request(options)
    .then(body => {
        console.log(body);
        callback(null, JSON.parse(body).ResultSet.ResourceURI);
    })
    .catch(error => {
        callback(error, 'Error');
    });
};
  • 「Save」ボタンを押して、保存とデプロイを行います。

手順3. テスト

  • PATH欄の右側にあるコピーアイコンを押して、URLをコピーします。
  • ブラウザの別のタブを開き、今コピーしたURLを貼り付けます。
  • URLの最後に?from=銀座&to=渋谷と付加して実行します。
  • 次のようなURLが戻れば成功です。

スクリーンショット 2018-08-31 10.13.57.png

  • 表示されたURLをコピーしてブラウザで表示すると、銀座から渋谷までの経路が表示されます。

探索結果のURLを短縮する

駅すぱあとWebサービスAPIを使うことで、経路情報のURLを取得することができましたが、取得したURLが長すぎるため、このままではSMSで送ることができません。そのため、URLを短縮する仕組みを作ります。
短縮URLは、ランダムな短い文字列と、元々のURLの2つを、Key/Value形式でデータベースに格納したいと思います。Twilioにはデータベース機能はありませんが、Twilio Syncというサービスがあります。

<参考> Twilio Syncとは

  • Twilio Sync
  • 複数のデバイス間で情報を同期させるためのサービス
  • WebSocketやSocket.ioのようなリアルタイム通信プラットフォーム
  • Syncは、受け取ったデータをTwilio上に保存してから配信する
  • Sync上のデータは、明示的に削除しない限り消えない
  • 月10,000アクションまでは無料、それ以降は1000アクション単位で1.5円
  • Documents / Lists / Maps / Message Streams の4種類のデータを扱うことが可能

今回は、Twilio SyncのMapsオブジェクトを使って、短縮URLを管理したいと思います。

手順1. Functionの作成

  • Runtimeメニューの中から、Functionsを選択します。
  • Create a Functionボタンを押して、新規にFunctionを作成します。
  • New Functionダイアログが表示されますので、Blankを選択して、Createボタンを押します。
  • FUNCTION NAMEに、「MakeShortUrl」と入力します。
  • PATHに、「/make-short-url」(すべて小文字)と入力します。
  • ACCESS CONTROLのチェックはOFFにします。
  • EVENTはそのままでOKです。
  • CODEに書かれているデフォルトのコードを削除し、以下のコードを貼り付けます。
exports.handler = function(context, event, callback) {
    const longUrl = event.url || '';
    if (longUrl === '') {
        callback(null, 'Error');
    }

    // 短縮URLを生成
    const key = Math.random().toString(36).slice(-6);
    const shortUrl = `https://${context.DOMAIN_NAME}/result?key=${key}`;

    let sync = Runtime.getSync();
    let payload = {
        url: longUrl,
    };

    sync.maps.create({  // Mapsオブジェクトを作成
        uniqueName: 'url_map'
    })
    .then(response => {
        console.log(`Sync map url_map created.`);
    })
    .catch(error => {   // Mapsオブジェクトがすでにある場合
        console.log(`Sync map url_map already exists.`);
    })
    .then(() => {   // データの書き込み
        return sync.maps('url_map').syncMapItems.create({
            key: key,
            data: payload
        });      
    })
    .then(response =>{  // 書き込み成功
        console.log(response);
        callback(null, shortUrl);
    })
    .catch(error => { // 書き込み失敗
        console.log(error);
        callback(error);
    });
};
  • 「Save」ボタンを押して、保存とデプロイを行います。

手順2. テスト

  • PATH欄の右側にあるコピーアイコンを押して、URLをコピーします。
  • ブラウザの別のタブを開き、今コピーしたURLを貼り付けます。
  • URLの最後に?url=abcdefgfと付加して実行します。
  • 次のようなURLが戻れば成功です。

スクリーンショット 2018-08-31 10.52.52.png

短縮URLをもとに経路結果ページにナビゲートする

では次に、今の短縮URLがクリックされた時に、元の長いURLにリダイレクトするためのFunctionを作っていきます。
先程のTwilio Sync Mapsオブジェクトを検索して元のURLを取得します。

手順1. Functionの作成

  • Runtimeメニューの中から、Functionsを選択します。
  • Create a Functionボタンを押して、新規にFunctionを作成します。
  • New Functionダイアログが表示されますので、Blankを選択して、Createボタンを押します。
  • FUNCTION NAMEに、「Result」と入力します。
  • PATHに、「/result」(すべて小文字)と入力します。
  • ACCESS CONTROLのチェックはOFFにします。
  • EVENTはそのままでOKです。
  • CODEに書かれているデフォルトのコードを削除し、以下のコードを貼り付けます。
exports.handler = function(context, event, callback) {
    const key = event.key || '';
    if (key === '') {
        callback(null, 'Error');
    }

    const response = new Twilio.Response();
    let sync = Runtime.getSync();
    sync.maps('url_map').syncMapItems.get(key).fetch()
    .then(res => {  // 正しくURLが取得できた 
        console.log(res.data.url);
        response.appendHeader('Location', res.data.url);
        callback(null, response);
    })
    .catch(err => {  // URLの取得に失敗
        console.log(err);
        callback(null, 'Error');
    });

};
  • 「Save」ボタンを押して、保存とデプロイを行います。

Twilio Studioを使ってコールフローを作成する

部品の準備が整いましたので、いよいよフローを作っていきたいと思います。
念の為、最初にご紹介した今回のフローのサンプルを再度載せておきます。

スクリーンショット 2018-08-31 11.43.33.png

手順1. フローを作成する

  • 管理コンソールの左側のスライドメニューから、Studioを選択します。
  • 赤いプラスアイコンをクリックするか、「Create a new flow」を選択して、新しいフローを作成します。
  • FLOW NAMEに「Ekispert」と入力して、NEXTボタンを押します。
  • New Flowダイアログが表示されるので、Start from scratchが選択されていることを確認して、Nextボタンを押します。
  • Triggerボックスのみが表示されたフローが作成されます。

手順2. 応答メッセージと駅名の問い合わせ

  • キャンパスが表示されたら、WIDGET LIBRARYからSay/Playをキャンパス上にドラッグアンドドロップします。
  • TriggerボックスのIncoming Callと、今配置した「say_play_1」ボックスの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「SayHello」、SAY OR PLAY MESSAGE欄は、「Say a Message」を選択します。
  • TEXT TO SAY欄に、「はい。こちらは、エキスパートです。」と入力します。
  • LANGUAGEのプルダウンリストから「Japanese」を選択します。
  • MESSAGE VOICEのプルダウンリストから好みの音声を選択します。
  • SAVEボタンを押します。
  • 続いて、WIDGET LIBRARYからGather Input On Callウィジェットをキャンパス上にドラッグアンドドロップします。
  • 先程作成したSayHelloボックスのAudio Completeと、今配置した「gather_input_on_call_1」ボックスの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「WhereWillYouGo」、SAY OR PLAY MESSAGE欄は、「Say a Message」を選択します。
  • TEXT TO SAY欄に、「どこからどこに行きたいかを喋ってください。」と入力します。
  • LANGUAGEのプルダウンリストから「Japanese」を選択します。
  • MESSAGE VOICEのプルダウンリストから好みの音声を選択します。
  • SPEECH RECOGNITION LANGUAGEのプルダウンリストから「Japanese(Japan)」を選択します。
  • SAVEボタンを押します。
  • 続いて、WIDGET LIBRARYからSay/Playをキャンパス上にドラッグアンドドロップします。
  • 今作成したWhereWillYouGoボックスのNo inputと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「Retry」、SAY OR PLAY MESSAGE欄は、「Say a Message」を選択します。
  • TEXT TO SAY欄に、「すみません。うまく聞き取れませんでした。」と入力します。
  • LANGUAGEのプルダウンリストから「Japanese」を選択します。
  • MESSAGE VOICEのプルダウンリストから好みの音声を選択します。
  • SAVEボタンを押します。

  • 今作成したRetryボックスのAudio Completeと、WhereWillYouGoボックスの左上の黒丸部分を結合しておきます。

手順3. 一連のFunctionを呼び出し

  • WIDGET LIBRARYからRun Functionをキャンパス上にドラッグアンドドロップします。
  • 先程作成したWhereWillYouGoボックスのUser Said Somethingと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「GoogleNLAPI」、FUNCTION URL欄は、「GoogleNLApi」を選択します。
  • Function Parametersのプラスアイコンを押します。
  • Key欄に「text」、Value欄には「{{widgets.WhereWillYouGo.SpeechResult}}」と入力します。
  • Add Parameterをクリックして、値を保存します。
  • SAVEボタンを押します。
  • 再度、WIDGET LIBRARYからRun Functionをキャンパス上にドラッグアンドドロップします。
  • 今作成したGoogleNLAPIボックスのSuccessと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「EkispertApi」、FUNCTION URL欄は、「EkispertApi」を選択します。
  • Function Parametersのプラスアイコンを押します。
  • Key欄に「from」、Value欄には「{{widgets.GoogleNLAPI.parsed.from}}」と入力します。
  • Add Parameterをクリックして、値を保存します。
  • Function ParametersAddを押します。
  • Key欄に「to」、Value欄には「{{widgets.GoogleNLAPI.parsed.to}}」と入力します。
  • Add Parameterをクリックして、値を保存します。
  • SAVEボタンを押します。
  • 再度、WIDGET LIBRARYからRun Functionをキャンパス上にドラッグアンドドロップします。
  • 今作成したEkispertApiボックスのSuccessと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「MakeShortUrl」、FUNCTION URL欄は、「MakeShortUrl」を選択します。
  • Function Parametersのプラスアイコンを押します。
  • Key欄に「url」、Value欄には「{{widgets.EkispertApi.body}}」と入力します。
  • Add Parameterをクリックして、値を保存します。
  • SAVEボタンを押します。

手順4. SMS送信

  • WIDGET LIBRARYからSend Messageをキャンパス上にドラッグアンドドロップします。
  • 先程作成したMakeShortUrlボックスのSuccessと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「SendSMS」、MESSAGE BODY欄は、「{{widgets.MakeShortUrl.body}}」と入力します。
  • SEND MESSAGE FROM欄には、購入したUS番号を入力します。SEND MESSAGE TOはそのままでOKです。
  • SAVEボタンを押します。

手順5. 終了メッセージ

  • 最後に、WIDGET LIBRARYからSay/Playをキャンパス上にドラッグアンドドロップします。
  • 先程作成したSendSMSボックスのSentと、今配置したウィジェットの左上の黒丸部分を結合します。
  • 今ドロップしたウィジェットを選択し、WIDGET NAMEを「SayComplete」、SAY OR PLAY MESSAGE欄は、「Say a Message」を選択します。
  • TEXT TO SAY欄に、「お待たせいたしました。経路の探索が完了しましたので、結果をエスエムエスでお送りします。またのご利用をお待ちしております。」と入力します。
  • LANGUAGEのプルダウンリストから「Japanese」を選択します。
  • MESSAGE VOICEのプルダウンリストから好みの音声を選択します。
  • SAVEボタンを押します。

手順6. パブリッシュ&電話番号設定

  • 画面上部にあるPublichボタンを押します。
  • Publish Flow?ダイアログが表示されたら、Publishボタンを押します。 スクリーンショット 2018-07-13 00.11.44.png
  • 左側のスライドメニューを開き、Phone Numbersを選択します。
  • すでに購入済みの050番号を選択して、設定画面を表示します。
  • 着信時の設定で、「Studio Flow」を選択し、さらに今作成した「Ekispert」を選択します。
  • 画面下部にある保存ボタンを押します。

テスト

  • 購入した050番号に電話を掛けてみて、経路情報を正しく表示できることを確認します。
  • もしうまくいかない場合は、Studioのログを参照すると良いでしょう。
  • 以下は、正しくできた場合のログです。

スクリーンショット 2018-08-31 13.32.28.png

まとめ

今回は、自然言語解析を使って場所(駅名)を抜き出しました。
駅名によっては、全国に複数あったり、うまく抜き出せない場合などもあるので、実際にはもう少し改良が必要かと思いますが、Twilioの音声認識と、外部Webサービスとの連携によって、音声だけで色々なことができるということを体験していただければと思います。