astamuse Lab

astamuse Labとは、アスタミューゼのエンジニアとデザイナーのブログです。アスタミューゼの事業・サービスを支えている知識と舞台裏の今を発信しています。

AI社員のつくり方

フロントエンドエンジニアをしているkitoです。
今回は、Slackにいると便利なAI社員ことチャットボットについて書きたいと思います。
事の発端は、メルカリさんの「AI社員」が入社! 翻訳から日々の業務サポートまで大活躍中というブログのエントリーを読んだことです。Dialogflowを知らなかったこともあり、非常に興味深い内容で、弊社にもAI社員がいたら便利なのでは? と漠然と思うようになりました。
そして実際、HISASHIくんにインスパイアされたAI社員をつくってみたので、その概要を書きたいと思います。

Dialogflow

Dialogflowは、Googleが運用開発している自然言語解析プラットフォームです。自然言語処理を丸投げできるので、対話型botをつくる際には本当に有り難いサービスです。これなしで気軽にチャットボットはつくれないでしょう。しかも、Standard Editionの範囲なら無料で利用できます。今回、こちらのサービスを利用します。

さて、社内のヘルプデスク業務などを行うAI社員に必要な機能として最低限下記が必要かなと思います。

  • 気軽に話かけられる
  • メンションつきの質問に答えられる
  • 正しい答えを学習させられる

まず、気軽に話しかけるか否かですが、あまり難しく考えても仕方がないので、HISASHIくん同様いらすとやのイラストをアイコンにして、mizekoという名前にしました。(弊社、アスタミューゼなので。。)

f:id:astamuse:20191212111053p:plain

DialogflowとSlackの連携

メンションつきの質問に答えられるようにするのは、予想外に骨が折れました。
Dialogflowには、Slack、Facebookなどと簡単に連携できるintegrationsという機能があり、当初これを利用すれば簡単にAI社員ができるのではと思っていました。

しかし、実際には、@mizekoのようなメンションつきのテキストに反応するだけではなくて、あらゆるテキストに反応してしまい、チャットの邪魔になってしまいました。通常のチャットボットのユースケースならば発言する人が一人いて、それに随時反応すれば良いのかもしれませんが、会社のSlackで使う場合、様々な人がチャットに参加しているので、メンションつきもしくは特定のテキストにだけ反応して応答するようにしなければなりません。
これを解決するために、integrationsを使うのではなく、すべてAPIを通じて受け答えするように変更しました。
SlackのAPIでパブリックなチャンネルのテキストを取得し、@mizekoのメンションがついていた場合、DialogflowのAPIにテキストを投げ、解析の結果をSlackのAPIを通じて該当チャンネルに投稿するという流れです。

SlackのAPI

まず、SlackのEvent APIでパブリックなチャンネルのメッセージを待ち受けて、メッセージに@mizekoのメンションがあれば、DialogflowのAPIを叩く必要があるので、Request URLを待ち受けるサーバーが必要になります。
開発中や手元で試してみるなら、ngrokを使うのが簡単でしょう。本格的に試すならfirebaseが第一選択として考えられます。ただし、外部APIを呼ぶ場合は、有料プランのFlameかプランBlazeプランにする必要がある点注意です。Blazeプランの無料枠があるので、使い方によっては良い選択肢だと思います。私は、重量課金が少し気になったので他で用意しました。

さて、肝心のSlackのEvent APIですが、まずこちらでCreate New APPから新しいAPPを作成します。
OAuth & PermissionsにあるOAuth Access Tokenを控えておきましょう。 f:id:astamuse:20191212111130p:plain 次に、Event Subscriptionsの「Subscribe to workspace events」でSubscribeするEventからmessage.channelsを選びます。
f:id:astamuse:20191212111154p:plain 同じページの一番上にあるEnable EventのスイッチをONにして、Event Subscriptionsから、Request URLを指定します。
私の場合、Express.jsで下記のようなコードでRequest URLをSubscribeしました。

const express = require("express");
const app = express();
const bodyParser = require("body-parser");
app.use(bodyParser.json());

