IBM Watson Retrieve and Rank に問い合わせるLINE Botの作成

#Watson Retrieve And Rank に問い合わせるLINE Botの作成
社内の勉強会でLINE Botを立てるお題が出たので、どうせならと構築済みの IBM Watson Retrieve and Rank サービスに問い合わせるようにしました。
もっとも実質は、ryuta46 さんの [LINE Bot で雑談 Bot 作ってみた](http://ryuta46.com/204) の問い合わせ先を [Talk API](https://a3rt.recruit-tech.co.jp/product/talkAPI/) から [IBM Watson Retrieve and Rank](https://www.ibm.com/watson/jp-ja/developercloud/retrieve-rank.html) に変えただけです。

#準備1 - Watson Retrieve And Rank サービスの構築
まず、Watson Retrieve And Rank サービスを構築します。
今回は [ダイヤルIBM FAQ](http://www-06.ibm.com/jp/domino02/services/dialqa/cicfaq.nsf) のデータでサービスを構築しました。
構築方法は、良い記事があちこちにあるので、検索してみてください。
Bot アプリから R&R にアクセスするため下記の値を確認しておきます。

- USERNAME
- PASSWORD
- COLLECTION名
- CLUSTER ID
- RANKER ID

#準備2 - Bluemix上に Python のサーバーを構築
Bluemix 上である必然はないのですが、R&Rを使えるなら Bluemix にサーバーを立てられるだろうということで、こちらを使います。
url を確認しておきます。

#準備3 - LINE Botの作成・設定
LINE Business Center から LINE Bot を作成・設定します。
Bot の設定は、良い記事があちこちにあるので、検索してみてください。
Webhook URL は、アプリに合わせ「サーバーURL + /callback」にします。
Channel Secret, Channel Access Token, QRコードを確認しておきます。

#ryuta46 さんのLINE Bot の入手
ryuta46 さんは作成したコードを [GitHub](https://github.com/ryuta46/linebottalk) で公開されているので、これをありがたく頂戴します。

```
$ git clone https://github.com/ryuta46/linebottalk linebotrnr
$ cd linebotrnr/
```

構成用の情報の config.json から Talk API の情報を削除し R&R の情報を追加します。
もちろん、実際には値を各項目にセットします。

```config.json
{
    "lineChannelToken":"",
    "lineChannelSecret": "",
    "RnR_USERNAME": "",
    "RnR_PASSWORD": "",
    "RnR_COLLECTION": "",
    "RnR_CLUSTER_ID":"",
    "RnR_RANKER_ID": ""
}
```

本体の server.py を変更していきます。

まず、R&R で利用するモジュールを import します。

```server.py 
import pysolr
from watson_developer_cloud import RetrieveAndRankV1
```

config.json の値を読み込んでいる部分があるので、talk_api_key を読まないようにし、R&R の情報を読むようにします。

さらに、初期化の一環として RetrieveAndRankV1 オブジェクトを作成します。

```server.py 
config_file = open('config.json' , 'r')
config = json.load(config_file)
# talk_api_key = config["talkApiKey"]
line_channel_token = config["lineChannelToken"]
line_channel_secret = config["lineChannelSecret"]:

rnr_collection = config["RnR_COLLECTION"]
rnr_cluster_id = config["RnR_CLUSTER_ID"]
rnr_ranker_id  = config["RnR_RANKER_ID"]

retrieve_and_rank = RetrieveAndRankV1(
     username = rnr_user, 
     password = rnr_pass )
```

Talk API へのアクセスが記述された handle_message メソッドを R&R へのアクセスに書き換えます。
LINE で大量のメッセージを返すと見づらいので、最大でも3つまでの「title」と「url」を返すようにしました。この「title」と「url」は、今回の R&R データ固有の項目なので、必要に応じて変更してください。
RANKER の 自信度「ranker.confidence」が 0 のものが見つかったら、一つ目だけを返し打ち切ることにしました。このあたりはお好みで。

```server.py 
def handle_message(event):
 
    #query_string = event.message.text.encode("utf-8")
    query_string = urllib.parse.quote(event.message.text.encode("utf-8"), safe='')
    results = solrclient._send_request("GET", path="/fcselect?q=%s&ranker_id=%s&wt=json&fl=ranker.confidence,title,url" % (query_string, rnr_ranker_id))

    reply=""
    
    if len(json.loads(results)["response"]["docs"]) == 0:
       reply="見つかりませんでした。"

    else:
    #    for doc in json.loads(results)["response"]["docs"]:
        for i, doc in enumerate(json.loads(results)["response"]["docs"]):
            reply += doc["title"] + "\n" + doc["url"] + "\n\n"
            # max 3 docs
            if i >= 2:
                break       
            # only one for confidence == 0 if >= 3 items 
            if doc["ranker.confidence"] <= 0 :
                break
                
    line_bot_api.reply_message(
          event.reply_token,
          TextSendMessage(text=reply.rstrip() ))
```

#動かない純正API
実は IBM Watson Retrieve and Rank には検索したうえでランクを返す [Python 用のSearch and rank API](https://www.ibm.com/watson/developercloud/retrieve-and-rank/api/v1/?python#rank) があることになっています。

こんなコードで利用できることになっています。

```
import json
import pysolr
from watson_developer_cloud import RetrieveAndRankV1

retrieve_and_rank = RetrieveAndRankV1(
  username='YOUR_SERVICE_USERNAME',
  password='YOUR_SERVICE_PASSWORD')

solrclient = retrieve_and_rank.get_pysolr_client("sce94ccf57_1ee8_410b_8e67_3b2b3311a994", "example_collection")

solr_results = solrclient.search("wing")

ranking_results = retrieve_and_rank.rank("B2E325-rank-67", solr_results)

for ranking_result in ranking_results:
  print(json.dumps(ranking_result, indent=2))
```

しかし試すと、retrieve_and_rank.rank で「AttributeError: 'Results' object has no attribute 'read'」のエラーが発生します。

```
user01@ubuntu1604:~/test$ python3 rankApiTest.py
Traceback (most recent call last):
  File "rankApiTest.py", line 32, in <module>
    ranking_results = retrieve_and_rank.rank(rnr_ranker_id, solr_results)
  File "/home/user01/.local/lib/python3.5/site-packages/watson_developer_cloud/retrieve_and_rank_v1.py", line 134, in rank
    accept_json=True)
  File "/home/user01/.local/lib/python3.5/site-packages/watson_developer_cloud/watson_developer_cloud_service.py", line 302, in request
    **kwargs)
  File "/home/user01/.local/lib/python3.5/site-packages/requests/api.py", line 58, in request
    return session.request(method=method, url=url, **kwargs)
  File "/home/user01/.local/lib/python3.5/site-packages/requests/sessions.py", line 488, in request
    prep = self.prepare_request(req)
  File "/home/user01/.local/lib/python3.5/site-packages/requests/sessions.py", line 431, in prepare_request
    hooks=merge_hooks(request.hooks, self.hooks),
  File "/home/user01/.local/lib/python3.5/site-packages/requests/models.py", line 308, in prepare
    self.prepare_body(data, files, json)
  File "/home/user01/.local/lib/python3.5/site-packages/requests/models.py", line 496, in prepare_body
    (body, content_type) = self._encode_files(files, data)
  File "/home/user01/.local/lib/python3.5/site-packages/requests/models.py", line 159, in _encode_files
    fdata = fp.read()
AttributeError: 'Results' object has no attribute 'read'

```

検索すると [IBM dW に回避方法](https://developer.ibm.com/answers/questions/288779/retrieve-and-rank-python-what-kind-of-answer-data.html?smartspace=bluemix&wirelessshow=1) がありました。
今回は、記載があった、この回避策を利用しています。

```
results = pysolr._send_request("GET", path="/fcselect?q=%s&ranker_id=%s&wt=json" % (query_string, ranker_id))
 for doc in json.loads(results)["response"]["docs"]:
     print doc
```

前提モジュールが追加になったので requirements.txt は下記のように変更しました。

```requirements.txt
Flask==0.12.2
cf-deployment-tracker==1.0.4
cloudant==2.4.0
line-bot-sdk==1.1.0
pysolr==3.6.0
watson-developer-cloud==0.26.0
```

Python2系だと正常に動作しなかったので runtime..txt に下記をしています。

```
python-3.5.3
```

#デプロイ
Blumix にコードをデプロイします。

```
cf login -a https://api.ng.bluemix.net -u ユーザー名
cf push アプリケーション名

```

#テスト
バーコードからLINEで友達になってアクセスすると動いているのわかります。

<img width="320" alt="image.png" src="https://qiita-image-store.s3.amazonaws.com/0/184930/75f34262-cb56-5a49-ff5c-887bc667579c.png">

#今回のコード
今回のコードを [GitHUb](https://github.com/6onoda/linebotRnR) で公開しました。
[ryuta46 さんのコード](https://github.com/ryuta46/linebottalk) からの fork になっています。

ryuta46 さん、本当にありがとうございました。