Watson Conversation APIでBotを作るための記事

Node.jsベースのBotkitでBotを開発、IBM Cloudで運用しているのだが、Nodeのブロックング/ノンブロッキングとBotkitの非同期処理の扱いが良くわからず、細かい所の制御が出来ずにいる。bot.replyとか非同期で動くのだが、強制的に順序を合わせることが出来ずにいたり… 自分の理解不足を感じる。

ただ、簡単なことをやるには簡単だし、見た目(返信の順序とか)を気にしなければ良いと言えば良いので、自分や小さいコミュニティ向けのBot開発には悪くない選択ではないか。HubotやRubotyも昔使ってみたことがあるが、CoffeeScriptRubyじゃなく、JavaScriptでやりたいという強い意志がある場合には特に良い。

これに味をしめて、もう少しちゃんとした会話が出来るBotを作ってみようと考えているが、同じIBM Cloudということで、Watson Conversation APIが気になっている。

www.ibm.com

これは

  • Intents: 会話の中身
  • Entities: 会話構成要素となる単語など
  • Dialog: 会話のフロー

を定義することで、ノンプログラミングでチャットのコア部分が出来るという優れもの。しかも、Watsonなので、Entitiesなどは勝手に学習して賢くなっていってくれる。GoogleのDialogflow(旧api.ai)もそうだが、こんな簡単にChat Botが出来るなんて、なんて良い世の中になったもんだ。

ということで、次はWatson Conversation APIを使ってみようと考えているのだが、そんな時に秀逸な紹介記事が出ていることに気づいた。

ThinkWatsonというIBMのオウンドメディアでの記事だが、次の4記事が公開されている。

www.ibm.com

www.ibm.com

www.ibm.com

www.ibm.com

この4記事で、Conversation APIを用いたチャットの設計・開発、Node-REDを使って、これまたノンプログラミングでSlackと連携、API化してWordPressに組み込むことまで出来る。素晴らしい。

5年くらい前に、もう無くなってしまったYahoo! PipesTwitterBotを作ったことがあったが、その時はなんだかんだで結構たいへんだった。良い世の中になったもんだ。

Botにリマインドしてもらう

takoratta.hatenablog.com

昨日書いたBotkitを使ったBotIBM Cloudライトアカウントを使って動かしているが、このIBM Cloudライトアカウントは

  • 10日間 開発なしでアプリを自動停止
  • 30日間 活動なしでサービスの自動削除

という制限がある。Botなので、ずっと動かし続けているから、後者は良いとして前者については、開発をし続けるしかない

忘れないようにリマインダーとして、Slackにメッセージを送って貰うようにした。Botkitとnode-cronを用いた。

const = require('cron'); して、startRTM()のところを次のように変えた。

controller.spawn({
    token: process.env.token
}).startRTM((err, bot, payload) => {
    if (err) {
        throw new Error(err);
    }
    new cron.CronJob({
        cronTime: '0 0 9 17 0 *', // 1/17の9時に起動
        onTick: () => {
            bot.say({
                channel: 'takoratta_test', // チャンネル名
                text: 'IBM Cloudライトアカウントを使っているので開発し続けないとサービスが停止します'
            });
        },
        start: true,
        timeZone: 'Asia/Tokyo'
    });
});

cronTimeのところはサービスが停止する数日前にしておけば良い。本当は更新期限をIBM Cloud側から取得したかったが、どうすれば良いかわからないので、ハードコードとしてある。ソースコードを変更後に、cf pushするときに、ここも更新しておけば良い。ここだけ更新するだけで開発したことになるだろうとも思うし、こんなリマインダー送るようなことしなくても、cf pushをローカルマシンでCronしておけば良いではないかというチートも考えつくがやっちゃいけない。

ところで、Cronの時間設定のフォーマットはいつも間違える。今回も月が0から始まることを忘れていた。ここは、cronTime: 'sec min hour day month(0~11) week'となっている。

BotにSlackのプライベートチャンネルリストを教えてもらう(Node.jsのBotkitとIBM Cloudを用いた対応)

課題

自分が属している団体のSlackで運営スタッフだけのプライベートチャンネルがいくつかあるが、プライベートチャンネルのため、どのような運営用チャンネルが存在しているかわからなくなるという問題がある。一度誤って抜けてしまうと、誰かに頼まないと戻れなくなるのだが、誰に頼んで良いのかもわからなくなることもある。

この問題を解決するためにBotを開発した。ただし、プライベートチャンネルのため、Botもそのプライベートチャンネルに所属していないとならない。

