1. はじめに
Node-REDのWebサイトには、"A visual tool for wiring the Internet of Things"と紹介されていますが、Node-REDは必ずしもIoTだけでなく、普通にWebアプリケーションのロジックを作るツールとしても優れています。
Node-REDについてblackaplysiaさんが投稿された「Bluemix+Node-REDでWatson QA」では、入出力は主にTwitterを使用していました。これをWebアプリ化し、WebブラウザからWatsonに質問し回答を受け取る簡単なアプリを作ってみましょう。
本稿でご紹介するフローの情報は記事末にありますので、実際にNode-REDにインポートして動かしてみてください。
【注意】WatsonのQuestions and Answerサービスは、2015年12月16日をもって提供終了となりました
2. 環境準備
Bluemix上にNode-REDをセットアップし、Watson Question and Answerが使えるようにバインドしてください。この手順については省略します。適宜、Bluemix+Node-REDでWatson QAを参照してください。
3. Node-REDでフロー作成
###1. debugノードで回答する
まずはWatsonに質問を投げてみます。
Watson Q&Aに質問を投げて、回答をdebugに出力するフローがこちら。
injectノードの左側のボタンをクリックすると、処理が一度実行されます。
質問文はinjectノードで定義することもできますが、ここではpayloadはblankとし、injectノードは処理の開始を行うためだけに使用しています。代わりに質問文はtemplateノードに入力しました。payloadの内容はtemplateノードで定義するのが基本です。
2. Webページで回答する
続いてWatsonからの返答をWebページに出力してみましょう。
上のフローでWatson Q&Aに質問を投げ、その回答をグローバル変数に格納しています。
下のフローではその変数の内容を受け取って、回答をhttpノードとhttp responseノードで挟んでWebに出力しています。
templateノードにはhtmlを書くことができます。
Webブラウザで https://<Node-RED URL>/wat01
にアクセスすると、Watsonからの回答が表示されます。(URLはNode-REDフローエディタのURLから、red/#
を削除し、wat01
を追加したものです)
3. URLリクエストパラメータで質問する
templateノードに静的に書かれた質問ではなく、任意の質問を投げるにはどうすればいいでしょうか。URLリクエストに対してパラメータを指定して質問文を付与するフローがこちらです。
URLリクエストパラメータというのは、URLの末尾に?org=sales
などと疑問符を付けてアプリケーションに値を渡す機能です。ここではquestionというパラメータで質問文を渡します。
WebブラウザのURL欄に https://<Node-RED URL>/wat02?question=Which is the best hotel to stay in Okayama?
と指定してください。毎度OkayamaばかりではつまらないのでGinzaでもAkihabaraでもご自由に指定してください。もちろんまったく別の質問を?question=
の後に書いてもOKです。(現時点でのWatsonの回答クオリティはともかく...)
リクエストパラメータを使うことで、Nodeの内容を変更しなくても自由に質問を投げられるようになりました。
4. 入力フォームで質問する
続いてWebアプリケーションらしく、入力フォームを使って質問するフローです。
Webブラウザで https://<Node-RED URL>/wat03
にアクセスして、フォームに質問を入力して送信すると、別のページに遷移してWatsonからの回答が表示されます。
GETメソッドとPOSTメソッドで実現する問い合わせ処理の基本形ですね。
5. 応用 : Webアプリ機能拡張
後は自由に拡張してみましょう。画面周りが寂しいのでCSSでBluemix風UIにしてみました。
Webブラウザで https://<Node-RED URL>/wat05
にアクセスしてください。
ノード配置の変更としては、functionノードの出力を複数にすることで質問文が入力されずに送信された場合に対応しました。また、質問をグローバル変数に代入して回答画面にも表示するよう仕様を変更しました。
6. おまけ : モバイルデバイス対応
JQuery Mobileでスマホやタブレットからも操作しやすいUIにしてみました。
Webブラウザで https://<Node-RED URL>/wat07
にアクセスしてください。
いかがでしょうか。
Node-REDって意外となんでもできますね!
4. サンプルフロー
ご紹介したフローの詰め合わせです。Node-REDに取り込んでみましょう。
インポート操作はマウス操作ではなく、キーボードショートカットを利用した方がずっと早くて作業が捗ります。ちなみにエクスポートは(Ctrl+E)です。是非覚えておきましょう!
- 下記jsonテキストを選択してコピー(Ctrl+C)
- Node-REDのシートにインポート(Ctrl+I)
- 「Import nodes」のボックスにペースト(Ctrl+V), OKをクリック
- シートの適当な場所をクリックしてフロー貼り付け
[{"id":"ab031444.14aad8","type":"comment","name":"1. injectからWatson Q&Aに質問を投げる","info":"","x":203.75,"y":96.25,"z":"e44cdea7.c40328","wires":[]},{"id":"4e25082f.9e529","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":451.8611068725586,"y":155.4444661140442,"z":"e44cdea7.c40328","wires":[["7316358b.1afe04"]]},{"id":"7316358b.1afe04","type":"debug","name":"","active":true,"console":"true","complete":"payload","x":611.527759552002,"y":155.55557250976562,"z":"e44cdea7.c40328","wires":[]},{"id":"4cedf267.d0939c","type":"inject","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":162.08330535888672,"y":155.44447898864746,"z":"e44cdea7.c40328","wires":[["3a489da2.3a4012"]]},{"id":"3a489da2.3a4012","type":"template","name":"質問文","field":"payload","format":"handlebars","template":"Which is the best hotel to stay in Okayama?","x":303.74999237060547,"y":155.22220993041992,"z":"e44cdea7.c40328","wires":[["4e25082f.9e529"]]},{"id":"ad4dfd13.2635c8","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":462.5278015136719,"y":350.5,"z":"e44cdea7.c40328","wires":[["1ab24e12.a68d02"]]},{"id":"c7e7236b.7cfd6","type":"debug","name":"","active":true,"console":"true","complete":"payload","x":808.9443969726562,"y":350.6111145019531,"z":"e44cdea7.c40328","wires":[]},{"id":"3192b4b.db4d34c","type":"inject","name":"","topic":"","payload":"","payloadType":"none","repeat":"","crontab":"","once":false,"x":172.75,"y":350.50001287460327,"z":"e44cdea7.c40328","wires":[["32828f2e.89fff"]]},{"id":"32828f2e.89fff","type":"template","name":"質問文","field":"payload","format":"handlebars","template":"Which is the best hotel to stay in Okayama?","x":314.41668701171875,"y":350.27774381637573,"z":"e44cdea7.c40328","wires":[["ad4dfd13.2635c8"]]},{"id":"492b5c90.805994","type":"template","name":"Watson返答","field":"payload","format":"handlebars","template":"<b>Watsonさんの答え</b><br>\n{{payload}}<br>","x":536.25,"y":405.75,"z":"e44cdea7.c40328","wires":[["a82c14a7.2fd8d8"]]},{"id":"bdb6fa42.34a1f","type":"http in","name":"","url":"/wat01","method":"get","swaggerDoc":"","x":166.25,"y":406,"z":"e44cdea7.c40328","wires":[["91c85bad.07fcb8"]]},{"id":"a82c14a7.2fd8d8","type":"http response","name":"","x":721.25,"y":405.75,"z":"e44cdea7.c40328","wires":[]},{"id":"91c85bad.07fcb8","type":"function","name":"payloadにセット","func":"msg.payload = context.global.answer\nreturn msg;","outputs":1,"noerr":0,"x":340.25,"y":405.75,"z":"e44cdea7.c40328","wires":[["492b5c90.805994"]]},{"id":"1ab24e12.a68d02","type":"function","name":"返答を格納","func":"context.global.answer = msg.payload;\nreturn msg;","outputs":"1","noerr":0,"x":627.75,"y":350.5,"z":"e44cdea7.c40328","wires":[["c7e7236b.7cfd6"]]},{"id":"38142e66.8e804a","type":"comment","name":"2. injectからWatson Q&Aに質問を投げる (Web)","info":"","x":229.5,"y":288.75,"z":"e44cdea7.c40328","wires":[]},{"id":"17f5cdf5.b9df5a","type":"comment","name":"3. URLリクエストパラメータを使ってみる","info":"https://<Node-RED URL>/wat02?question=Which is the best hotel to stay in Okayama? にアクセス","x":191.25,"y":532.75,"z":"e44cdea7.c40328","wires":[]},{"id":"684cc2d.a886f3c","type":"http in","name":"","url":"/wat02","method":"get","swaggerDoc":"","x":169.75,"y":598.25,"z":"e44cdea7.c40328","wires":[["cf1ea7da.577a8"]]},{"id":"cf1ea7da.577a8","type":"function","name":"質問を格納","func":"msg.payload = msg.req.query.question;\nreturn msg;","outputs":1,"noerr":0,"x":336.75,"y":598.25,"z":"e44cdea7.c40328","wires":[["41e84bc4.11d474"]]},{"id":"41e84bc4.11d474","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":498.25,"y":598.5,"z":"e44cdea7.c40328","wires":[["411aed72.feeb94"]]},{"id":"411aed72.feeb94","type":"template","name":"Watson返答","field":"payload","format":"handlebars","template":"<b>Watsonさんの答え</b><br>\n{{payload}}","x":650.9642105102539,"y":598.2137584686279,"z":"e44cdea7.c40328","wires":[["7ad5fa5f.86369c"]]},{"id":"7ad5fa5f.86369c","type":"http response","name":"","x":814.3929595947266,"y":598.2138366699219,"z":"e44cdea7.c40328","wires":[]},{"id":"68edb309.56a0e4","type":"comment","name":"4. WebフォームからWatson Q&Aに質問を投げる","info":"https://<Node-RED URL>/wat03 にアクセス","x":216.25,"y":719,"z":"e44cdea7.c40328","wires":[]},{"id":"cfeabed6.8b704","type":"http response","name":"","x":502.10717010498047,"y":782.28564453125,"z":"e44cdea7.c40328","wires":[]},{"id":"d1e9eac7.79ac1","type":"http in","name":"","url":"/wat03","method":"get","swaggerDoc":"","x":154.53573989868164,"y":782.7142868041992,"z":"e44cdea7.c40328","wires":[["82437aab.a75ca8"]]},{"id":"33d601f6.f0f566","type":"http in","name":"","url":"/wat04","method":"post","swaggerDoc":"","x":159.24998474121094,"y":840.2855682373047,"z":"e44cdea7.c40328","wires":[["b6dafc94.c255d"]]},{"id":"5b7ebd21.900b4c","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":455.1071548461914,"y":839.8577919006348,"z":"e44cdea7.c40328","wires":[["4b35e916.ef8b7"]]},{"id":"4b35e916.ef8b7","type":"template","name":"Watson返答","field":"payload","format":"handlebars","template":"<b>Watsonさんの答え</b><br>\n{{payload}}<br><br>\n<input type=\"button\" value=\"もう一度\" onClick=\"location.href='../wat03'\">","x":607.8213653564453,"y":839.5715503692627,"z":"e44cdea7.c40328","wires":[["52625aa7.e8a5f4"]]},{"id":"52625aa7.e8a5f4","type":"http response","name":"","x":758.250114440918,"y":839.5716285705566,"z":"e44cdea7.c40328","wires":[]},{"id":"b6dafc94.c255d","type":"function","name":"質問を格納","func":"msg.payload = msg.payload.question;\nreturn msg;","outputs":"1","noerr":0,"x":316.5357208251953,"y":840.2859630584717,"z":"e44cdea7.c40328","wires":[["5b7ebd21.900b4c"]]},{"id":"82437aab.a75ca8","type":"template","name":"質問入力","field":"payload","format":"handlebars","template":"<form action=\"/wat04\" method=\"post\">\n<b>Watson Q&A</b><br> <input type=\"text\" name=\"question\" size=\"80\" value=\"Which is the best hotel to stay in Okayama?\"><br><br>\n<button type=\"submit\">質問する</button>\n</form>","x":333.5357437133789,"y":782.5715732574463,"z":"e44cdea7.c40328","wires":[["cfeabed6.8b704"]]},{"id":"c7f88880.8afb4","type":"http response","name":"","x":512.1428833007812,"y":1028.4285888671875,"z":"e44cdea7.c40328","wires":[]},{"id":"f0f8f690.3b4f2","type":"template","name":"質問入力","field":"payload","format":"handlebars","template":"<head>\n\t<title>Watsonさんに質問</title>\n</head>\n<style type=\"text/css\">\n body {\n background-color: #26343F;\n }\n .page {\n background-color: #223A4B;\n }\n .zone {\n\t font-size:16px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:13px; \n\t padding-left:10px; \n\t margin-bottom:15px;\n\t display:inline-block;\n\t}\n\t.title {\n\t font-size:48px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:60px; \n\t padding-left:70px; \n\t margin-bottom:0px;\n\t display:inline-block;\n\t}\n .form {\n\t font-size:17px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:30px; \n\t padding-left:100px; \n\t margin-bottom:0px;\n\t display:block;\n\t}\n</style>\n<body>\n <span class=\"zone\" ><b></b>IBM Bluemix</b></span>\n <section class=\"page\">\n <div class=\"title\">Watsonさんに質問</div>\n\t <div class=\"form\">\n <form action=\"/wat06\" method=\"post\">\n <b>Watson Q&A</b><br> <input type=\"text\" name=\"question\" size=\"80\" value=\"Which is the best hotel to stay in Okayama?\" /><br><br>\n <button type=\"submit\">質問する</button><br>\n </div>\n\t</section>\n <span class=\"zone\" >Powered by <b>IBM Bluemix</b> and <b>Node-RED</b></span>\n</body>","x":352.14288330078125,"y":1028.4285888671875,"z":"e44cdea7.c40328","wires":[["c7f88880.8afb4"]]},{"id":"5e48523e.b21d34","type":"http in","name":"","url":"/wat05","method":"get","swaggerDoc":"","x":164.57145309448242,"y":1028.8572311401367,"z":"e44cdea7.c40328","wires":[["f0f8f690.3b4f2"]]},{"id":"c7aa3a37.9685b8","type":"http in","name":"","url":"/wat06","method":"post","swaggerDoc":"","x":168.57154273986816,"y":1109.7147417068481,"z":"e44cdea7.c40328","wires":[["e20384fb.a655f8"]]},{"id":"917d6e8b.4717f8","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":627.1429958343506,"y":1077.8577423095703,"z":"e44cdea7.c40328","wires":[["a7ef4c21.40193"]]},{"id":"60cb5643.9ca5f8","type":"http response","name":"","x":1063.0001697540283,"y":1114.7146530151367,"z":"e44cdea7.c40328","wires":[]},{"id":"eb2be6aa.f53b28","type":"function","name":"空文字判定","func":"if ( context.global.quest ){\n msg.payload = msg.payload.question;\n return [msg, null];\n}else{\n msg.payload = \"please don't hesitate to ask me.\";\n return [null, msg];\n}","outputs":"2","noerr":0,"x":469.42871284484863,"y":1109.7142419815063,"z":"e44cdea7.c40328","wires":[["917d6e8b.4717f8"],["a7ef4c21.40193"]]},{"id":"e20384fb.a655f8","type":"function","name":"質問を格納","func":"context.global.quest = msg.payload.question;\nreturn msg;","outputs":"1","noerr":0,"x":320.2858600616455,"y":1109.5716981887817,"z":"e44cdea7.c40328","wires":[["eb2be6aa.f53b28"]]},{"id":"a7ef4c21.40193","type":"function","name":"","func":"msg.question = context.global.quest;\nreturn msg;\n\n","outputs":"1","noerr":0,"x":774.2857799530029,"y":1115.285662651062,"z":"e44cdea7.c40328","wires":[["3a3ef787.3a04d"]]},{"id":"cf1d3788.fb8108","type":"comment","name":"5. WebフォームからWatson Q&Aに質問を投げる (応用)","info":"https://<Node-RED URL>/wat05 にアクセス","x":233.39291381835938,"y":965.1785888671875,"z":"e44cdea7.c40328","wires":[]},{"id":"9927d8ee.4f01f","type":"comment","name":"6. WebフォームからWatson Q&Aに質問を投げる (モバイル)","info":"https://<Node-RED URL>/wat07 にアクセス","x":237.1428680419922,"y":1222.4285888671875,"z":"e44cdea7.c40328","wires":[]},{"id":"322025a1.9a588a","type":"http response","name":"","x":507.00003814697266,"y":1293.7142333984375,"z":"e44cdea7.c40328","wires":[]},{"id":"e941f8d4.2ce0a8","type":"template","name":"質問入力","field":"payload","format":"handlebars","template":"<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Watsonさんに質問</title>\n <link rel=\"stylesheet\" href=\"http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css\" />\n <script src=\"http://code.jquery.com/jquery-1.11.0.min.js\"></script>\n <script src=\"http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js\"></script>\n</head>\n<body>\n <div data-role=\"page\">\n <div data-role=\"header\">\n <h1>Watsonさんに質問</h1>\n </div>\n \n <form method=\"post\" action=\"/wat08\">\n\t <div class=\"ui-field-contain\">\n\t\t <label for=\"question\">Watson Q&A:</label>\n\t\t <input type=\"text\" name=\"question\" id=\"question\" value=\"Which is the best hotel to stay in Okayama?\" />\n\t </div>\n\t <input type=\"submit\" value=\"質問する\" />\n </form>\n\n <div data-role=\"footer\">\n <h3>Powered by IBM Bluemix and Node-RED</h3>\n </div>\n</body>","x":347.00003814697266,"y":1293.7142333984375,"z":"e44cdea7.c40328","wires":[["322025a1.9a588a"]]},{"id":"b5a43d3e.377b8","type":"http in","name":"","url":"/wat07","method":"get","swaggerDoc":"","x":159.42860794067383,"y":1294.1428756713867,"z":"e44cdea7.c40328","wires":[["e941f8d4.2ce0a8"]]},{"id":"f2777c36.9267a","type":"http in","name":"","url":"/wat08","method":"post","swaggerDoc":"","x":158.42869758605957,"y":1374.0003862380981,"z":"e44cdea7.c40328","wires":[["9948d53a.70d73"]]},{"id":"88a5c101.67044","type":"watson-question-answer","name":"Q&A","output":"top","corpus":"travel","x":601.000150680542,"y":1341.1433868408203,"z":"e44cdea7.c40328","wires":[["bbb062f0.bea8b"]]},{"id":"a96a7dff.f78b1","type":"template","name":"Watson返答","field":"payload","format":"handlebars","template":"<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>Watsonさんに質問</title>\n <link rel=\"stylesheet\" href=\"http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.css\" />\n <script src=\"http://code.jquery.com/jquery-1.11.0.min.js\"></script>\n <script src=\"http://code.jquery.com/mobile/1.4.2/jquery.mobile-1.4.2.min.js\"></script>\n</head>\n\n<body>\n \n<b>あなたの質問</b><br>\n{{question}}<br><hr>\n\n<b>Watsonさんの答え</b><br>\n{{payload}}<br>\n\n\n <input type=\"button\" value=\"もう一度\" onClick=\"location.href='../wat07'\">\n <div data-role=\"footer\">\n <h3>Powered by IBM Bluemix and Node-RED</h3>\n </div>\n</body>","x":874.8573131561279,"y":1379.000262260437,"z":"e44cdea7.c40328","wires":[["208e5618.f9ef62"]]},{"id":"208e5618.f9ef62","type":"http response","name":"","x":1012.8573246002197,"y":1379.0002975463867,"z":"e44cdea7.c40328","wires":[]},{"id":"b4e3be1c.20bd48","type":"function","name":"空文字判定","func":"if ( context.global.quest ){\n msg.payload = msg.payload.question;\n return [msg, null];\n}else{\n msg.payload = \"please don't hesitate to ask me.\";\n return [null, msg];\n}","outputs":"2","noerr":0,"x":459.28586769104004,"y":1373.9998865127563,"z":"e44cdea7.c40328","wires":[["88a5c101.67044"],["bbb062f0.bea8b"]]},{"id":"9948d53a.70d73","type":"function","name":"質問を格納","func":"context.global.quest = msg.payload.question;\nreturn msg;","outputs":"1","noerr":0,"x":309.1430149078369,"y":1373.8573427200317,"z":"e44cdea7.c40328","wires":[["b4e3be1c.20bd48"]]},{"id":"bbb062f0.bea8b","type":"function","name":"","func":"msg.question = context.global.quest;\nreturn msg;\n\n","outputs":"1","noerr":0,"x":735.1429347991943,"y":1379.571307182312,"z":"e44cdea7.c40328","wires":[["a96a7dff.f78b1"]]},{"id":"3a3ef787.3a04d","type":"template","name":"Watson返答","field":"payload","format":"handlebars","template":"<head>\n\t<title>Watsonさんに質問</title>\n</head>\n<style type=\"text/css\">\n body {\n background-color: #26343F;\n font-size:18px; \n\t color: #00AED1;\n\t font-family: sans-serif;\n\t margin-top:10px; \n\t padding-left:90px; \n\t margin-bottom:0px;\n\t display:block;\n }\n .page {\n background-color: #223A4B;\n }\n .zone {\n\t font-size:16px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:13px; \n\t padding-left:10px; \n\t margin-bottom:15px;\n\t display:inline-block;\n\t}\n\t.title {\n\t font-size:48px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:60px; \n\t padding-left:80px; \n\t margin-bottom:0px;\n\t display:inline-block;\n\t}\n .form {\n\t font-size:17px; \n\t color: white;\n\t font-family: sans-serif;\n\t margin-top:30px; \n\t padding-left:100px; \n\t margin-bottom:0px;\n\t display:block;\n\t}\n</style>\n<body>\n <span class=\"zone\" ><b></b>IBM Bluemix</b></span>\n <section class=\"page\">\n <div class=\"title\">あなたの質問</div><br>\n\t {{question}}<br><hr>\n <div class=\"title\">Watsonさんの答え</div><br>\n {{payload}}<br><br>\n <input type=\"button\" value=\"もう一度\" onClick=\"location.href='../wat05'\">\n\t</section>\n <span class=\"zone\" >Powered by <b>IBM Bluemix</b> and <b>Node-RED</b></span>\n</body>\n","x":920.1428375244141,"y":1115.285717010498,"z":"e44cdea7.c40328","wires":[["60cb5643.9ca5f8"]]}]