Amazon EchoとIBM Watsonを連携させてみた - パート2 -

# **はじめに**
* [前回](https://qiita.com/TMR1000/items/637751727dc02daf93b9)に続き、AlexaとWatson（Conversation）の連携を解説する。


# **2. Alexa<->Watsonの対話を変換する(Node-RED)**
* さて、続いてIBM Cloudである。アカウント登録の詳細は割愛するが、Regionを聞かれたら米国南部か英国を選ぼう。フリーアカウントだとRegionは1つしか使えないが、Regionによって使える機能に若干差がある様で、米国南部か英国だと一通り揃っているためだ。Regionはアカウントのアップグレードをしない限り後から変更ができないので要注意だ。

* IBM Cloudに無事ログインできたら、カタログからNode-REDを探してみよう。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/61d41e82-353f-99cf-5d2c-b1acc77b721f.png)

* 続いてアプリ名の設定だ。ここにはAlexa Skills Kit(ASK)のエンドポイントで設定したリソース名を指定しよう。"mybluemix.net"というドメイン名が勝手に追加されるので、指定するのは "ibm-cloud-nodered" までで良い。ほかは変更する必要はない。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/761c2acb-51b4-0038-e647-4fc45ce851b5.png)

* Node-REDの環境が生成されるのをしばらく待つ。ステップを進めて次のNode-REDのログイン画面まで来たら、登録したユーザーID、パスワードでログインしよう。Node-REDの開発コンソールが立ち上がったらOKだ。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/b6cd212c-6e3c-1ea2-b6d3-4b35d9595e6c.png)

* これがNode-REDの開発コンソールだ。画面左側にある様々な機能のノードをドラッグ＆ドロップして、ノードの間をフローでつないでいき、必要に応じてノードに設定やコーディングを加える、という流れでアプリを作っていく。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/425fd061-6b25-9ce0-1102-e2da3b431b0a.png)

* 今回作成したフローの全体図はこれだ。少し冗長なところがあるが開発初心者のためお許し願いたい。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/850926bd-7c7c-7e7c-a54b-8dd26ab01896.png)


* 最初の"rest api"はHTTPノードである。これでAlexaからのRESTリクエストを受け付ける入り口を作る。メソッドはPOSTを指定し、ASK(Alexa Skills Kit)で指定したAlexaWatsonというリソース名を定義する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/ffffebb9-3676-daad-630f-f0e89883d13e.png)

* 続いて、"AlexaのセッションIDを取り出す"と"Intentを振り分ける（起動・停止発話と通常の発話）"の処理には、functionというノードを使っている。ここにはnode.jsで任意のロジックを書くことができる。msg.payloadというのは、Node-REDのノードで扱うメッセージオブジェクトの名称だ。ここではAlexaからのPOSTリクエスト時に受け取ったメッセージを取り出して次に渡している。なおNode-REDで扱うメッセージオブジェクトの形式はJSONである。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/8d4d888a-43e1-78bc-ed0c-5bc949a35285.png)


* "Intentを振り分ける（起動・停止発話と通常の発話）"ところではいくつか条件分岐をさせている。Alexaからスキルの起動と停止(LaunchRequestとStopIntent/CancelIntent)が来た時はtrue/falseのフラグを立てて、この後に接続するWatsonに渡す様にしている。それ以外の場合は、最後のelseで来た発話のテキストをそのままWatsonに渡す。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/710dbd90-46e1-f3fb-aa0c-4562cb749993.png)


* 次の"AlexaWatsonを呼び出す"はNode-REDにプリセットされているWatson Conversationを呼び出すためのノードである。ここでは接続の設定をするのみである。Username、パスワード、WorkspaceIDは、Watson Conversationの手順で解説する。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/3481554c-8c07-a33d-8fd5-4e467d0c3f1e.png)

* "応答文を取り出す"、"セッションIDをセットしてAlexaへ渡すJSONを生成する"もfunctionノードである。Watson Conversationからの応答をAlexaが解読できるJSONフォーマットに変換するためのロジックを入れている。詳細はコードを参照されたい。

* 最後に、"http"とあるところにHTTP responseノードを配置している。今回ここには特別な設定はしていない。

* 途中に、"msg.payload"というノードがくっついているが、これは各ノードが出力したJSONメッセージオブジェクトをプリントするためのノードである。意図した様なJSONが渡されているかを見ながらどこまでコードが正しく動いているかデバッグする際に使う。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/03422d60-1453-e11a-cbc1-470c7dab78ce.png)

* 画面右のデバッグタブに出力が出てくる。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/2ca4a8f3-0054-047a-b169-43f2662c7ef8.png)

* 以上で作成したNode-REDのコード(JSON形式)を添付する。