運用イメージ

  • 運営用プライベートチャンネルはBotをメンバーとして登録する
  • 運営スタッフはそのBotにメンションかDMかで話しかけることにより、Botが属しているプライベートチャンネルの全リストをその作成者名とともに取得できる
  • 自分が入るべきプライベートチャンネルがあったならば、その作成者にコンタクトし、招待してもらう。ここにはBotは介在しない

Botの開発

BotはNode.jsで開発し、IBM Cloudで運用することにした。

Botkit

Node.jsで作られたBotkitを利用した。Slackとの連携も極めて簡単。

使ったものは、

ソースコードは以下。

const Botkit = require('botkit');
const async = require('async');

if (!process.env.token) {
  console.log('Error: Missing API Token');
  process.exit(1);
}

const controller = Botkit.slackbot({
    debug: false,
    retry: Infinity // 常駐させるため
});

controller.spawn({
    token: process.env.token
}).startRTM((err, bot, payload) => {
    if (err) {
        throw new Error(err);
    }
});

// pubchlistと聞かれたら
controller.hears('pubchlist',['direct_message','direct_mention','mention'],(bot,message) => {
    bot.api.channels.list({}, (err, res) => {
        async.each(res.channels, (channel, callback) => {
            if(!channel.is_archived){
                bot.api.users.info({user:channel.creator}, (err,res) => {
                bot.reply(message, channel.name + ' created by ' + res['user']['name']);
                })
            }
        })
    })
});

// prchlistと聞かれたら
controller.hears('prchlist',['direct_message','direct_mention','mention'], (bot,message) => {
    bot.api.groups.list({}, (err, res) => {
        async.each(res.groups, (groups, callback) => {
            if(!groups.is_archived){
                bot.api.users.info({user:groups.creator}, (err,res) => {
                bot.reply(message, groups.name + ' created by ' + res['user']['name']);
                })
            }
        })
    })
});

// それ以外を聞かれたら
controller.hears(['(.*)'], ['direct_message','direct_mention','mention'], (bot,message) => {
    bot.reply(message, 'Usage: type prchlist to get private channels list OR publist to get public channels');
});

Botに話しかけられるコマンドは2つだけ。

  • pubchlist: パブリックチャンネルの一覧をその作成者とともに表示
  • prchlist: Botが所属しているプライベートチャンネルの一覧をその作成者とともに表示

【追記】Admin権限を持つ人だけがプライベートチャンネルリストを見れるようにする

プライベートチャンネルリストが見れる人をAdmin権限を持つ人だけに絞るには、prchlisthearsしている部分を以下のように変更する。

// prchlistと聞かれたら
controller.hears('test',['direct_message','direct_mention','mention'], (bot,message) => {
    var currentUser; 
    bot.api.users.info({user:message.user},function(err,response) {
        if (err) {
            throw new Error(err);
        }
        currentUser = response['user'];
        if (!currentUser['is_admin']) {
            bot.reply(message, 'Only Admin can see the list of private channels');
        }
        bot.api.groups.list({}, (err, res) => {
            async.each(res.groups, (groups, callback) => {
                if(!groups.is_archived){
                    bot.api.users.info({user:groups.creator}, (err,res) => {
                    bot.reply(message, groups.name + ' created by ' + res['user']['name']);
                    })
                }
            })
        })        
    })
});

Slack APIusers.infoでBotkitから代えるmessage.user情報を取得し、Admin権限があるかどうかを確認している。

Slack側の準備

プログラムの中でSlackのBot用のAPI Tokenを利用している。これは環境変数で引き渡すことになる。

Sign in | Slack にアクセスし、API Tokenを発行する。

f:id:takoratta:20180109155043p:plain

ローカル環境での動作

$ token=<API Token> node app.js

これでSlack側でBotにDMするか、Inviteしたチャンネルでメンションし、話しかけてテストできる。

Botの運用

BotはNode.jsが動かせる環境ならなんでも良かったのだが、IBM Cloudライトアカウントの無料枠の範囲で十分そうだったので、それを使った。

$ cf api https://api.ng.bluemix.net/
$ cf login
$ cf push itdartbot --no-route -u none -m 256M
$ cf cf set-env itdartbot token <api token>
$ cf restage itdartbot

cf pushでの--no-route-u noneはワーカーとして動作させ、Health Check不要のため。また、-m 256MIBM Cloudライトアカウントではメモリが256MBに制限されているため。

