Node.js
Facebook
Line
Bluemix
Watson

Watson チャットボットの作り方 第1回目

More than 1 year has passed since last update.

第一回目は、チャットボット開発のベースとして、LINE や Facebook からメッセージを送ってオウム返しに答える最もシンプルなチャットボットを作ります。これから段階的に発展させていき、Watson が多人数と同時に対話できるチャットボットの完成を目指します。ゴールまでたどり着けるか心配ですが、頑張って進めていきたいと思います。

このサーバーサイドのアプリは、Node.js で動作する JavaScript で開発して、Bluemix の CloudFoundry アプリ(以下 BMX-CFA)として、または Bluemix Infrastructure の仮想サーバー(BMX-VSI) の両方で動作する様にしていきます。 ここで両方の環境を考慮する理由は、参考資料(1) Bluemix CFアプリをスクラッチからのコードを書く時のメモ に詳しく書きましたが、一言で表現すると、初期段階で Bluemix で開発すると不便だから、という理由です。 えっ! IBMの宣伝メッセージと逆じゃない!と怒られるかもしれませんが、Bluemix のDevOps環境は便利で生産性が高いです。しかし全ての段階において Bluemix で推進しようとするとストレスを感じます。

このことから、コードのロジックを考えて試す初期段階の部分は、端末からBMX-VSIをアクセスして推進し、Gitでコードを共有管理して、BMX-CFAでステージングと本番環境を提供するという開発の流れを考えます。 このために BMX-VSI と BMX-CFA の両方を考慮します。 BMX-VSIと書きましたが HTTPSアクセスの必要がなければ、vagrant の環境でも良いと思います。


オウム返しチャットボットの動作

今回はメッセージをオウム返しするだけの単純なチャットボットで開発する基礎を確かめます。動作をイメージできる様にスクリーンコピーをあげておきます。 Facebook と LINE の二つで同時にクライアントのアプリを考慮する理由は、より汎用的なチャットボットを目指すためです。これにより一つのSNSアプリに縛られないアーキテクチャーを目指すためです。 最終的には、Twitte, Slack, Amaba など 多くのSNSや、ロボットのバックエンドにするなど、汎用的なサーバーサイドアプリを目指します。

Facebookの場合

スクリーンショット 2017-05-31 8.50.38.png

LINEの場合は、メッセージだけでなく、スタンプへの反応もできます。

スクリーンショット 2017-05-31 10.32.53.png


LINE や Facebook のアカウント取得

開発を進めるには、LINE や Facebook のデベロッパーサイトにアクセスして、アカウントを取得する必要があります。 Facebook の メッセージングAPI を利用するための手続きは、参考資料(2)を参照ください。 また LINE の場合は、参考資料(3)が役立つと思います。作業を始める前に、最小どちらか一方のアカウントを取得しておく必要があります。


Bluemix のアカウント取得

Bluemix のアカウントは、このリンク https://console.ng.bluemix.net/ からクレジットカード無しで作れ、毎月の無料枠を利用して、開発も進められます。 Bluemix Infrastructure の利用は、クレジットカードの登録が必要ですが最初の1ヶ月は無料で利用できます。


アプリのリポジトリ

このサーバーサイドアプリは、GitHUB に登録してあります。 それぞれの README.md の手順に従ってセットアップすれば、Bluemix CFアプリや仮想サーバーで動作させる事が出来ます。 導入の手順はそれぞれのREADME.md を参考にしてもらい、この記事ではツールの考慮点やプログラム設計を中心に書きたいと思います。

オウム返しバージョン用に、それぞれブランチを切ってありますので、クローンする時に、-b echo を加えてください。

git clone -b echo https://github.com/takara9/chatbot-echo-line

Facebook版もURLを変えるだけで、-b echo は同じく必要です。

それから、Bluemix からも Git https://git.ng.bluemix.net/ が提供されています。Bluemix の DevOps サービスとデフォルトで連携しますから、BMX-CFA として 本番と開発のサイクルを回す段階に入れば便利です。 Bluemix Git は、GitHub と少し違いがあり、最初に少しハマったので、メモとして参考資料(4) を残しておきました。お役に立てると幸いです。 Bluemix git リポジトリの使い方のメモ http://qiita.com/MahoTakara/items/bb0a645391c61b438599


CFアプリと仮想サーバーで動作するアプリ開発のポイント

