はじめに
前の記事でNode-REDからWatson APIを呼び出す方法を書いた。それらを組み合わせてスマホの質問に答えてくれる質問応答システムを作ってみたので、今回はそれの解説をしようと思う。
学習させているドキュメントが少なすぎて、いまいちいい回答はしてくれないので注意。コンセプトが伝わってくれれば御の字。
全体フロー
Node-REDで作ったフローはこんな感じ。
小さすぎて見えないけど、この後で細切れにアップにするのでご安心を。
①トップページリクエスト
初期画面としてブラウザからのアクセスを受けると、HTMLを組み立ててブラウザに返す、という動作をさせる。
まずは、httpリクエストノードを使って、HTTPリクエストを受け取る。今回はブラウザーから*/qaservice*にアクセスをしてトップページを表示させる。
functionノードを使って、HTMLのコードを書く。JavaScriptのコードも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レスポンスノードをくっつけて完了。
②質問の内容判断
ここでは、質問文を受け取り、質問内容への一段目の対応を行う。一段目では、内容が「あいさつ」か「それ以外」かを判断し、「あいさつ」ならば挨拶の種類にあわせた返事を行い、「それ以外」ならば二段目の対応に分岐させる。確信度が高くない場合は、クラス分け失敗ということで、再度質問を返すように求める。
HTMLでボタンをクリックすると*/askflow*を呼び出すように書いておいたので、スタートは/askflowからはじまる。
「Greeting or Not Classifier」ではNLCを使ってあいさつかそうでないかを判断する。「あいさつではない」という教師データは、後ろで使うAndroidやiPhoneの質問文をそのまま借用した。
ここで、確信度が低い(今回は80%未満)、つまり「あいさつなのか質問なのかすらわからない」場合は、もう一度質問を入れてくれるようにレスポンスをセットしてhttpレスポンスノードに繋げる。
確信度が高い場合は、それが「あいさつ」なのか「それ以外」なのかによって分岐する。
③あいさつを返す
②で「あいさつ」と判定された場合、あいさつの種類に応じた応答を返す。
NLCによってあいさつの種類を特定し、種類の沿った応答メッセージをセットしてhttpレスポンスノードに繋げる。登録されているあいさつ以外は「その他」にしているが、トップクラスは登録されているあいさつのどれかなので、ここには分岐しないはずである・・・。
④AndroidかiPhoneか分類する
②で「それ以外」と判定された場合、「Android」か「iPhone」か判定して次のステップに繋げる。
NLCによって質問文がAndroidの質問かiPhoneの質問か判別させる。確信度が低い(今回は80%未満)場合は分類失敗としてもう一度質問するようにお願いする。確信度が高い(今回は80%以上)場合は、該当クラスの処理へと分岐する。
⑤R&Rで回答を探して答える
④でAndroidかiPhoneか分類できたので、それぞれのR&Rに対して質問を投げて最適な回答を取得し、応答する。
R&Rではコレクション名なども指定しなければならず、簡単のために最初から分岐して作った。頑張れば分岐せずに共通フローでいけるかもしれない。
最後にhttpレスポンスノードでブラウザーに応答を返し、回答欄に表示させる。
⑥TTSでしゃべらせる
⑤でブラウザーに応答を返したときに、回答欄への表示の他にTTSの呼び出しも実行する。
エンドポイントを「/speech」としてTTSを呼び出すフローを作る。中身は前回の解説のとおり、単純なフローになる。
以上で、Node-REDを使った日本語質問応答アプリができた。
おわりに
やっぱりNode-REDは楽だ。app.jsとかHTMLを行ったり来たりしなくてもいい。JavaScriptやNode.jsの面倒なお作法とかも(極力)考えずに済む。これからアプリ開発をはじめてやります、という人に入門としては向いているかもしれない。が、これをやれるようになったからといって、JavaやJavaScriptを普通に(一般的なエディターを使って)開発できるとは思えないが・・・。
なにはともあれ、これであとは教育データをそろえさえすれば、何でも質問できちゃうネ!