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

# はじめに
[前の記事](http://qiita.com/VegaSato/items/7ce47e6a9c06c290b212)でNode-REDからWatson APIを呼び出す方法を書いた。それらを組み合わせて[スマホの質問に答えてくれる質問応答システム](http://satohdai-nodered.mybluemix.net/qaservice)を作ってみたので、今回はそれの解説をしようと思う。
*学習させているドキュメントが少なすぎて、いまいちいい回答はしてくれないので注意。コンセプトが伝わってくれれば御の字。*

# 全体フロー
Node-REDで作ったフローはこんな感じ。
![2016-02-23 13_56_14-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/202ba4e2-cd8e-ad19-301f-856185cd838c.png)
小さすぎて見えないけど、この後で細切れにアップにするのでご安心を。

解説のために以下のように区切った。
![2016-02-23 14_21_48-PowerPoint Slide Show - [Node-RED.pptx].png](https://qiita-image-store.s3.amazonaws.com/0/102519/727e83ba-0d60-2de1-7a81-80dbe57bbad5.png)

# ①トップページリクエスト
初期画面としてブラウザからのアクセスを受けると、HTMLを組み立ててブラウザに返す、という動作をさせる。
![2016-02-23 14_04_08-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/ef8eb9a5-f8b7-45f0-7840-e2304895810f.png)
まずは、httpリクエストノードを使って、HTTPリクエストを受け取る。今回はブラウザーから*/qaservice*にアクセスをしてトップページを表示させる。
functionノードを使って、HTMLのコードを書く。JavaScriptのコードもHTMLの中に埋め込む。今回の場合は、ここが一番重い作業になる。

```html:初期画面整形
<!DOCTYPE html>
<html>
    <head>
        <title>Watson Smartphone Advisor</title>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1">
    
        <!-- jquery -->
        <script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
    
        <!-- Bootstrap -->
        <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

        <!-- Bootswatch -->
        <link rel="stylesheet" href="http://bootswatch.com/flatly/bootstrap.min.css">
        <!-- <link rel="stylesheet" href="http://bootswatch.com/cerulean/bootstrap.min.css"> -->
        
        <!-- ユーザーロジック -->
        <script type="text/javascript">
            var askQuestion = function(){
                // ボタンの無効化
                var askButton = document.getElementById("askButton");
                askButton.className = "btn btn-default disabled";
                
                //質問文を取り出す
                var q = document.getElementById("q");
                
                // *** Node-red /askflow を呼び出すためのxhrを作って呼び出し***
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function (){
                    switch(xhr.readyState){
                        case 4:
                            var answerArea = document.getElementById("answerArea");
                            if(xhr.status == 0){
                                answerArea.value = "XHR 通信失敗";
                            }else{
                                if(xhr.status == 200){
                                    // フローから返ってきた結果を "answerArea" に入れる
                                    answerArea.value = xhr.responseText;
                                }else{
                                    // status == 200以外
                                    answerArea.value = xhr.status + " : " + xhr.responseText;
                                }
                            }
                            // 音声出力
                            var audio = document.createElement("audio");
                            audio.src = "./speech?text=" + answerArea.value;
                            audio.play();
                            
                            // ボタンの有効化
                            askButton.className = "btn btn-primary";
                            
                            break;
                    }
                };
                var url = "./askflow?q=" + q.value;
                xhr.open("GET", url, true);
                xhr.send("");
            }
            
            var checkLength = function(){
                // ボタンオブジェクトを取り出す
                var askButton = document.getElementById("askButton");
                // 質問文を取り出す
                var q = document.getElementById("q");
                if (q.value.length > 0) {
                    askButton.className = "btn btn-primary";    // ボタンの有効化
                } else {
                    askButton.className = "btn btn-default disabled";   // ボタンの無効化
                }
            }
        </script>

    </head>

    <body>
        <!-- Navbar -->
        <nav class="navbar navbar-default">
            <div class="container-fluid">
                <div class="navbar-header">
                    <a class="navbar-brand" href="http://satohdai-nodered.mybluemix.net/qaservice">Watson Smartphone Advisor</a>
                </div>
                <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
                    <ul class="nav navbar-nav navbar-right">
                        <li><a href="https://satohdai-nodered.mybluemix.net/" target=_>Node-Red</a></li>
                    </ul>
                </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="q" placeholder="ここに質問を入力してください" oninput="checkLength()"></textarea>
                            <br>
                            <a class="btn btn-default distabled" id="askButton" href="javascript:askQuestion()">質問する</a>
                        </div>
                        <br>
                        <div class="form-group">
                            <textarea class="form-control" rows=5 id="answerArea" placeholder="Watsonからの回答" disabled=""></textarea>
                        </div>
                    </center>
                </div>
                
                <!-- 右カラム -->
                <div class="col-xs-1 bg-right"></div>
            </div>
        </div>

    </body>

</html>
```

とまぁこんな感じ。動くの優先で作ったので醜い部分があってもキニシナイ。
最後にhttpレスポンスノードをくっつけて完了。

# ②質問の内容判断
ここでは、質問文を受け取り、質問内容への一段目の対応を行う。一段目では、内容が「あいさつ」か「それ以外」かを判断し、「あいさつ」ならば挨拶の種類にあわせた返事を行い、「それ以外」ならば二段目の対応に分岐させる。確信度が高くない場合は、クラス分け失敗ということで、再度質問を返すように求める。
![2016-02-23 14_04_26-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/92cf0b35-db77-3aa8-f7ff-c792367cc8a6.png)
HTMLでボタンをクリックすると*/askflow*を呼び出すように書いておいたので、スタートは/askflowからはじまる。
「Greeting or Not Classifier」ではNLCを使ってあいさつかそうでないかを判断する。「あいさつではない」という教師データは、後ろで使うAndroidやiPhoneの質問文をそのまま借用した。
ここで、確信度が低い(今回は80%未満)、つまり「あいさつなのか質問なのかすらわからない」場合は、もう一度質問を入れてくれるようにレスポンスをセットしてhttpレスポンスノードに繋げる。
確信度が高い場合は、それが「あいさつ」なのか「それ以外」なのかによって分岐する。

# ③あいさつを返す
②で「あいさつ」と判定された場合、あいさつの種類に応じた応答を返す。
![2016-02-23 14_04_50-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/399f6cfd-643f-b7b8-cd36-3d7f5a2fd1a0.png)
NLCによってあいさつの種類を特定し、種類の沿った応答メッセージをセットしてhttpレスポンスノードに繋げる。登録されているあいさつ以外は「その他」にしているが、トップクラスは登録されているあいさつのどれかなので、ここには分岐しないはずである・・・。

# ④AndroidかiPhoneか分類する
②で「それ以外」と判定された場合、「Android」か「iPhone」か判定して次のステップに繋げる。
![2016-02-23 14_05_15-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/cd1e281d-b8ca-3743-2d5c-78cd3588e9b4.png)
NLCによって質問文がAndroidの質問かiPhoneの質問か判別させる。確信度が低い(今回は80%未満)場合は分類失敗としてもう一度質問するようにお願いする。確信度が高い(今回は80%以上)場合は、該当クラスの処理へと分岐する。

# ⑤R&Rで回答を探して答える
④でAndroidかiPhoneか分類できたので、それぞれのR&Rに対して質問を投げて最適な回答を取得し、応答する。
![2016-02-23 14_05_31-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/97081a50-7b8e-54cf-3533-2bf827cd2afc.png)
R&Rではコレクション名なども指定しなければならず、簡単のために最初から分岐して作った。頑張れば分岐せずに共通フローでいけるかもしれない。
最後にhttpレスポンスノードでブラウザーに応答を返し、回答欄に表示させる。

# ⑥TTSでしゃべらせる
⑤でブラウザーに応答を返したときに、回答欄への表示の他にTTSの呼び出しも実行する。
![2016-02-23 14_05_45-Node-RED _ satohdai-nodered.mybluemix.net.png](https://qiita-image-store.s3.amazonaws.com/0/102519/9d2e8128-5bd2-600b-92fe-71703660f754.png)
エンドポイントを「/speech」としてTTSを呼び出すフローを作る。中身は前回の解説のとおり、単純なフローになる。

以上で、Node-REDを使った日本語質問応答アプリができた。

# おわりに
やっぱりNode-REDは楽だ。app.jsとかHTMLを行ったり来たりしなくてもいい。JavaScriptやNode.jsの面倒なお作法とかも(極力)考えずに済む。これからアプリ開発をはじめてやります、という人に入門としては向いているかもしれない。が、これをやれるようになったからといって、JavaやJavaScriptを普通に(一般的なエディターを使って)開発できるとは思えないが・・・。
なにはともあれ、これであとは教育データをそろえさえすれば、何でも質問できちゃうネ！