社内チャットの会話をWatsonで感情分析し、トラブルを未然に防ぐ。


IBM Cloud Advent Calendar 2018 12月16日分です。

序文

Slackのようなツールを、社内のサーバールーム内のサーバーや、自社用にリソース専有させたクラウド環境等で使いたいというニーズは常にあります。気づけば10年以上グループウェアの世界に、提案側、開発側、導入側、ユーザー側として携わり、ヘルプデスク組織の運営などもやってきました。新卒一年目にPHPでWikiシステムを一から作っていたなぁ...とか思い出はあります。

社外からの問い合わせやSNSへの対応は今後のネタとして残しておくとして、社内での会話からトラブルにならないように、ネガティブコメントを注視しておく必要があります。特にお客様とのトラブルに発展しないようにフォローできる仕組みを、IBM Cloudで使用可能なWatsonを用いて構築してみましょう。

準備

下記のような環境を用意します。

社内チャットシステムとして、Mattermostサーバーを用意します。

構築自体は、公式のオンラインドキュメント通りに実施しています。DBのパスワード等は変えます。
使用したMattermostは、無料で使えるOpen Source Team Editionで、バージョン5.6です。

外向きのWebhookを設定します。

Mattermost内に投稿先としてチームを作成し、チーム作成後「統合機能」の画面から、「外向きのWebhook」の設定を行います。

サンプルとして、トリガーワードは#helpとしています。

Mattermostにおける「外向きのWebhook」については、以前書いた記事がありますので、興味がありましたらご確認ください。
参考資料:Mattermost向けチャットボットをNode-REDとWatson Assistantで実装

タスク管理システムとして、Jira Software環境を用意します。

下記の手順を用いて構築しました。もちろん、ここではIBM Cloud IaaS上に構築していますが、自社のプライベートクラウドや社内のサーバールームでも構いません。後述のNode-RED環境から接続できれば良いです。
構築手順:IBM Cloudを用いてJIRA Softwareで、日々のタスク管理をはじめる。

Jiraに登録するタスクは、「課題」と言います。Redmineではチケットと言いますし、CRMなどでは、ToDoと言います。言い方の違いで意味は同じです。

カスタムフィールド作成

Jiraには感情分析した値を格納する項目がないため、感情分析した結果として「怒り」「嫌悪」「恐れ」「楽しみ」の4つの感情スコアの値を格納する追加項目、カスタムフィールドを作成します。また作成したカスタムフィールドIDを確認しておきます。
後述のサンプルコードでは、次のように設定しています。

カスタムフィールド名 フィールドタイプ カスタムフィールドID Node-REDの「POST - Create Issue」の記述
怒り 数値フィールド 10206 "customfield_10206"
嫌悪 数値フィールド 10207 "customfield_10207"
恐れ 数値フィールド 10208 "customfield_10208"
楽しみ 数値フィールド 10209 "customfield_10209"

MattermostとJira Softwareのデータをつなぐために、Node-REDサーバーを用意します。

MattermostとJira Softwareの両方に接続できる環境に、Node-REDサーバーを構築します。MattermostとJira Softwaareの両方に接続できれば、パブリッククラウドやプライベートクラウド、社内のサーバールームでも構いません。Node-REDのインストールには、公式のオンラインドキュメントを使用しています。

Node-REDで実装

Node-REDに追加したノード

サーバーにNode-REDをインストールした後、標準では付属しないWatsonやJiraに接続するノードを追加します。
node-red-contrib-jira:https://flows.nodered.org/node/node-red-contrib-jira
node-red-node-watson:https://flows.nodered.org/node/node-red-node-watson

フロー図

処理の流れ

  1. Mattermostに投稿されたトリガーワード付きのメッセージをNode-REDで受信
  2. Node-REDで受信したメッセージを「Watson Language Translator」で日本語から英語に翻訳。翻訳する理由は、感情分析を行う「Watson Tone Analyzer」が2018年12月現在日本語未対応のため、英語に翻訳します。
  3. 「Watson Tone Analyzer」で感情分析します。
  4. 感情分析の結果、怒りのスコアが「0.05」以上の時に、タスク管理システムの「Jira」に課題として登録するためのJSON形式のデータを作成します。
  5. 作成したJSON形式のデータを「Jira」に渡し、要対応のネガティブコメントとして新規課題登録します。

サンプルコード

下記のサンプルコードをNode-REDで読み込み、「language translator」ノードや「tone analyzer」ノード、「jira-issue-create」ノードをそれぞれダブルクリックし設定します。

