LoginSignup
11
9

More than 5 years have passed since last update.

Watson Retrieve and Rankの公式ツールを利用して質問応答システムを作る (3)

Last updated at Posted at 2017-05-07

2017年11月1日追記

[Watson] Retrieve and Rankのサービス終了について

概要

本稿では、Retrieve and Rank Web interfaceという公式ツールを利用してRetrieve and Rank(以下R&R)のトレーニングを行い、簡単な質問応答システムを作るまでの手順をシリーズ化して解説します。

(1) R&Rおよび公式ツール利用開始〜回答データ投入
(2) 質問データ投入〜Rankerトレーニング
(3) Node-REDアプリケーションとR&Rの連携 ※この記事で説明する部分

まだ第1回の記事第2回の記事をお読みでない方は、そちらを先にお読みください。

前回までの作業でR&Rのトレーニングが完了し、利用可能になりました。
今回はいよいよアプリケーションからR&Rを呼び出してみます。

免責事項

本稿におけるWatsonサービスに関する解説は執筆者の個人的な見解であり、公式資料に基づいていない内容を含みます。
また本稿の情報は2017年4月現在のものです。
必ずWatson Developer Cloudなどの公式資料も併せて確認してください。

完成イメージ

以下のようなシンプルなWebアプリケーションを、Node-REDを利用して作成します。
スクリーンショット 2017-05-07 7.31.05.png

Node-REDとは

あらかじめ用意された「ノード」と呼ばれる部品をつなげて「フロー」を作成することにより、最小限のコードでIBM Bluemix上にWebアプリを構築できます。

Node-REDの利用開始

Bluemixのカテゴリーにて「ボイラープレート」-「Node-RED Starter」をクリックします。
スクリーンショット 2017-05-02 20.04.27.png

アプリ名を指定します。アプリ名+ドメインがルートURLになります。
スクリーンショット 2017-05-02 20.04.50.png
「作成」をクリックすると、環境作成が開始されます。

5-10分待ち、以下のように「実行中」の表示になったらURLをクリックします。
7a4d7780-3ea9-d60c-b2f7-cc2497a482b2.png

以下の画面はNextで進めます。
FireShot Capture 9 - Node-RED in IBM Bluemix - https___y-someya.au-syd.mybluemix.net_.png
FireShot Capture 11 - Node-RED in IBM Bluemix - https___y-someya.au-syd.mybluemix.net_.png
FireShot Capture 12 - Node-RED in IBM Bluemix - https___y-someya.au-syd.mybluemix.net_.png

「Go to your Node-RED flow editor」をクリックします。
スクリーンショット 2017-05-02 20.16.52.png

フローエディターにアクセスするためのユーザーIDとパスワードを指定します。スクリーンショット 2017-05-02 20.17.33.png

フローエディターが起動します。
スクリーンショット 2017-05-02 20.17.50.png

左側に並んでいるのがノードです。
Node-REDでは、これらのノードをドラッグ&ドロップし、ノード同士をつなげてフローを作成することでアプリケーションを構築します。

Watson用のノードはあらかじめ準備されており、R&Rのノードもあります。
スクリーンショット 2017-05-07 8.27.07.png

本アプリのフロー定義

環境

Item Version
Node-RED 0.16.2

2017年4月末時点でBluemix上にNode-RED環境を構築すると上記バージョンとなりました。

フロー定義のインポート

フロー定義はインポート可能です。
本アプリのフロー定義は以下になります。