app.post("/api/channels.history", function(req, res) {
    res.setHeader("Content-Type", "text/plain");
    res.send(req.body.challenge);

    //ここでDialogflowの処理

});

app.listen(process.env.PORT || 8080);

channels.historyのレスポンスは、公式のSlack APIを参考にしてください。

DialogflowのAPI

Dialogflowは、先にも書いたように自然言語解析プラットフォームでIntentというユーザーのリクエスト単位で管理されています。「今日の東京の天気教えて?」というユーザリクエストに、「東京は今日晴れです」と答えるのがひとつのIntentになります。
今回のAI社員には、新規Intentを覚えさせ、またIntentをUpdateして再学習させなければなりません。
要は、Slackのメッセージに応じて、DialogflowのAPIでIntentをCreateしたりUpdateしたりすることになるのですが、例えばAI社員が間違った回答をしたとき何を基準にIntentをUpdateすれば良いのでしょうか?

メルカリさんのHISASHIくん場合、ブログのスクリーンショットから察すると、ある特定のチャンネルでの質問に対して、HISASHIくんの回答にスレッド形式で:hisashi:という絵文字をつけると正しい答えとしてみなして、後で?Updateしているようでした。

このスレッド形式での正しい答えを絵文字つきで教える方式は、クレバーなやり方だと思ったので、弊社のmizekoでも採用しました。
ただ、特定のチャンネルではなくすべてのチャンネルで、かつリアルタイムで学習して欲しかったので、APIでIntentをCreateもしくはUpdateするようにしました。SlackのAPIの設定で、Event Subscriptionsを「Subscribe to workspace events」で設定したのは、すべてのパブリックなチャンネルで、AI社員mizekoに質問でき学習させるためです。

DialogflowのAPIに関しては、公式のリファレンスが参考になります。

公式のサンプルをそのまま使えるので、最初の設定さえ間違えなければそれほど迷わないと思います。 ポイントとしては、detectTextIntentでintentの検出を行い、帰って来た結果をSlackで返信するところです。

function detectTextIntent(projectId, sessionId, queries, languageCode) {
  const dialogflow = require('dialogflow');
  const sessionClient = new dialogflow.SessionsClient();

  async function detectIntent(
    projectId,
    sessionId,
    query,
    contexts,
    languageCode
  ) {
    //省略  
    const responses = await sessionClient.detectIntent(request);
    return responses[0];
  }

  async function executeQueries(projectId, sessionId, queries, languageCode) {
       //省略

       //slackのチャンネルに返信
       await slack.chat.postMessage({
                    token: botToken,
                    channel: _channel,
                    text: intentResponse.queryResult.fulfillmentText
                })

  }
  executeQueries(projectId, sessionId, queries, languageCode);

}

また、Slackのメッセージがスレッドであるかは、channels.historyレスポンスのメッセージにthread_tsが含まれているかどうかで判断できます。 スレッド上でのメッセージの場合は、:mizeko:という絵文字があれば、それは正しい答えを教えようとしているのだと判断できます。 どのthreadが該当のthreadであるかは、conversations.repliesから導きだせます。

f:id:astamuse:20191212111217p:plain

Threadで正解を教え、もう一度同じ質問をすると正しく答えてくれます。(IntentのUpdateで上書きしているので、後から教えた方を覚えます) f:id:astamuse:20191212124201p:plain

まとめ

メルカリのHISASHIくんにインスパイアされたAI社員をつくり、APIを通じて簡単な学習機能をつけました。社内で広く使われていくためには、 Dialogflowコンソールからの手入力による柔軟な運用が必要になるのではないかという気もしますが、自動化できる部分は自動化して勝手に育ってくれると嬉しいですね。
これからのオフィスでは、生産性を上げるためにBotがさらに活用されていくことでしょう。Botとうまく付きあって生産性を上げていきたいですよね? f:id:astamuse:20191212120422p:plain

アスタミューゼでは、エンジニア・デザイナーを募集中です。ご興味のある方は遠慮なく採用サイトからご応募ください。お待ちしています。

Copyright © astamuse company, ltd. all rights reserved.