移行メモ
CUIで作ったチャットボットをMattermostに接続したものにWeb UIをつけたい
ごちゃごちゃしてて混乱するので書きながら進める
既存のリポジトリが色々なしがらみが絡まってごちゃごちゃになってるので、一旦新しいリポジトリに切り離そう
$ git init keicho-server
$ heroku create keicho
Herokuに置くのはserverなの自明だし
サーバ上で最小限のコードが動くようになった
ここからが面倒
過去のコードの理解と整理をやる
https://gyazo.com/b9ae8e5954c15ae5acd949fb026235af
CUI時代には情報をオンメモリに持っていた
MattermostとOutgoing Webhooksで繋ぐ
そのためにサーバに持っていこうとした段階で情報をFirebaseに入れるようになった
serverからはchat.connect_slack(username, text)を呼ぶ
当時はパッケージにしてなかったが、今この機会にやるべきか
environment.pyがEnvオブジェクトの定義とFirebaseでの永続化を一手に引き受けている
あー、ここでアクセス権のためのクレデンシャルをリポジトリに入れると公開しにくくなるのだな…
で、「秘密部分だけリポジトリわけたとして、どうやってHeroku内でくっつけるのか、そのくっつけるための情報がまた秘密情報になるのでは」と思って全体非公開にしたんだな
デフォルト非公開のまま、公開できる部分だけを公開リポジトリに切り出せば良い
ここまでのインポートはうまく行った
Firebase接続のテスト用のURLを作って叩いてみる
environment.pyから state.pyを呼んでいる
INITIAL_STATEを参照
あんまりここを密結合にしたくないでstate_constant.py / state_action_map.py に分けた
永続化周りは動くようになった
次
code:python
def connect_slack(name, text):
"Top-level interface for chat server"
env = environment.load(name)
if text0 in "!(>#~@": # (1) environment.save(name, env)
return ""
process_command(env, text) # (2)
res = make_response(env) # (3)
if res:
environment.save(name, env)
return res
(1)が何のために必要なのかわからない…
process_command
まず重要なのは「よくない質問」の時のフィードバック
NGKWならキーワードの選択自体が悪い
NGはキーワード自体は悪くないが質問との組み合わせが悪くて回答できない
人間がこの二つをちゃんと区別して使い分けれるかは微妙だと思うので後者でも2〜3回使ったらキーワードが悪いと判断した方が良いかも
リセットコマンドが送られた時に環境のリセットを行なっている
これはチャットの場合「セッション」などがないのでリセットのタイミングがないから
「話そう」などで通常フローが開始される
これはHerokuがスリープしてるのを起こす目的もある
Web UIの場合、ページにアクセスした時点で入力可能であるべきで、バックグラウンドで起こすべき
「まず聞いて」の相槌モードと「おしまい」による通常モードへの復帰
「別にない」みたいな発言でスコアを落とす実験的コード
NGコマンドの処理などと一緒にすべき感
いったんコメントアウト、後で検討
後で読むための読みやすい形でのログの出力をしているが、CUI時代のコードなのでローカルファイルに書いている
Slack時代にログ表示機能をつけた気がするのでなくて良さそう
環境と回答長さのデータの保存
これは「回答がたくさん帰ってくる質問が良い質問」という発想
envの更新
後述
質問キーワード対のデータの更新
学習用のデータをローカルファイルに吐いていた、Slack時代にコメントアウトしてた
うーん、なるほど
https://gyazo.com/f8c2d6a5951c8eb8bfff9cc08807b89d
process_command が、その曖昧ななんでも入りそうな関数名のせいで肥大してて、さらにそれがupdate_envなんていうなんでも入りそうな関数を呼んでる
逆に make_responseは2行(しかも片方assert)
これは分割境界が適切でないな、建て増しを繰り返して迷宮化してる
いったんくっつけてから適切に分割しよう
update_envの中でさらにstate_transitionを呼んでる
ここは状態遷移を主にやってる
状態遷移をログに書き出してるけど、やっぱりローカルファイルシステムのみ
そもそもオンメモリの情報をFirebaseで永続化する形に書き換えたタイミングで、ファイルに書き出してるログも別途Firebaseに置き場所を作るべきだったのでは?(後で検討)
整理
process_command の、本当に文字列をコマンドとして処理する部分はreturnで脱出したいので関数に分かれているのが自然
state_transitionも同様
https://gyazo.com/00bcda856bb76dab5bbd5af342a2e3e0
update_env_with_inputの中でキーフレーズ抽出をしてたの、奥まりすぎ
キーフレーズ抽出
今はいったん過去のコードのままにするけど、近々更新予定なので、切り出しておく
キーフレーズ抽出は他のプロジェクトでも使うのでパッケージにしておこう
NGKWでのキーフレーズの削除
フレーズ指定ありとなしの両方が実装されてるけど、指定して使うことはない
ここ、単純に記憶から削除してるけど、それだと「削除された」という情報が残らない
再度追加される可能性がある
セッション中はブラックリストに入れるなどして追加されないようにし、長期的にはキーフレーズ抽出の部分でブラックリストで弾くべき(後で検討)
Mattermostに繋いだことで使いやすくなった反面、ログを諸々無効化したことで改善がしにくくなったのだなぁ、反省
次はアクション周り
ステートごとにアクションがあり、envを引数にして実行される、という設計
state_action_map[env.state](env)
しかし実際のところ少数の例外を除いて「引数で受け取った質問のリストから選んで質問する」というアクション
選ぶところはランダムかと思ったが違った
すべてのキーフレーズ×質問についてスコアを計算
既出のものや、NGリストに入ってるものは除外
このNGリストはどこから来るんだっけ…
envの永続化対象には入ってるが、NGリストのファイル書き出しを止めた際にリストへの追加も止まってるから常に空だな
基本2質問に下駄を履かせてる
直前にした質問に使ったキーワードに正の補正あり
ここで自然な質問かどうかの調整が入ってるが、有効に機能してるかが未検証
未検証だと思うけど検証したんだっけ?という気持ち
一旦外して後で検討
そしてスコア最大のものを選択
使用済みに入れる
対称形の質問に対してはキーワードを交換したものに関しても使用済みに入れる
質問
質問の種類ごとに「1つキーフレーズを選ぶ」「2個選ぶ」などが実装されている
スコアに応じたサンプリングもある
あれ?さっき全部のキーフレーズ質問対についてスコアを計算して最大のものを選んだんじゃなかったっけ?
そうだ、これ呼ばれてない
例外であるChainQuestionは「直前のキーワードを使い続ける」なのだが…
これちゃんとテストされてない気がするな
たぶんたまたま「前回最高スコアだったキーワードは、次も最高である確率が高い」ってだけで、逆転しうる
「直前のキーワードを使い続ける質問」ってフラグを立てて、スコア計算の時に特別扱いするべき
使われてないコードを取り除く
ローカルのサーバで動いた
Herokuでも動いた
「後で検討」
ランダムな扱いをしている部分、シードを固定する方法を用意して回帰テストできるようにしてはどうか
そもそもランダム性は除去済み
細かい粒度のテストがないのは良くない
A: ファイルに書き出してるログも別途Firebaseに置き場所を作るべき
Yes
どのようなログを取るべきか
現状「人間が読めるログ」自体もリセットされる領域に置かれてるがこれはおかしい
最低限、人間が読むためのログが保存されるべき
加えて、ログを見て「イマイチな返答」が起きてた時に、その時の内部データを後から確認できるといい
これは気をつけないと容量が大きくなりそう…
B: 長期的にはキーフレーズ抽出の部分でブラックリストで弾くべき
これを記録しておく場所が今はない
まずAする
C: 「別にない」みたいな発言でスコアを落とす実験的コード
NGコマンドの処理などと一緒にすべき感
Bしてからまとめる