[{"id":"506aa228.0d18dc","type":"http in","z":"33444cbb.50d004","name":"","url":"/rr","method":"get","swaggerDoc":"","x":996,"y":711,"wires":[["ef47a863.502158"]]},{"id":"86c01a38.dd5dc8","type":"debug","z":"33444cbb.50d004","name":"Debug","active":true,"console":"false","complete":"payload","x":1387.687744140625,"y":947.4136352539062,"wires":[]},{"id":"656875de.65f10c","type":"function","z":"33444cbb.50d004","name":"回答取り出し","func":"if (msg.payload.response.docs.length > 0) {\n    msg.payload = msg.payload.response.docs[0].title + \"です。\";\n} else {\n    msg.payload = \"回答できません\";\n}\nreturn msg;\n","outputs":1,"noerr":0,"x":1485,"y":1154,"wires":[["c0549441.3da728","990f985c.6896c8"]]},{"id":"74e06930.a54698","type":"http response","z":"33444cbb.50d004","name":"","x":1309.2205810546875,"y":710.2529907226562,"wires":[]},{"id":"3c2845a4.0d8f0a","type":"change","z":"33444cbb.50d004","name":"質問文の取り出し","rules":[{"t":"set","p":"payload","pt":"msg","to":"payload.q","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1133,"y":933,"wires":[["e256f1d1.ecef9","eb8da968.b80668"]]},{"id":"e256f1d1.ecef9","type":"debug","z":"33444cbb.50d004","name":"Debug","active":true,"console":"false","complete":"payload","x":1294,"y":879,"wires":[]},{"id":"c0549441.3da728","type":"debug","z":"33444cbb.50d004","name":"Debug","active":true,"console":"false","complete":"payload","x":1658,"y":1152,"wires":[]},{"id":"6b42b6dc.85bd78","type":"switch","z":"33444cbb.50d004","name":"レスポンスチェック","property":"payload.response.docs","propertyType":"msg","rules":[{"t":"null"},{"t":"nnull"}],"checkall":"true","outputs":2,"x":1368,"y":1065,"wires":[["990f985c.6896c8"],["656875de.65f10c"]]},{"id":"ba873c1.293cbc","type":"comment","z":"33444cbb.50d004","name":"エラーの場合","info":"","x":1521,"y":1026,"wires":[]},{"id":"a4f56bee.7efa18","type":"comment","z":"33444cbb.50d004","name":"正常の場合","info":"","x":1403,"y":1109,"wires":[]},{"id":"ee8036b0.1888d8","type":"comment","z":"33444cbb.50d004","name":"初期表示","info":"","x":993,"y":662,"wires":[]},{"id":"22940a42.554616","type":"comment","z":"33444cbb.50d004","name":"質問応答","info":"","x":993,"y":827,"wires":[]},{"id":"eb8da968.b80668","type":"watson-retrieve-rank-search-and-rank","z":"33444cbb.50d004","name":"R&R search","servicecreds":"","clusterid":"","collectionname":"","searchmode":"search-and-rank","rankerid":"","x":1236,"y":997,"wires":[["86c01a38.dd5dc8","6b42b6dc.85bd78"]]},{"id":"d612ed97.00bf9","type":"http in","z":"33444cbb.50d004","name":"","url":"/rr/ask","method":"get","swaggerDoc":"","x":1019,"y":868,"wires":[["3c2845a4.0d8f0a"]]},{"id":"990f985c.6896c8","type":"http response","z":"33444cbb.50d004","name":"","x":1621,"y":1059,"wires":[]},{"id":"ef47a863.502158","type":"template","z":"33444cbb.50d004","name":"html","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<!DOCTYPE html>\n<html lang=\"ja\">\n    <head>\n        <meta charset=\"utf-8\">\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n        <meta http-equiv=\"x-ua-compatible\" content=\"ie=edge\">\n        <title>Watson R&R Test</title>\n        <link rel=\"stylesheet\" href=\"//bootswatch.com/cerulean/bootstrap.min.css\">\n\n        <script type=\"text/javascript\">\n            // Node-REDのWeb API連携\n            var askWatson = function() {\n                // ボタン無効\n                $(\".btn\").addClass(\"disabled\").addClass(\"btn-default\").removeClass(\"btn-primary\");\n                // Watson連携\n                $.ajax({\n                    type: \"GET\",\n                    url: \"./ask\",\n                    dataType: \"text\",\n                    timeout: 10000,\n                    data: {\n                        //質問文を取得してパラメータとして渡す\n                        q: $(\"#question\").val()\n                    }\n                })\n                .done(function(data, textStatus, jqXHR) {\n                    // 通信成功\n                    // フローから返ってきた結果をanswerに入れる\n                    $(\"#answer\").val(data);\n                    // ボタン有効\n                    $(\".btn\").removeClass(\"disabled\").removeClass(\"btn-default\").addClass(\"btn-primary\");\n                })\n                .fail(function(jqXHR, textStatus, errorThrown) {\n                    // 通信エラー\n                    $(\"#answer\").val(\"通信エラー  \" + jqXHR.status + \" : \" + jqXHR.statusText);\n                })\n                .always(function(data) {\n                    // Debug\n                    console.log(data);\n                });\n            }\n           \n            // 質問文入力済み判定\n            var checkLengthQ = function() {\n                if ($(\"#question\").val().length > 0) {\n                    // ボタン有効\n                    $(\".btn\").removeClass(\"disabled\").removeClass(\"btn-default\").addClass(\"btn-primary\");\n                } else {\n                    // ボタン無効\n                    $(\".btn\").addClass(\"disabled\").addClass(\"btn-default\").removeClass(\"btn-primary\");\n                }\n            }\n        </script>\n    </head>\n    \n    <body>\n        <nav class=\"navbar navbar-default\">\n            <div class=\"container-fluid\">\n                <div class=\"navbar-header\">\n                    <a class=\"navbar-brand\" href=\"{{req._parsedUrl.pathname}}\">Watson R&R Test</a>\n                </div>\n            </div>\n        </nav>\n        <div class=\"container-fluid\">\n            <div class=\"row\">\n                <div class=\"col-xs-1 bg-left\"></div>\n                <div class=\"col-xs-10 bg-main\">\n                    <center>\n                        <h3>北海道の観光地について質問してください</h3>\n                        <div class=\"form-group\">\n                            <textarea class=\"form-control\" rows=3 id=\"question\" placeholder=\"ここに質問を入力してください\" oninput=\"checkLengthQ()\"></textarea>\n                            <br>\n                            <a class=\"btn btn-default disabled\" id=\"askButton\" href=\"javascript:askWatson()\">質問する</a>\n                        </div>\n                        <br>\n                        <div class=\"form-group\">\n                            <textarea class=\"form-control\" rows=5 id=\"answer\" placeholder=\"Watsonからの回答\" disabled=\"\"></textarea>\n                        </div>\n                    </center>\n                </div>\n                <div class=\"col-xs-1 bg-right\"></div>\n            </div>\n        </div>\n    <body>\n        \n    <script src=\"//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js\" integrity=\"sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7\" crossorigin=\"anonymous\"></script>\n    <script src=\"//cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js\" integrity=\"sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8\" crossorigin=\"anonymous\"></script>\n    <script src=\"//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js\" integrity=\"sha384-BLiI7JTZm+JWlgKa0M0kGRpJbF2J8q+qreVrKBC47e3K6BW78kGLrCkeRX6I9RoK\" crossorigin=\"anonymous\"></script>\n</html>","x":1154,"y":709,"wires":[["74e06930.a54698"]]}]

クリップボードにコピーし、ハンバーガーメニューの「読み込み」-「クリップボード」を選択します。

スクリーンショット 2017-05-02 21.35.13.png

以下のようなフロー定義がインポートされます。
スクリーンショット 2017-05-07 8.51.36.png

フロー定義解説

本アプリはシンプルなSingle Page Applicationです。

「初期表示」のフローではHTMLをブラウザに返します。
Submit時に「質問応答」フローにAjaxでアクセスし、R&Rの応答を返します。

各ノードの解説は以下の通りです。

http request

スクリーンショット 2017-05-07 16.50.24.png
HTTPリクエストを受け付けるノードです。
スクリーンショット 2017-05-07 9.33.30.png
Method欄でHTTPメソッドを選択します。
ここで指定したURLがアプリのアクセスポイントとなります。
Nameは全ノード共通のプロパティで、フローエディタ上に表示する名前です。(入力は任意)

template

スクリーンショット 2017-05-07 16.51.29.png
HTMLテンプレートを記述します。
スクリーンショット 2017-05-07 9.37.12.png
テンプレートエンジンMustacheを使うことができます。


<!DOCTYPE html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta http-equiv="x-ua-compatible" content="ie=edge">
        <title>Watson R&R Test</title>
        <link rel="stylesheet" href="//bootswatch.com/cerulean/bootstrap.min.css">

        <script type="text/javascript">
            // Node-REDのWeb API連携
            var askWatson = function() {
                // ボタン無効
                $(".btn").addClass("disabled").addClass("btn-default").removeClass("btn-primary");
                // Watson連携
                $.ajax({
                    type: "GET",
                    url: "./ask",
                    dataType: "text",
                    timeout: 10000,
                    data: {
                        //質問文を取得してパラメータとして渡す
                        q: $("#question").val()
                    }
                })
                .done(function(data, textStatus, jqXHR) {
                    // 通信成功
                    // フローから返ってきた結果をanswerに入れる
                    $("#answer").val(data);
                    // ボタン有効
                    $(".btn").removeClass("disabled").removeClass("btn-default").addClass("btn-primary");
                })
                .fail(function(jqXHR, textStatus, errorThrown) {
                    // 通信エラー
                    $("#answer").val("通信エラー  " + jqXHR.status + " : " + jqXHR.statusText);
                })
                .always(function(data) {
                    // Debug
                    console.log(data);
                });
            }

            // 質問文入力済み判定
            var checkLengthQ = function() {
                if ($("#question").val().length > 0) {
                    // ボタン有効
                    $(".btn").removeClass("disabled").removeClass("btn-default").addClass("btn-primary");
                } else {
                    // ボタン無効
                    $(".btn").addClass("disabled").addClass("btn-default").removeClass("btn-primary");
                }
            }
        </script>
    </head>

    <body>
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="{{req._parsedUrl.pathname}}">Watson R&R Test</a>
                </div>
            </div>
        </nav>
        <div class="container-fluid">
            <div class="row">
                <div class="col-xs-1 bg-left"></div>
                <div class="col-xs-10 bg-main">
                    <center>
                        <h3>北海道の観光地について質問してください</h3>
                        <div class="form-group">
                            <textarea class="form-control" rows=3 id="question" placeholder="ここに質問を入力してください" oninput="checkLengthQ()"></textarea>
                            <br>
                            <a class="btn btn-default disabled" id="askButton" href="javascript:askWatson()">質問する</a>
                        </div>
                        <br>
                        <div class="form-group">
                            <textarea class="form-control" rows=5 id="answer" placeholder="Watsonからの回答" disabled=""></textarea>
                        </div>
                    </center>
                </div>
                <div class="col-xs-1 bg-right"></div>
            </div>
        </div>
    <body>

    <script src="//ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js" integrity="sha384-3ceskX3iaEnIogmQchP8opvBy3Mi7Ce34nWjpBIwVTHfGYWQS9jwHDVRnpKKHJg7" crossorigin="anonymous"></script>
    <script src="//cdnjs.cloudflare.com/ajax/libs/tether/1.3.7/js/tether.min.js" integrity="sha384-XTs3FgkjiBgo8qjEjBk0tGmf3wPrWtA6coPfQDfFEY8AnYJwjalXCiosYRBIBZX8" crossorigin="anonymous"></script>
    <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.5/js/bootstrap.min.js" integrity="sha384-BLiI7JTZm+JWlgKa0M0kGRpJbF2J8q+qreVrKBC47e3K6BW78kGLrCkeRX6I9RoK" crossorigin="anonymous"></script>
</html>

http response

スクリーンショット 2017-05-07 16.52.08.png
HTTPレスポンスをクライアントに返すノードです。
スクリーンショット 2017-05-07 9.41.50.png
何も入力すべき項目はないのですが、このノードを配置しないとクライアントに何も返らないので注意が必要です。

change

スクリーンショット 2017-05-07 16.52.41.png
changeノードはデータを編集するために利用します。
スクリーンショット 2017-05-07 9.47.07.png
msgオブジェクトは、ノード間でやりとりされるデータです。payloadはそのボディ部にあたります。
ここでは、ブラウザから渡された引数payload.qをpayload自体に詰め替えて次のノードに渡しています。

retrieve and rank search and rank

スクリーンショット 2017-05-07 16.53.18.png
R&RのSearch and rank APIを呼び出すためのノードです。
スクリーンショット 2017-05-07 9.55.26.png

Service Credentialsの鉛筆アイコンをクリックすると、サービス資格情報の入力が表示されます。
スクリーンショット 2017-05-07 10.07.33.png

サービス資格情報は、Bluemixダッシュボードの「サービス」一覧にてR&Rを選択し、「サービス資格情報」タブ「資格情報の表示」で確認できます。
スクリーンショット 2017-05-07 10.09.36.png

Cluster ID, Collection Name, Ranker IDは、公式ツールで確認し入力します。
スクリーンショット 2017-05-07 10.13.51.png
スクリーンショット 2017-05-07 10.14.09.png

switch

スクリーンショット 2017-05-07 16.53.58.png
判断分岐を行うためのノードです。
スクリーンショット 2017-05-07 10.21.11.png

debug

スクリーンショット 2017-05-07 16.54.51.png
デバッグ出力を行うためのノードです。
スクリーンショット 2017-05-07 10.28.26.png

以下のように、フローエディタ右側のデバッグエリアにデバッグ情報を出力してくれます。
スクリーンショット 2017-05-07 10.27.23.png

前述のswitchノードでは、R&Rから"docs"というオブジェクトが返って来れば正常として後続処理を行い、そうでなければR&Rからのエラーをクライアントに返しています。

function

スクリーンショット 2017-05-07 16.55.25.png
JavaScriptを記述するためのノードです。
スクリーンショット 2017-05-07 10.35.08.png
次ノードにデータを引き継ぐには、msgオブジェクトをreturnします。

補足

R&R search and rankノードではR&Rのレスポンスとして回答データ(Solrコレクションデータ)のIDとTitleしか取得できず、Body(本文)を取得できません。
※2017年4月末現在の情報です。

公式のAPI Referenceのサンプルにも載っていないのですが、実はR&R Search and rank APIではBodyも取得できます。

API Referenceのサンプルにある、"fl=id,title"の部分を"fl=id,title,body"に変更することで、Bodyを取得可能です。

ただし、functionノードにJavaScriptを記述してR&Rを呼び出す必要があります。
Bluemix Node-REDを使ってWatson APIを呼び出す

まとめ

第1回の記事に記載した通り、R&Rは、オープンソースのApache Solrと、Watson独自のRankerという仕組みを組み合わせたサービスです。

その特性上、curlコマンドによるトレーニングは少々わかりづらく、また手間のかかる作業でしたが、公式ツールであるWeb interfaceを利用することで、わかりやすく、簡単にトレーニングが可能になりました。

Node-REDについては他にもいろいろなノードがあり、また自分でノードを作成することもできますので、Node-RED日本ユーザ会のサイトなどをご参照ください。

<参考記事>
Bluemix Node-REDを使ってWatson APIによる日本語質問応答システムを作ってみた

11
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
9