参考にしたもの

推薦図書(メディアや書籍で紹介したもの一覧)

お勧め図書は?という質問を受けることがある。

ビジネス書で良い本もあるし、文学書で好きなものもあるが、ここではIT系のメディアで今まで聞かれた際に答えたものを整理しておこう。

まえがきとして書いておくと、正直、推薦図書と言われても回答するのは結構難しい。推薦図書とは、1) 自分が影響を受けた本なのか、2) 今の人に参考にして欲しい本なのかで、まず悩むし、1) の自分が影響を受けた本だとしても、どの時期に影響を受けたのか、さらには 2) のその本を今の人に参考にして欲しいのかでも悩む。参考にして欲しいとしても、そのターゲットは誰かでも薦める本は異なる。

というような悩みを抱えつつ、今までパブリックなメディアや書籍などでお勧めした書籍は以下のものだ。

まず、日経BP ITPro Watcherブログ - Avoid Note*1から

itpro.nikkeibp.co.jp

 

itpro.nikkeibp.co.jp

itpro.nikkeibp.co.jp

 

少し脱線するが、このAvoid Noteというブログは結構工夫をして書いていたものだった。個別の記事は検索すればひっかかるのだが、一覧から辿れなくなってしまっているのはちょっと悲しい。 今に記事本体も消されてしまいそうな予感がするので、後でアーカイブしておこう。

 

2012年にデブサミの記念書籍にも寄稿して書籍を推薦した。

www.amazon.co.jp

その原稿がこれだ。

takoratta.hatenablog.com

 

そして、今年にまた書籍を紹介して欲しいと言われて寄稿したのが以下の記事だ。自分では違う書籍を紹介しようと思っていたのだったが、日経BP ITPro Watcherで推薦したものを忘れてしまい、こちらでもヘネパタ本を紹介してしまっていた。

employment.en-japan.com

 

今のヘネパタ本はこれ。内容が結構更新されているみたいなので、久しぶりに読んでみようかと思う。 

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

ヘネシー&パターソン コンピュータアーキテクチャ 定量的アプローチ 第5版

 

これ以外にも影響を受けた本はあるので、おいおい紹介していくかもしれない。

以上、Facebookにふんわりと書いておいたのだが、Facebookだと流れていってしまうので、自分の記録ようにこちらに書き直した。

Facebookの投稿はこれ → 及川 卓也 - 私も推薦図書を挙げさせて頂いています。一人だけ、やたら長文になってしまっていて、これだから年寄りは昔話... | Facebook

*1:2008年ごろに連載を持っていた

加齢による記憶力低下に対する考え方

今年もCROSS先達に聞くこれからのエンジニア像というセッションに参加した。例年にも増して、言いたい放題の本当に勝手放題してしまったが、今年は放言に加えて、年金だとか終活だとか、参加者を無視した内容になってしまったことは反省している*1

セッションの中で出た30年前に戻ったら何をしたいかという質問に、私以外の登壇者2名*2は数学とお答えになられていて、「今勉強しても、昔と違って記憶力が悪くなっている。(だから昔にもっと勉強しておけば良かった)」と発言されていた。

セッション内でも言ったのだが、おそらくこれは正しくない。

今も昔も覚えるのは大変なのだ。年を取ったことで記憶できなくなったのではなく、昔も覚えるのには苦労したのだ。

私がそのように指摘したら、「昔から能力が低かったってことですね」と笑われていたが、それはちょっと卑屈すぎる冗談として、昔が良かったと考えることでいろんなことを言い訳にしてしまいがちになる。

今が大変なのは、本当に大変なのであって、昔が良かったからではない。そう考えるだけで、物事への捉え方は変わる。

新しい技術を覚えるのが大変だと感じても、それはその技術の習得が本当に難しいからだ。そう考えたい。

数学の話に戻ろう。

数学の場合は、新しい事柄を学ぶのとは異なる。一度覚えた(はず)のことを再学習するということだ。そのため、本当は、昔覚えていたときの体験と、今の再学習での体験を比較することが可能だ。その上で、「昔のほうが記憶力は良かった」かどうかを判断することができる。

だが、それはおそらく無駄だ。なぜなら、「昔、記憶*3していたかどうか」や「どれだけスムーズに記憶していたか」をあなたはもう覚えていないはずだから ;)

*1:本当はそんなにしていない

*2:伊勢さんと吉岡さん

*3:ずっと「記憶」と書いているが、正しくは「習得」かもしれない