```
[
    {
        "id": "d7a5e7a3.de423",
        "type": "http response",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "statusCode": "",
        "headers": {},
        "x": 750,
        "y": 520,
        "wires": []
    },
    {
        "id": "16ded655.3b1d72",
        "type": "function",
        "z": "ed64aa8f.fe08c",
        "name": "セッションIDをセットしてAlexaへ渡すJSONを生成する",
        "func": "var resobj = \n{\n//    \"body\": {\n\t\t\"version\": \"1.0\",\n\t\t\"response\": {\n\t\t\t\"outputSpeech\": {\n\t\t\t\t\"type\": \"SSML\",\n\t\t\t\t\"ssml\": \"\"\n\t\t\t},\n\t\t\t\"shouldEndSession\": \"\" //true\n\t\t},\n\t\t\"sessionAttributes\": {\n\t\t    \"string\": \"\"\n\t\t}\n//\t}\n}\n;\n\nresobj.response.outputSpeech.ssml = '<speak>' + msg.payload + '</speak>'\nresobj.sessionAttributes.string = 'amzn1.echo-api.session.' + msg.user\nresobj.response.shouldEndSession = msg.context\n\nmsg.payload = resobj;\n\nreturn msg;\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 430,
        "y": 480,
        "wires": [
            [
                "480c7ab0.20cf84",
                "d7a5e7a3.de423"
            ]
        ]
    },
    {
        "id": "7e0e404e.ecf868",
        "type": "function",
        "z": "ed64aa8f.fe08c",
        "name": "応答文を取り出す",
        "func": "\nmsg.payload = msg.payload.output.text[0];\n\nreturn msg;\n\n",
        "outputs": 1,
        "noerr": 0,
        "x": 450,
        "y": 400,
        "wires": [
            [
                "9a4ff280.4d035",
                "16ded655.3b1d72"
            ]
        ]
    },
    {
        "id": "480c7ab0.20cf84",
        "type": "debug",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 750,
        "y": 440,
        "wires": []
    },
    {
        "id": "9191849a.8c7b4",
        "type": "watson-conversation-v1",
        "z": "ed64aa8f.fe08c",
        "name": "AlexaWatsonを呼び出す",
        "workspaceid": "XXXXXXX",
        "multiuser": true,
        "context": true,
        "empty-payload": true,
        "default-endpoint": true,
        "service-endpoint": "https://gateway.watsonplatform.net/conversation/api",
        "x": 410,
        "y": 300,
        "wires": [
            [
                "77dec5de.33335c",
                "7e0e404e.ecf868"
            ]
        ]
    },
    {
        "id": "9a4ff280.4d035",
        "type": "debug",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 650,
        "y": 360,
        "wires": []
    },
    {
        "id": "88ea2b01.0b8f78",
        "type": "function",
        "z": "ed64aa8f.fe08c",
        "name": "Intentを振り分ける（起動・停止発話と通常の発話）",
        "func": "\nif (msg.req.body.request.type==\"LaunchRequest\") {\nmsg.payload=\"LaunchRequest\"\nmsg.context=\"false\"\n} \nelse if (msg.req.body.request.intent.name==\"AMAZON.StopIntent\") {\nmsg.payload=\"StopRequest\"\nmsg.context=\"true\"\n}\nelse if (msg.req.body.request.intent.name==\"AMAZON.CancelIntent\") {\nmsg.payload=\"StopRequest\"\nmsg.context=\"true\"\n}\nelse {\nTargetString = msg.req.body.request.intent.slots.EveryThingSlot.value\nTargetString = TargetString.replace(/\\s+/g, \"\")\nmsg.payload=TargetString\nmsg.context=\"false\"\n}\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "x": 360,
        "y": 220,
        "wires": [
            [
                "40dcf332.b4d1cc",
                "9191849a.8c7b4"
            ]
        ]
    },
    {
        "id": "77dec5de.33335c",
        "type": "debug",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 650,
        "y": 260,
        "wires": []
    },
    {
        "id": "40dcf332.b4d1cc",
        "type": "debug",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 650,
        "y": 180,
        "wires": []
    },
    {
        "id": "41d82813.7f4ea8",
        "type": "function",
        "z": "ed64aa8f.fe08c",
        "name": "AlexaのセッションIDを取り出す",
        "func": "msg.payload=msg.req.body\nvar sourceSessionId = msg.req.body.session.sessionId\nvar sessionId = sourceSessionId.replace( \"amzn1.echo-api.session.\" , \"\" ) ;\nmsg.user=sessionId\n\nreturn msg;\n",
        "outputs": 1,
        "noerr": 0,
        "x": 230,
        "y": 140,
        "wires": [
            [
                "69cf4a88.424fb4",
                "88ea2b01.0b8f78"
            ]
        ]
    },
    {
        "id": "ca960160.456f68",
        "type": "http in",
        "z": "ed64aa8f.fe08c",
        "name": "rest api",
        "url": "/AlexaWatson",
        "method": "post",
        "upload": false,
        "swaggerDoc": "",
        "x": 90,
        "y": 60,
        "wires": [
            [
                "41d82813.7f4ea8"
            ]
        ]
    },
    {
        "id": "69cf4a88.424fb4",
        "type": "debug",
        "z": "ed64aa8f.fe08c",
        "name": "",
        "active": true,
        "console": "false",
        "complete": "payload",
        "x": 470,
        "y": 80,
        "wires": []
    }
]
```


* このコードは、開発コンソール右上のメニューからクリップボードにコピペでそのままインポートすることができるので、お試しいただきたい。なお、Watson ConversationのUsername、WorkspaceID、パスワードはご自身のものを設定する必要があることは、ご承知おきを。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/0c765ba5-07c5-774f-6189-44f7fe255eea.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/224839/8ea80782-b0e0-0b50-d9db-1e2c8960fe20.png)


* パート3に続く


# **関連記事**
* [Amazon EchoとIBM Watsonを連携させてみた - パート1 -](https://qiita.com/TMR1000/items/637751727dc02daf93b9)
* [Amazon EchoとIBM Watsonを連携させてみた - パート3 -](https://qiita.com/TMR1000/items/ca154882e25dbf0bacea)
* [Amazon EchoとIBM Watsonを連携させてみた - パート4 -](https://qiita.com/TMR1000/items/d3d2c1086c9cc72e1897)

以上