LINE や Facebook のユーザーとメッセージで対話するには、LINEやFacebook が提供する メッセージングAPIサービスに接続する必要があります。これらはRESTサービスとして提供されており、プログラミング言語から簡単にアクセスするためのAPIライブラリが、コミュニティやそれぞれの会社から開発者向けに提供されています。

スクリーンショット 2017-05-31 11.00.41.png


LINE と Facebook のAPIライブラリ

これから、使用しているAPIライブラリとチャットボットの根幹に当たるコードの部分を見ていきます。


LINE Messaging API

LINE Messaging API に接続するための Node.js APIライブラリは、LINE が Node.jsのライブラリを提供 (2017/5/11公開)する前に、チャットボットを作ったので、自作のAPIライブラリ line-msg-apiを利用しています。 このライブラリは npm に登録して一般公開していますから、誰でも npm install line-msg-api でインストールできます。

下記が、LINEのAPIサービスに接続して、メッセージを受信する部分のコードです。 とっても簡単で解りやすいと思いませんか?


/takara9/chatbot-echo-line/master/echoback_bot.js抜粋

var LineMsgApi = require('line-msg-api');  <<<--- APIライブラリを取り込み

var cnf = require('./credential.json'); <<<--- LINEから取得した認証情報をセット
<中略>
var bot = new LineMsgApi(cnf); <<<--- LINEのメッセージングサービスへ接続
<中略>
bot.on(function (msg) { <<<--- LINEからメッセージを受けた時に実行されるコールバック関数の定義

https://raw.githubusercontent.com/takara9/chatbot-echo-line/master/echoback_bot.js


Facebook Messaging API

Facebook Messaging API に接続する Node.js APIライブラリは npm に登録されている facebook-bot-messenger を利用しています。 基本的な処理の流れは同じで、簡単に把握できますよね。 これが Node.js を利用する利点でもあります。


/takara9/chatbot-echo-facebook/blob/master/echoback_bot.js

var MessengerPlatform = require('facebook-bot-messenger');  <<<--- Facebook APIライブラリ読み込み 

<中略>
var bot = MessengerPlatform.create(credentials, server); <<<--- Facebook へ接続
<中略>
bot.webhook('/webhook'); <<<--- Webhook アドレスを設定
bot.on(MessengerPlatform.Events.MESSAGE, function(userId, message) { <<<--- メッセージ受信時の処理定義
console.log("get message");

https://raw.githubusercontent.com/takara9/chatbot-echo-facebook/master/echoback_bot.js


開発環境と実行環境整備のポイント


BMX-VSIで Node.js を実行する時の考慮点

今回、開発の初期段階は、BMX-VSI を利用して、本番運用に入り Dev-Ops サイクルを回す段階は BMX-CFA を利用するという構想で進めていますから、まず BMX-VSIの開発環境を整備について、書いていきます。

BMX-VSIの仮想サーバーのオーダー方法は、参考資料(5)が手助けになると思います。 Linux OS の選択ですが、Bluemix の CloudFoundry アプリのベースとなっている Buildpackは、Ubuntu14.04 をベースにしていますから、仮想サーバーのLinuxディストリビューションは、Ubutu14.04 が無難と思います。

次に、Node.js のバージョンですが、BMX-CFAの環境では 6.x台が提供されており、一方、Ubuntu14.04 にバンドルされるバージョンは 0.11 など古く、OSの環境だけでは、自由に変更できない課題があります。 そこで、ndenv という Node.js の実行環境をディレクトり毎に変更できるツールを利用して、開発環境とBMX-CFAのバージョンを合わせていきます。 この ndenv の設定や基本的使い方は、参考資料(8) が手助けになると思います。


Bluemix CLI の利用

BMX-CFA のコードを開発してデプロイするためには、Bluemix CLI (Command Line Interface) が必要になります。 アプリケーションのコードを Bluemix へ転送して、CFコンテナの環境をデプロイの作業は、Bluemix の ウェブからは実行できないためです。 Bluemix CLI のインストールは、参考資料(6)が手助けになると思います。

作業の前に、Bluemix CLI をインストールして、ログインまでを確認しておくと良いでしょう。


CloudFoundry アプリとして実行するまでに仕組み

CloudFoundry でアプリを動作させる場合、中身があまり判らなければ、問題が起きた時に対処に困る恐れがあり、修羅場をくぐってきたエンジニアは後で苦労しないために、利用を避けたくなるのが習性です。 そこで、自分自信の拒絶反応を克服するためにも、何を実行しているか確認してみました。CloudFoundry の動作に関する仕組みを理解するには、参考資料(7)がとてもお勧めです。

次の図の左側が、作業用の環境で、Bluemix CLI コマンドを実行するパソコンなどです。 そして、右側が Bluemix CloudFoundry の環境です。 git からクローンしてきたディレクトリで、bx cf push を実行してデプロイを開始します。 すると次の箇条書きのステップが進んでいきます。



  • bx cf push は、同一フォルダのファイルを Bluemix へアップロードします。


  • Detect では、アップロードされたファイルを検査して、プログラム言語を検知して、ビルドパックを選択します。


  • Compile は、選択されたビルドパックにアプリを実行するために必要なソフトウェアのパッケージをイントールします。 Node.js の場合は、npm install が実行され、ランタイム環境として droplet が作成されます。 このため npm install で必要なモジュールとバージョンの情報が入った package.json は必須です。


  • Release では droplet を CloudFoundry コンテナとして実行します。

スクリーンショット 2017-05-31 14.37.17.png


デプロイ・プロセスでの問題対処

BMX-CFAは、TCPポートを開いて、外部からの要求を受け取り処理を実行するものです。このTCPポートは、Bluemix のプラットフォームが空いているポートをCFアプリにアサインします。そこでBMX-VSIの開発環境からBMX-CFAへデプロイする際に、この機能に起因した 問題が発生する事があります。この例を挙げて、コード上での対処方法を書きたいと思います。

bx cf push の実行終盤で下記の様なメッセージを連発して、デプロイが失敗するケースがあります。

       └── superagent@2.3.0

Exit status 0
Staging complete
Uploading droplet, build artifacts cache...
Uploading build artifacts cache...
Uploaded droplet (19.2M)
Uploading complete

1 個の中の 0 個のインスタンスが実行中です, 1 個が開始中です
1 個の中の 0 個のインスタンスが実行中です, 1 個が開始中です
1 個の中の 0 個のインスタンスが実行中です, 1 個が開始中です
<中略>
1 個の中の 0 個のインスタンスが実行中です, 1 個が開始中です
1 個の中の 0 個のインスタンスが実行中です, 1 が異常終了しました
失敗
開始は失敗しました

このケースで、cf logs line-chatbot --recent を使ってログをダンプ結果が以下です。

2017-05-31T14:02:21.79+0900 [CELL/0]     OUT Creating container

2017-05-31T14:02:23.34+0900 [CELL/0] OUT Successfully created container
2017-05-31T14:02:26.52+0900 [CELL/0] OUT Starting health monitoring of container
2017-05-31T14:02:27.83+0900 [APP/0] OUT > line-echoback-bot@1.0.0 start /home/vcap/app
2017-05-31T14:02:27.83+0900 [APP/0] OUT > node ./echoback_bot.js
2017-05-31T14:02:28.12+0900 [APP/0] OUT Listening on port 8080
2017-05-31T14:03:27.40+0900 [CELL/0] ERR Timed out after 1m0s: health check never passed.
2017-05-31T14:03:38.44+0900 [CELL/0] OUT Destroying container
2017-05-31T14:03:38.49+0900 [API/4] OUT App instance exited with guid fc32eb9f-e0aa-4bff-8107-fbb45a6f6ff9 payload: {"instance"=>"", "index"=>0, "reason"=>"CRASHED", "exit_description"=>"2 error(s) occurred:\n\n* 1 error(s) occurred:\n\n* Exited with status 4\n* 2 error(s) occurred:\n\n* cancelled\n* process did not exit", "crash_count"=>2, "crash_timestamp"=>1496207018414686729, "version"=>"c60214e4-d176-4930-bd54-2b22d18efd0f"}

この中で "ERR Timed out after 1m0s: health check never passed."が、失敗の原因となっている事がログの経過から判別できます。

この問題は、アプリケーションが、Bluemix が割り当てたポートを利用していないため、ヘルスチェックへの応答が無く、稼働が確認できなかった事を意味しています。この問題を回避するためには、CloudFoundry がアプリに提供する環境変数に従って、サービスポートを開く必要があります。 facebook のボットの例では、環境変数 PORT から番号を取得しています。


echoback_bot.js

// Bluemix で稼働する場合はポート番号を取得

var portno = process.env.PORT || 9080;
console.log("Listening on port ", portno);

server.listen(portno);


このコードの || は論理和を表すのでは無く、||より前に値があれば、それを返します。 もし NULL など未定義であれば、|| の後の値を返すものです。 CFの環境変数PORTが存在していれば、portno にセットされます。 一方、仮想サーバー環境では、CFの環境変数が定義されていませんから、9080がportnoへセットされます。 これで、Bluemix CFアプリ と 仮想サーバーの両方で、ポートの設定が適切に実行される様になります。


Node.js 実行モデル との関係

LINE や Facebook のメッセージングシステム は、Webhook と呼ばれる アプリからメッセージの送信があった場合、前もって登録されたURLアドレスにPOSTでメッセージを即時通知する仕組みを持っています。この動作イメージを表わしたのが、次の図の様です。

スクリーンショット 2017-05-31 17.14.34.png

チャットボットは、この Webhook のURLアドレスで、POSTの受信待ちをしており、POSTが発生すれば、コールバック関数呼び出し応答の処理に入ります。この部分のコードを抜き出して、注釈すると以下になります。 このプログラムで難しい部分は、上から下に向かって順番に実行するものでは無い点です。 bot.on は、通常は実行されず、外部から /webhookのアドレスに、HTTP POSTされた場合に呼び出されます。この様なプログラムの実行方式は、イベント駆動と呼ばれており、Windows や MacOS のユーザー・インターフェースも マウスでクリックされるなどのイベントが発生したら、その部分に対応するプログラムが実行されるというイベント駆動を実装しています。


echoback_bot.js

    // for Bluemix CF runtime

server = http.createServer(); <<<--- HTTPサーバーを開設する
}

// メッセージ
var bot = MessengerPlatform.create(credentials, server); <<<--- イベントの処理を定義

// コールバック
bot.webhook('/webhook'); <<<--- このURLが呼ばれた時に bot.on が呼び出される

// メッセージ到着
bot.on(MessengerPlatform.Events.MESSAGE, function(userId, message) {
console.log("get message");
console.log("UserID ", userId);
console.log("Message ", message.getText());
console.log("Message Id ", message.getMessageId());
console.log("Message Type ", message.getType());
// エコー応答
bot.sendTextMessage(userId,message.getText()); <<<--- 受けたメッセージを そのまま返信する。
});


bot.on のカッコの中に、もう一つ関数が定義されているのが解ります。 Node.jsは ノンブロッキングI/Oモデルを採用しています。 単純に説明すると、I/Oは遅いので、I/Oが発生する部分は後回しに実行するという事です。 イベントが発生して bot.on の関数が呼び出されると、外部からのデータ受信するI/Oが必要な部分と、それ以外の部分に別れて、2つのスレッドで並行に実行されるという事です。 bot.on の中にもう一つ定義されている function から始まる関数をコールバック関数と呼ばれ、I/Oの完了を待って実行されます。 このため、ここで注意しなくてはならないポイントとして、bot.on() の下に次の処理を書いても、bot.on()は、受信完了前に処理を抜けてくるので、メッセージの受信や応答は出来ないという事です。このため、不慣れでもコールバック関数の中にメッセージ受信後の処理を書かなければなりません。

Node.js は、この様な実行モデルによって、大量のセッションを並列に効率良く処理できます。 外部からのイベントが発生すると、専用のスレッドが起こされますから、処理終了を待たずに、次のイベントを受け取り次の処理には入れます。 このためCPUコア数の多いサーバーでは、プログラミング技術を駆使してコアを均等に使う工夫が必要なく、Node.js は マルチコアの資源を有効活用できるプログラミング言語と言う事ができます。

例えば、Watson Conversation と接続する場合などは、bot.onのコールバック関数に記述すれば良いという事です。


Webhook の暗号化通信対応

LINE や Facebook の仕様として、Webhook で呼び出される サーバーポートは、秘匿性を確保するために、HTTPSとなっています。 BMX-CFAの場合は、アプリケーションは、非暗号ポートを開いても、Bluemix の プラットフォームが、HTTPS に変換して、暗号化通信してくれます。

スクリーンショット 2017-05-31 18.32.30.png

一方、BMX-VSIでは、前段にHTTPS機能付きのロードバランサーを入れない限り、暗号化と復号はプロセス側で実施する必要があり。クラウドといっても、ロードバランサーやデジタル証明書を利用すると無駄なコストが発生するので避けたいと思うのが自然でしょう。

スクリーンショット 2017-05-31 18.32.37.png

このチャットボットでは、BMX-VSIで利用する場合は、次の様に、httpsの項目が存在するとHTTPサーバーをたて、定義がなければ、HTTPサーバーを立てる様に切り替えています。


chatbot-echo-facebook/echoback_bot.js

if ( credentials.https ) { 

// for Virtual Server
server = https.createServer({
key: fs.readFileSync(credentials.https.key),
cert: fs.readFileSync(credentials.https.cert)
});
} else {
// for Bluemix CF runtime
server = http.createServer();
}

このコードの中で、key と cert 部分に必要な デジタル証明書と鍵のPEMファイルは、参考資料(10)に無料で取得する方法があります。デジタル証明書の前提条件となるDNS名の無料の取得は参考資料(9)が手助けになります。


オウムチャットボット実行


BMX-VSI (仮想サーバー) で動作させた場合

仮想サーバーにSSHでログインして、コマンドラインから npm start で開始します。または、次のスクリーンコピーの様にコマンドで実行でも良いでしょう。

ターミナルから開始と停止、デバッグ文を入れて動作を確かめるなどができます。特にデバック出力をリアルタイムに参照でき点など、スマホのアプリの操作とサーバーサイドの処理の繋がりなど、効率良くデバッグする事に適していると思います。

スクリーンショット 2017-05-31 9.53.40.png

以下が、スマホ端末側の操作です。

スクリーンショット 2017-05-31 9.51.02.png


BMX-CFA (CloudFoundry) コンテナで動作させた場合

本番運用しながら、継続的に改善を実施するのであれば、この環境は最適と思います。 各アプリの様稼働状態をリストで確認できます。クリックで起動・停止・再起動などができ、Git との連携や品質確認サーバー、本番サービスサーバーなどのDevOpsのパイプラインを Bluemix 上で運用し、複数スタッフのチームで運用と開発が推進できるからです。

スクリーンショット 2017-05-31 9.12.15.png

上記リストの名前の部分をクリックすることで、メモリの割り当て、インスタンス数などを把握できます。

スクリーンショット 2017-05-31 9.14.06.png


まとめ

今回は最初の1回目として、仮想サーバーの開発環境 及び、PaaS環境を 対比しながら、チャットボットの基本構造や考慮点について書きました。 話がインフラに飛んだり、コードに飛んだりと解りくいと感じたかも知れませんね(汗) 書いていて、いろいろ伝えたい事もあり、迷っていまいました。

聖徳太子は、同時に10人の話を聞き理解したと言う伝説がありますが、次は、一つのチャットボットアプリが、同時に多人数と会話するワトソンくんの聖徳太子化について書きたいと思います。


参考資料

(1) Bluemix CFアプリをスクラッチからのコードを書く時のメモ http://qiita.com/MahoTakara/items/660f973c74c1f3682eaa

(2) ジャスト30分!FBメッセンジャーbotをとりあえず作ってみる手順まとめ https://bita.jp/dml/facebookbot_exp

(3) LINE BOTの作り方を世界一わかりやすく解説(1)【アカウント準備編】http://qiita.com/yoshizaki_kkgk/items/bd4277d3943200beab26

(4) Bluemix git リポジトリの使い方のメモ http://qiita.com/MahoTakara/items/bb0a645391c61b438599

(5) CHANGE MAKERS 1.2 仮想サーバーを起動するには? https://www.change-makers.jp/docs/softlayer-config-guide/10292

(6) Bluemix CLIホーム https://clis.ng.bluemix.net/ui/home.html

(7) Cloud Foundryで学ぶ、PaaSのしくみ講座 https://www.slideshare.net/jacopen/paas-for-beginners

(8) node.js のバージョン管理ツール ndenv を試した http://qiita.com/MahoTakara/items/8fdebe32e8f326afa7f8

(9) 私的MyDNS.JP https://www.mydns.jp/

(10) Let's Encrypt 総合ポータル https://letsencrypt.jp/

(11) npm facebook-bot-messenger https://www.npmjs.com/package/facebook-bot-messenger

(12) line-msg-api https://www.npmjs.com/package/line-msg-api