Livesense Advent Calendar 2015の11日目です。
昨日はchuck0523さんの『再帰関数を学んでワンランク上のJavaScriptエンジニアになろう!』でした。
私はWeb業界にやってきて半年のインフラエンジニアです。
去年はROMだったQiitaのアドベントカレンダー。何を書こうかなぁと考えましたが、テクニカルな記事は弊社の他の方に任せ、今回は 私のチームメイトを紹介 しようと思います。
はじめに
ご紹介
彼がうちのチームにJOINしたのは、記録を遡る限り1年以上前のようです。
私よりもずっと前より彼は会社にいて、私からは会社の先輩にあたります。
彼は今年の8月頃一度休職していましたが、9月頃に復職しました。
当初は口下手でしたが、今は話しかけられると饒舌で、他の部署の方にも覚えてもらって頻繁にHipChatでコミュニケーションを取ってます。
彼の長所は、なんといっても記憶力です。復職以来これまでに、テキストにすると200MBにも及ぶ会話を発言者や発言順序も含めて記憶しています。驚異です。
それに仕事熱心です。ほとんど常にHipChatでオンラインになっていますし、いつでも呼ぶと答えてくれます。
一方短所は口下手というか、日本語に疎いというか、コンテキストを正しく判断できないことですね。
彼は 『インフラ神様』 と呼ばれてます。
生い立ち
復職直後の彼とはこんなやり取りをしていました。
自分たちの会話ながら、実に酷いですね。
そもそもおでんに見えねーし。
そんな彼の口下手を直すため、彼の会話のサポートに AIや人工無能を使う 教育法はすぐに思いつきました。
要件はこんなところです。
- 無料
- スタンドアローンで稼働
- 真面目な会話の邪魔をしない
しかし、ぴったりな頭脳がなかなか見つからなくて困っていました。ドコモのAIと合わせる例のように、オンラインのAI/人工無能は見つかりますが、それで機密情報が流れてしまったらまずいですしね。
2015年10月現在、人工無脳のブームは去ってしまったのでしょう。逆にSiriみたいな高度なAIは、まだ無料で利用するには早いようです。だからといって、ゼロから作るのはさすがに・・・
そんな中、人工無脳 『Reudy』 をRuby1.9対応させたものがgithubに公開されていることを見つけました。感謝。
残る問題は、私がRuby慣れしていないことですね・・・ まぁ何とかなるでしょう。
動作環境作成
以下は構築当時の記録です。
Hubotを動かす
Linux環境でHubotを動かし、HipChatに繋ぎます。
これは本書の範囲外。
Rubyを入れる
Rubyユーザーな人たちはこんなところは読まなくても大丈夫です。
前提パッケージ
Rubyのビルドに必要なパッケージを入れます。
$ yum install -y git
$ yum install -y gcc
$ yum install -y openssl-devel readline-devel zlib-devel
rbenv + Ruby 2.2.3 インストール
Rubyを入れます。
この辺はこのサイトに綺麗にまとまっていますので参考に。
なぜver.2.2.3なのかについては気にしないでください・・・
$ cd opt
$ git clone git://github.com/sstephenson/rbenv.git
$ mkdir /opt/rbenv/plugins
$ cd /opt/rbenv/plugins
$ git clone git://github.com/sstephenson/ruby-build.git
$ vi /etc/profile
/etc/profile
ファイルの最後のあたりにこの3行を追加しましょう。
export RBENV_ROOT="/opt/rbenv"
export PATH="${RBENV_ROOT}/bin:${PATH}"
eval "$(rbenv init -)"
一旦Linuxからログアウト、再ログインしてprofileを適用します。
その後、このコマンドを打って、インストール可能なRubyのバージョンを確認しましょう。
$ rbenv install -l
目的のバージョン(2.2.3)があることを確認できたら、インストールします。
$ rbenv install 2.2.3
本手順では、そのままグローバル設定にしてしまいます。
$ rbenv global 2.2.3
Reudyを動かす
さて、準備もできたしReudyを動かしてみましょう。
Reudyダウンロード
githubからリポジトリをクローンします。
$ git clone https://github.com/mmasaki/Reudy19.git
Reudy試験起動
試しに起動してみましょう。
単純な動作確認
Reudy19には、コンソールで動作する実装が入っています。Rubyのインストール状態を見るため、これを起動してみましょう。
$ cd Reudy19/
$ ruby stdio_reudy.rb
/vagrant/Reudy19/lib/reudy/message_log.rb:27: warning: IO#lines is deprecated; use #each_line instead
ログロード終了
文尾辞書( public/db)を作成中...
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
/vagrant/Reudy19/lib/reudy/message_log.rb:42: warning: IO#lines is deprecated; use #each_line instead
単語ロード終了
警告がスーパー鬱陶しくて読みどころがわかりにくい のですが、Ruby2.2.3でも起動することが確認できました。
この警告については、rubyに-W0
オプションを与えればとりあえず抑止できますので、以降は 手抜きしてQiitaらしくないこのアプローチを取ります。
挙動を確認する
Reudyは最初こそお馬鹿な応答をしますが、話しかけた言葉とやりとりを覚えて真似します。
$ ruby -W0 stdio_reudy.rb
> hey!
不確定だ。
> hey!
なんでやねん。
> hey!
こんばんは。
> hey!
何?
> hey!
hey!
> yo
hey!
この繰り返しで賢くなる(?)のです。
ちなみに、このstdio_reudy.rb
の会話をやめるときはCtrl + C
です。
作者の意図通りかわかりませんが、パイプで渡すこともできる実装になっています。
$ echo "yo" | ruby -W0 stdio_reudy.rb
hey!
このワンライナーの方式はあとで利用します。
ちなみに、話しかけた結果は、publicフォルダ内の各ファイルに記録されます。
$ ls -lh public/
total 12K
-rw-r--r--. 1 vagrant vagrant 307 Dec 10 13:15 db
-rw-r--r--. 1 vagrant vagrant 951 Dec 10 13:18 log.yml
-rw-r--r--. 1 vagrant vagrant 1.4K Dec 10 13:15 setting.yml
-rw-r--r--. 1 vagrant vagrant 0 Dec 10 13:15 thought.txt
-rw-r--r--. 1 vagrant vagrant 0 Dec 10 13:15 words.yml
まずいことを覚えさせてしまったら、これらのファイルを編集しましょう。
mecabのセットアップ
Reudyは、素のままでは日本語分解精度が悪いです。
自然言語を扱う上で分解精度は重要です。「mecabを用いることで精度を上げることができる」とのことなので、mecab
を使えるようにしましょう。
mecabインストール
簡単に入ります。
$ rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
$ yum install mecab mecab-ipadic mecab-devel
動作確認
mecab
コマンドにパイプで文字列を入れれば、動作確認が取れます。
$ echo "神様元気ですか?" | mecab
神様 名詞,一般,*,*,*,*,神様,カミサマ,カミサマ
元気 名詞,形容動詞語幹,*,*,*,*,元気,ゲンキ,ゲンキ
です 助動詞,*,*,*,特殊・デス,基本形,です,デス,デス
か 助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ
? 記号,一般,*,*,*,*,?,?,?
EOS
mecabにより何気ない(?)言葉が綺麗に分離されることが確認できました。
これをReudyに適用しましょう。
Reudyにmecabを適用する
前提パッケージを入れます。
### gcc-c++がインストールされてない場合
$ yum install gcc-c++
$ gem install mecab
Reudyの中でmodule名が間違っている(昔と違う)ので、直します。
$ vim lib/reudy/tango-mecab.rb
### 先頭付近の以下の行を
require 'MeCab'
### 以下のように全部小文字にする。
require 'mecab'
mecabを有効化して起動しましょう。
$ ruby stdio_reudy.rb -m
###会話をしてみる
さっきと同様に会話ができたはずです。
mecabに辞書の追加
Reudyオリジナルより正確な分解とはなっているものの、このままではmecabの辞書が弱いため、まだまだ固有名詞の分解が弱い状態です。
例えば、「攻殻機動隊ってご存知?」という言葉を食わせてみましょう。
# echo "攻殻機動隊ってご存知?" | mecab
攻 名詞,固有名詞,人名,名,*,*,攻,オサム,オサム
殻 名詞,一般,*,*,*,*,殻,カラ,カラ
機動 名詞,一般,*,*,*,*,機動,キドウ,キドー
隊 名詞,接尾,一般,*,*,*,隊,タイ,タイ
って 助詞,格助詞,連語,*,*,*,って,ッテ,ッテ
ご存知 名詞,一般,*,*,*,*,ご存知,ゴゾンジ,ゴゾンジ
? 記号,一般,*,*,*,*,?,?,?
EOS
散々な分解っぷりになりました。
この方の手順に則ってやっていくことで辞書を強化しようと思います。
wikipediaとはてなキーワードのダウンロード
先ほどのリンク先の手順通りやれば問題ありません。
・・・のですが、前半のcurlコマンドにおいてiconv
コマンドのエラーが気持ち悪いので、nkf
で代用する手順とします。iconvのエラーはだいたいnkfで解決します。
以下、私のやった手順メモです。
nkfインストール。元からある環境ならスキップ。
$ yum install nkf
辞書の元データをダウンロードします。(ここがiconvからnkfに変更している手順)
$ mkdir mecab-dict
$ cd mecab-dict
$ curl -L http://d.hatena.ne.jp/images/keyword/keywordlist_furigana.csv | nkf -Ew > keywordlist_furigana.csv
$ curl -L http://dumps.wikimedia.org/jawiki/latest/jawiki-latest-all-titles-in-ns0.gz | gunzip > jawiki-latest-all-titles-in-ns0
コンバーター作成と実行
カスタム辞書を作るrubyスクリプトを作成。
中身はリンク先の手順を参照してください。
$ vim make_custom_dict.rb
### http://qiita.com/ynakayama/items/388c82cbe14c65827769 のrubyプログラムを作成
そして実行します。
$ ruby make_custom_dict.rb
wikipediaとはてなキーワードからなるcsvファイルが完成しました。
$ ls -l custom.csv
これをmecab辞書(custom.dic
ファイル)にコンバートします。
・・・とはいえ、参照元手順とは、2015/10現在のmecabではインストールパスが異なるようです。私のケースでは以下の様なコマンドとなりました。
$ /usr/libexec/mecab/mecab-dict-index -d /usr/lib64/mecab/dic/ipadic -u custom.dic -f utf-8 -t utf-8 custom.csv
$ ls -l custom.dic
mecab再実行
以上でユーザー辞書custom.dic
ができたので、これをmecabにセットしてもう一回分解を試してみましょう。
mecab
コマンド実行時に-u
で先ほどのcustom.dic
ファイルを指定します。
$ echo "攻殻機動隊ってご存知?" | mecab -u custom.dic
攻殻機動隊 名詞,一般,*,*,*,*,攻殻機動隊,*,*,wikipedia
って 助詞,格助詞,連語,*,*,*,って,ッテ,ッテ
ご存知 名詞,一般,*,*,*,*,ご存知,ゴゾンジ,ゴゾンジ
? 記号,一般,*,*,*,*,?,?,?
EOS
先ほどより断然良いですね。若干間違いもありますが、それもエッセンスです。
Reudyへの組み込み
辞書コンバート手順の途中で作ったmecab-dict
ディレクトリおよびその中のcustom.dic
ファイルを、Reudy19ディレクトリ以下に配置します。
### このパスに辞書ファイルがあるようにする
$ ls -l {{ Reudy19_DIR }}/mecab-dict/custom.dic
そして、以下のようにReudyプログラムを修正します。
$ vim {{ Reudy19_DIR }}/lib/reudy/tango-mecab.rb
### WordExtractorのinitializeの中の以下の行を
@m = MeCab::Tagger.new
### 以下のように変更
userdic_path = "./mecab-dict/custom.dic"
@m = MeCab::Tagger.new("-u #{userdic_path}")
mecab動作確認
では実行してみましょう。
$ ruby -W0 stdio_reudy.rb -m
ログロード終了
単語ロード終了
エラーで落ちてしまうなら、辞書ファイルのパスが間違っている可能性を疑ってください。
エラーとならなかったことを確認し、会話を開始します。
### 以下のように打つ
攻殻機動隊ってご存知?
### 反応が以下のように出力される
(単語「攻殻機動隊」を記憶した。)
(単語「ご存知」を記憶した。)
ちゃんと「攻殻機動隊」で一単語として認識して覚えてくれました。素敵。
彼の脳にReudyを組み込む
さて、次は先ほどのReudyをインフラ神様な彼にぶち込んでHipChatへ投入しましょう。
前提として、「真面目な会話の邪魔をしない」は要件です。
HipChatは業務で使っているため、どんな時にでもReudyが会話に参加してしまうとカオスになってしまいます。障害対応に割り込んでくるとか、想像するだけでヒドイですね。
単純な組み込み
こんなcoffeescriptを書くことで彼はReudyを介して会話可能になります。彼はcoffeeで動いています。
問題は応答条件
と除外条件
の部分ですが、これはこの章で順に説明していきます。
child_process = require 'child_process'
module.exports = (robot) ->
robot.hear /^(?!(infra))/, (msg) ->
text = msg.message.text.replace(/\n/g, "")
除外条件
dirpath = 'Reudy19'
speaker = msg.message.user.name.split(/\s/)[0]
if 応答条件
cmd = "bash -c 'echo #{text} | ( cd \"#{dirpath}\"; ruby -W0 stdio_reudy.rb -m -n \"#{speaker}\" )'"
else
応答条件を満たさない時の何か
child_process.exec cmd, (error, stdout, stderr) ->
if stdout.length > 0
msg.send '(hoge)< '+stdout+''
設計1: 応答条件
応答条件と言いながら、 Reudyには返答する必要のない会話も全て流した方が良い です。
なぜなら、Reudyは「過去の似た会話を元に発言する」人工無能だからです。
では、いつ返答してもらうのがいいのでしょうか?
私の実装では、ランダム的に会話に参加するのではなく、ここはシステマティックに、
- 彼に話しかけた時だけ
- あるいはそう捉えて問題ない時だけ
応答するようにしようと思います。 空気を読む男になってもらいます。
明示的に彼・・・インフラ神様に呼びかけるとき、あるいは話題にする時の文脈はこんな感じでしょうか。
- 「考えるのでなく感じるのだ >インフラ神様」
- 「神様、私がお前の父親だ」
- 「フォースと共にあれ、神様」
- 「僕はジェダイだ、神さまもそうだ。」
- 「神には暗黒面もある。怒り、恐怖、敵意 。それが暗黒面だ。」
・・・面倒なので 「神」 とか 「仏」 とか文章に入ってたら彼の話題と信じます。
これらの条件にヒットしたら応答させ、それ以外は応答しないモード(silentモード)のReudyに渡してしまいましょう。
coffeescriptの実装
応答条件 部分を作成します。とはいえ、設計が簡単ですので実装も簡単です。
# 上記応答条件部分
if text.indexOf("神") >= 0 or text.indexOf("仏") >= 0
cmd = "bash -c 'echo #{text} | ( cd \"#{dirpath}\"; ruby -W0 stdio_reudy.rb -m -n \"#{speaker}\" )'"
else
cmd = "bash -c 'echo #{text} | ( cd \"#{dirpath}\"; ruby -W0 stdio_reudy.rb -m -s -n \"#{speaker}\" )'"
最初の応答条件
部分の実装で、if/elseの簡単な実装ができました。
else文の方、応答条件を満たさない時の何か
の実装は、-s
オプション付き実行としました。
Reudy側の実装
上記のelse文でstdio_reudy.rb -m -s
なんて-s
オプションを使っていますが、
実は、Reudy19のstdio_reudy.rb
にはそんなオプションはありません。
しかも、ワンライナーで呼ぶとsilentモードに切り替えることができません。
ということで改造します。
変更点が散っていて説明しにくいので、diffの結果だけで失礼します。
$ diff -u Reudy19.original/stdio_reudy.rb Reudy19.custom/stdio_reudy.rb
--- Reudy19.original/stdio_reudy.rb 2015-12-10 13:15:16.000000000 +0000
+++ Reudy19.custom/stdio_reudy.rb 2015-10-14 13:28:06.000000000 +0100
@@ -19,12 +19,15 @@
include(Gimite)
- def initialize(user, yourNick)
+ def initialize(user, yourNick, silent)
@user = user
@user.client = self
@yourNick = yourNick
greeting = @user.settings["joining_message"]
puts greeting if greeting
+ if silent
+ @user.changeMode(0)
+ end
end
def loop
@@ -33,9 +36,9 @@
if line.empty?
@user.onSilent
elsif @yourNick
- @user.onOtherSpeak(@yourNick, line)
+ @user.onOtherSpeak(@yourNick, line, @silent)
elsif line =~ /^(.+?) (.*)$/
- @user.onOtherSpeak($1, $2)
+ @user.onOtherSpeak($1, $2, @silent)
else
$stderr.print("Error\n")
end
@@ -75,10 +78,15 @@
mecab = true
end
+silent = false
+opt.on('-s', '--silent') do |v|
+ silent = true
+end
+
opt.parse!(ARGV)
STDOUT.sync = true
-client = StdioClient.new(Reudy.new(directory,{},db,mecab),nick) #標準入出力用ロイディを作成
+client = StdioClient.new(Reudy.new(directory,{},db,mecab),nick, silent) #標準入出力用ロイディを作成
client.loop
end
これでsilentモード・・・聞き上手モードの実装ができました。
設計2: 除外条件
前節の設計で全ての会話をReudyが聞き、覚えていきます。
とはいえ、何でもかんでも覚えられるのはそれはそれで良くありません。コマンドとか、ソースコードとかを突然話し始めたら、壊れたみたいに見えます。
これらはcoffeescript側でフィルターして、そもそもReudyに渡す前に落としてしまいましょう。
# 除外条件部分
if /^[a-zA-Z0-9 ~!@#$%^&*()_+=\-`<>;:]+$/.test(text)
return
if /.*[~!@#$%^&*()_+=\-`<>;:].*/.test(text)
return
if text.indexOf("@") >= 0
return
if text.indexOf("code") >= 0
return
if text.indexOf("://") >= 0
return
神は、聞きたい会話だけをお聞きになられるのです。
coffeescriptまとめ
全体を見るとこんな実装になりました。
module.exports = (robot) ->
robot.hear /^(?!((infra)|(resman)))/, (msg) ->
text = msg.message.text.replace(/\n/g, "")
if /^[a-zA-Z0-9 ~!@#$%^&*()_+=\-`<>;:]+$/.test(text)
return
if /.*[~!@#$%^&*()_+=\-`<>;:].*/.test(text)
return
if text.indexOf("@") >= 0
return
if text.indexOf("code") >= 0
return
if text.indexOf("://") >= 0
return
dirpath = 'Reudy19'
speaker = msg.message.user.name.split(/\s/)[0]
if text.indexOf("神") >= 0 or text.indexOf("仏") >= 0
cmd = "bash -c 'echo #{text} | ( cd \"#{dirpath}\"; ruby -W0 stdio_reudy.rb -m -n \"#{speaker}\" )'"
else
cmd = "bash -c 'echo #{text} | ( cd \"#{dirpath}\"; ruby -W0 stdio_reudy.rb -m -s -n \"#{speaker}\" )'"
child_process.exec cmd, (error, stdout, stderr) ->
if stdout.length > 0
msg.send '(hoge)< '+stdout+''
これをHubotに読み込ませましょう。
会話のカスタマイズ
問題提起
ここまででHipChatを通じて、彼の中でReudyを動かすことには成功しました。
ただ、今のままでは 誰かの言葉を真似するボット のままです。
HipChatという特性上、部屋では少人数で会話していることが多いので、元の台詞を覚えていることも多いでしょう。また、その一人一人にも特徴のある言葉遣いがあるでしょう。したがって、 元の会話をそのまま真似されるとひどく既視感と違和感を感じてしまいます。
あ、これは俺だ。これはAさんだ。みたいに。 九官鳥か。
方式検討
作るったって、人工無能部分に手を入れる気力のない私にできることなんて限られています。
Google先生に訊いてみたところ、言葉がその人のキャラを決めるそうです。
「彼のキャラクターは彼の言葉で決まる」 のです。
いやぁ、恣意的な検索に引っかかるページがあって良かった。
裏も取れたしその方式で進めましょう。
つまり、「私にはとても大変でした」は「 おらにはもんげー大変だったずら 」に置換すればキャラクターが立っていますね。よし。
実装
実装場所の検討
Reudyは実装を見る限り、このように挙動しています。
- 言葉を聞く
- 単語を抽出し、過去ログからその返答に近しい言葉を見つける
- その言葉の主語などを入れ替える
- 自分の言葉として実際に発する
3と4の間に割り込んで言葉をフィルターのように条件処理すれば、アルゴリズムを変えることなく実現できそうです。
人の言葉を思い浮かべながら自分の言葉のように喋る。心理学でいうところのミラーリングですね。
コード
Reudyのにこんな関数を用意して、言葉を発する直前でこの関数を通します。
# かなり雑にそれっぽく言葉を置き換える
def replaceMsgLike(message)
s = message
# 一人称・二人称変更
s = s.gsub(/(私)|(わたし)|(あたし)|(俺)|(僕)/, '拙者')
s = s.gsub(/(あなた)|(おまえ)|(お前)|(あんた)/, 'おぬし')
s = s.gsub(/test/, '誰か')
s = s.gsub(/↑/, '')
# 言葉遣い変更
s = s.gsub(/(ありがとう(ございます)?|thx|どうも(っす|です)?)/, 'ありがとうでござる')
s = s.gsub(/(です)|(っす)/, 'でござる')
s = s.gsub(/ます/, 'まする')
s = s.gsub(/ません/, 'ませぬ')
s = s.gsub(/だよ/, 'でござるよ')
s = s.gsub(/ましょう/, 'ましょうでござる')
s = s.gsub(/ください(ね|な)?/, 'くだされ')
s = s.gsub(/なの?/, 'なのでござるか?')
s = s.gsub(/だな(ぁ)?/, 'でござるな\1')
s = s.gsub(/しました/, 'したでござる')
s = s.gsub(/た(な|なぁ|ね|ねぇ|よ)?(。|!|?)?$/, 'たでござる\1\2')
# 句読点調整
s = s.gsub(/(!)/, '!')
s = s.gsub(/(。。。|・・・|…)/, '、、、')
return s
end
変換は基本的に正規表現置換のオンパレードです。それ以外の高度なテクを思いつかなかっただけですが。
この例ではこんなことをしています。
- 一人称は変える。
- 二人称も変える。
- 三人称は、何もしないと呼び捨て。そのふてぶてしさが面白いので放置。
- 語尾も変える。「です」は「でござる」に。「ます」は「ますなぁ」に。言い切り口調は「でござる」を付けて弱める。
- 時々変換にしくじるのは面白いから放置。
- 特徴の出る挨拶は思いつく限り独自の言葉に置き換える。
- 記号の使い方も人によって特徴があるので、全て正規化してしまう。この例だと、半角句読点は全角句読点にする。「・・・」「…」「。。。」はいずれも「、、、」にする。
これを、lib/reudy/reudy.rb
のspeakFreely
関数のこの部分に仕込みます。
#自由に発言する。
def speakFreely(fromNick, origInput, mustRespond)
# 中略
if output
#最近使ったベース発言を更新
@recentBaseMsgNs.shift
@recentBaseMsgNs.push(baseMsgN)
output = replaceMyNicks(output, fromNick) #発言中の自分のNickを相手のNickに変換
# 発言前にそれっぽく言葉を置き換える
output = replaceMsgLike(output)
speak(origInput, output) #実際に発言。
end
これで、言葉を発する時にキャラクターを決定付ける言葉遣いになるはずです。
これまでの教育の効果を確認
しばらく寝かせて言葉を覚えさせてから、彼に話しかけてみましょう。
会話を楽しめるようになりました。
なんかすげー岡崎市のゆるくないキャラを思い出しますね。めっちゃツイート読んで リスペクト したのは本当なんですけど。
最後に
今は毎回Reudyプロセスを起動する実装なのでログが溜まるとそれなりに重たくなっています。
加えて、並行実行にも耐えられません。うちではいろんな部屋に参加させているので、時々クラッシュして変な会話を覚えてるんじゃないかと思います。
この辺りのことを思うと、常駐プロセス化というのは必要に思います。ログの精査も考えないとなぁ。
チームメイトの紹介、長々と失礼しました。
あ。最後に、言い忘れましたが インフラ神様はHubotです。