[{"id":"fa32047e.c96898","type":"tab","label":"感情分析によるタスク管理","disabled":false,"info":""},{"id":"d1690281.96fc","type":"http in","z":"fa32047e.c96898","name":"Mattermost POST","url":"/mmpost","method":"post","upload":false,"swaggerDoc":"","x":130,"y":40,"wires":[["272aba56.17d726"]]},{"id":"dcdc3ecc.d75f2","type":"debug","z":"fa32047e.c96898","name":"debug:Mattermost Imcoming Webhook","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":670,"y":40,"wires":[]},{"id":"272aba56.17d726","type":"function","z":"fa32047e.c96898","name":"Cut Trigger Word","func":"var message = msg.payload.text;\nvar cutmsg = message.slice(5);\nmsg.payload = cutmsg;\nreturn msg;","outputs":1,"noerr":0,"x":350,"y":40,"wires":[["dcdc3ecc.d75f2","2f8329a9.bc1e56","ebe4c8a3.3ca2d8"]]},{"id":"2f8329a9.bc1e56","type":"watson-translator","z":"fa32047e.c96898","name":"","action":"translate","basemodel":"en-tr","domain":"general","srclang":"ja","destlang":"en","password":"","apikey":"","custom":"","domainhidden":"general","srclanghidden":"ja","destlanghidden":"en","basemodelhidden":"","customhidden":"","filetype":"forcedglossary","trainid":"","lgparams2":true,"neural":false,"default-endpoint":true,"service-endpoint":"https://gateway.watsonplatform.net/language-translator/api","x":150,"y":160,"wires":[["7e04a1f.8bdb26"]]},{"id":"7e04a1f.8bdb26","type":"watson-tone-analyzer-v3","z":"fa32047e.c96898","name":"","tones":"emotion","sentences":"true","contentType":"false","tone-method":"generalTone","interface-version":"2016-05-19","inputlang":"en","default-endpoint":false,"service-endpoint":"https://gateway-tok.watsonplatform.net/tone-analyzer/api","x":380,"y":160,"wires":[["32339235.6f97ce","27c3ad11.5e6372"]]},{"id":"32339235.6f97ce","type":"debug","z":"fa32047e.c96898","name":"debug:感情分析","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","x":600,"y":160,"wires":[]},{"id":"9db37b51.23bfb8","type":"jira-issue-create","z":"fa32047e.c96898","name":"","server":"667cbe9c.e4b3e","x":600,"y":240,"wires":[["cc2c73ae.2807d"]]},{"id":"27c3ad11.5e6372","type":"switch","z":"fa32047e.c96898","name":"","property":"response.document_tone.tone_categories[0].tones[0].score","propertyType":"msg","rules":[{"t":"gte","v":"0.05","vt":"str"},{"t":"else"}],"checkall":"true","repair":false,"outputs":2,"x":130,"y":247,"wires":[["f132ac7a.3a2d1"],[]]},{"id":"ebe4c8a3.3ca2d8","type":"function","z":"fa32047e.c96898","name":"variable:issue subject","func":"var message = msg.payload;\nvar msg = {\n    note: message\n    };\n    flow.set('desc',msg);\nreturn msg;","outputs":1,"noerr":0,"x":620,"y":100,"wires":[[]]},{"id":"f132ac7a.3a2d1","type":"function","z":"fa32047e.c96898","name":"POST - Create Issue","func":"var desc         = flow.get('desc');\nvar note         = desc.note;\nvar angerscore   = msg.response.document_tone.tone_categories[0].tones[0].score;\nvar disgustscore = msg.response.document_tone.tone_categories[0].tones[1].score;\nvar fearscore    = msg.response.document_tone.tone_categories[0].tones[2].score;\nvar joyscore     = msg.response.document_tone.tone_categories[0].tones[3].score;\nmsg.payload  = {\n        \"fields\": {\n            \"project\":{\"key\":\"EMOTION\"},\n            \"summary\":\"ネガティブコメントを確認\",\n            \"description\":note,\n            \"issuetype\":{\"id\": \"10002\"},\n            \"customfield_10206\":angerscore,\n            \"customfield_10207\":disgustscore,\n            \"customfield_10208\":fearscore,\n            \"customfield_10209\":joyscore\n        }\n    }\nreturn msg;","outputs":1,"noerr":0,"x":340,"y":240,"wires":[["9db37b51.23bfb8"]]},{"id":"cc2c73ae.2807d","type":"debug","z":"fa32047e.c96898","name":"debug:Jira Create Issue","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","x":630,"y":300,"wires":[]},{"id":"667cbe9c.e4b3e","type":"jira-server","z":"","url":"http://jira-dev.kolinzlabs.com:8080/jira/rest/api/latest/","name":"Jira Server"}]

language translatorノードの設定

IBM Cloudにアクセスし、Watson Language TranslatorのAPI鍵とエンドポイントURLを確認し設定します。

tone analyzerノードの設定

IBM Cloudにアクセスし、Watson Tone AnalyzerのAPI鍵とエンドポイントURLを確認し設定します。

jira-issue-createノードの設定

「jira-issue-create」ノードの編集画面で、Jira Softwaare用APIへの接続設定を行います。のドキュメントに無いため接続設定におけるURLは、お使いのJira Software環境におけるJira REST APIのURLになります。

https://your-jira-url/rest/api/latest/

動作確認

Mattermostにトリガーワード(サンプルコードでは、#help)をつけて、お客様から言われそうなメッセージを投稿します。Watson Tone Analyzerにより感情分析され、怒りや嫌悪といったネガティブコメントと判断された場合、Jira Softwareに新規課題として登録されることを確認できました。

まとめ

Node-REDを使用することで、社内チャットシステムのMattermostとタスク管理システムのJira Softwareを繋げて、ネガティブコメントがあればフォロー用のタスク(課題)を自動登録する動きを確認することができました。この仕組は、Mattermostを外部向けのSNSに変更することで、マーケティング活動や広報業務にも応用できるかもしれません。(次回のネタにします。)