fs495's junk box

第2世代botエンジンのアーキテクチャについて

以下は自宅サーバでフランちゃんbotを動かしていたときに公開していた設計メモです。

今読むと、当時のREST APIに高度に最適化していることが分かります。 TLの並行フェッチなんてやってたのは世界中に何人いたかわかりませんが、キャラクターもののbotとしては相当凝った部類だったと思います。 ただ、その分、現在のUser Streaming APIに適合できなくなってたのは否定できないところです。

また、情報はテキストファイル(YAMLファイル含む)で保存していましたが、これはこれで相当処理能力を使います。 最初のうちは良かったのですが、フォロワーが増えてTLがとんでもないスピードで流れるようになると、処理が追いつかなくなることが多くなってしまいました。 スパマーを排除することでなんとか挽回を図っていましたが、それはそれでいろいろと軋轢が生じやすく、Twitter社にアカウントを凍結されたり、だんだん仕事が忙しくなるにつれてbotの運用が難しくなっていった面がありました(実は2010〜2013年頃は部署を移った直後で、仕事も少なく、ものすごく時間があったんですね…w)。

まあそれはともかく、古臭さ(わずか3年程度前のことではありますがw)の再確認もしつつ、第3世代のロンチに向けてつらつら検討を進めています。遅くても495年後くらいにはリリースしたい…


第2世代botエンジンのアーキテクチャについて

botエンジン概要

多くのbotは、フェッチ-応答生成-ポストのサイクルを繰り返すタイプが多いと思います。 このタイプは単純で作りやすくはあるものの、フェッチとポストという時間のかかる処理をシリアルに実行するため、応答性が悪いという欠点があります。 ついったーの負荷が高い場合や、動作が不安定な場合はかなり致命的です。 さらに、ポスト規制されるとまったく応答できなくなる欠点もあります。

そこで、botを並行動作させ、分散ポストのための配送というステップを加えた フェッチ-応答生成-配送-ポストのサイクルを回すようにし、 フェッチおよびポストを行なうプロセスを複数独立に設けています。

これはCPUにおけるマルチスレッディング技術に対応していると考えられます。 メモリレイテンシを隠蔽するためにスレッド数>実行資源数とするように、 API発行のレイテンシを隠蔽するためにbot数>応答生成処理数としています。

応答生成処理は単一のプロセスが担当します。 これは、botどうしのかけあいを行うときに、 複数プロセスでやると非常に構成が複雑になってしまうからです。 例えば複数botを並行動作させると、 設定や状態の一貫性を保持するためにファイルのロック手順が煩雑になってしまい、 しかもロックコンテンションまで発生してしまいます。 ファイルロック自体は新アーキテクチャでも使っていますが、 粒度の小さいロックにとどまっています。

プロセス構成の説明

inoutプロセス

inoutプロセスはフェッチとポストを担当します。 このプロセスはbotごとに用意されおり、並行して複数のAPIを独立に発行できるようにしています。 例えば、単体のbotでのフェッチ間隔は20秒前後の設計にしていますが、実際には並列にフェッチするため全体としては3〜4秒間隔でフェッチしているのと同じ動作になります。

フェッチしたついーとはひとつひとつYAML形式のファイルで保存します。 ひとつのツイートはbotの数だけフェッチされることになりますが、 ファイルはStatusのIDごとに1つだけ生成されます。 ファイルが生成・更新されると次に説明するresgenプロセスに応答生成要求を出します。

resgenプロセス

resgenプロセスは、botのポスト内容を生成します。 ポスト内容としては、ユーザのついーとへの応答と、独り言とに大別されます。 独り言を細かく分類すると、ランダムついーと、時刻指定ポスト(なる4やよるほ)、botどうしの会話(独り言ではありませんがw)といったものがあります。

resgenプロセスは全botの応答生成を担当しています。 また、どのbotが受け取ったかは考慮しないため、 例えば「フランちゃんbot宛てのついーと」を他のbotが先にフェッチした場合は、 その時点で「フランちゃんbotからの返事」を生成します。

生成された応答はやはりYAML形式のファイルに保存し、配送要求を出します。

deliverプロセス

deliverプロセスは配送を処理します。 配送要求を受け取ると、応答を取り出し、 inoutプロセスに対してポスト要求をかけます。 ただし、規制状態のbotを担当するinoutプロセスにはポスト要求しません。 規制などの一時的なポスト失敗に対しては一定時間のウェイト後にポストの再要求を行います。

monitorプロセス

さらに、これらのプロセス群を監視するmonitorプロセスがあります。 このプロセスは他のプロセスを生成し、その実行を見守ります。 不意にどれかのプロセスが死んだ場合は他のプロセスも殺してシステム全体を完全に停止させます。 また、このプロセスはプロセス間の要求メッセージの媒介も行います。

フランbotの特徴について

実行形態

多くのbotはCGIとして実行されていますが、 フランちゃんbotはプロセスとして常駐しています。 第1世代は一応CGIから呼び出せるようにも作っていましたが、 第2世代は性能優先で常駐型の実行しかサポートしません (もっとも、応答生成処理を切り離してCGIなどから実行させることは 技術的には可能でしょう)。 実際のところ、CGIとして動かしているのは レンタルサーバで動かすという都合のためであって、 botとしてはCGI環境である必然性はまったくありません。 なお、動作しているのはUbuntuのServer Editionで、 4コアのAtomでほそぼそと動いています。プログラミング言語はRuby 1.8です。

好感度システム

好感度システムは@dazekoの初代システムをお手本にしています。 しかし開発当時の@dazekoの好感度は3段階しかなく、 レベルが上がると一気にデレるという若干不自然な点がありました。

このため、好感度自体は100段階の刻みで管理し、 メッセージ生成の段階で閾値を柔軟に適用する方式を採用しました。 これにより、「なかなかデレない応答」や「比較的デレやすい応答」を 作り出すことができます。 後に@dazekoもこの方式に変更しているようですし、 @Marisa_Bot_に至っては浮動少数点で好感度を管理するようになっています。 ### ツンデレ度 原作のフランちゃんは幼くてかわいいのにドSの破壊魔であるという 二面性を持ったキャラであり、 これを単一の軸(好き⇔嫌い)で表現するのは非常に困難です。 このため若干安易なところではありますが、 ツン⇔デレの性向をパラメータとして持つようにしました。 ただ、作る側としては好感度とツンデレ度の組み合わせで 応答パターン数が爆発するという問題があり、頭の痛いところですw ### ユーザによるパラメータ設定 ここまでの説明だとギャルゲを目指しているかのように思われるかもしれませんが、 ユーザが自分でパラメータを指定できるようにしました。理由はいくつかあります。 第一の理由は作業ゲー化を避けるようにするためです。 好感度システムを搭載してしまうと、好感度を上げるために 機械的に「かわいい」と繰り返しリプするだけのユーザが出てきてしまいます。 原作キャラの劣化コピーでしかないbotのために ユーザの手間と時間をムダにするのはあまりに忍びないと私は考えます。 また、なんの工夫もなく「かわいい」を繰り返させられるユーザというのは それこそできの悪いbotみたいで、どっちがbotだかわかったものではありません。 ユーザはbotと遊びたいだけなのに、 そんな苦行を強いる権利がbot作者にはあるとでもいうのでしょうか? 第二の理由は、キャラはbot作者のものではないという考えからです。 キャラbotには当然オリジナルのキャラがいて、それはオリジナルの作者のものです。 オリジナルの作者の前では、botの作者もbotのユーザも対等の立場です。 しかし実際には、bot作者は好感度パラメータを設けることで そのbotの感情表出をコントロールすることができます。 つまり、キャラの代理たるbotとユーザとの仲を bot作者は一方的にコントロールすることが可能なのです。 これってかなり傲慢ではないでしょうか。 ユーザはbotと仲良くなりたいだけなのに、 それを妨害する権利がbot作者にはあるとでもいうのでしょうか? 第三の理由は原作設定です。 フランちゃんは紅魔異変で初めて人間と出会い、 人間という生き物に興味を持ち始めています。 ギャルゲで言えば主人公に最初から好意を抱いている状態です。 また、500年近く地下に閉じこもっていて、人付き合いというもの自体が初めてです。 このため、プレイヤー側の態度でいかようにも関係性を決定できる状態であると 想定してます。 もっとも、ギャルゲ的な「攻略」性を求める人もいて、 そういう人には好感度が自由に設定できるのは 物足りないだろうなあということは理解しています。 ### パラメータのランダム化 反応のマンネリ化を防ぐため、好感度は乱数によって、 ツンデレ度は月相によってそれぞれランダム性を加えています。 ある意味、精神不安定という原作設定をこういう形で実現しているとも言えます。 余談ですが、月相でゲーム内パラメータが変動するというフィーチャーは NetHackというゲームから採用しています。 決してPMSではありません… フランちゃんはお赤飯前だと信じてるw ### 複数垢の使用 負荷分散のために複数垢を使うようにしたのは 東方関係のbotの中では最初だったかも…(自信ない)。 もっとも、第1世代のbotエンジンでは非常に稚拙な実装であり、 単に並列実行しているにすぎませんでした。 機構としては@9_Cirnoなどのほうがはるかに洗練されています。 第2世代ではようやく本格的な負荷分散ができるようになり、 複数垢を活用した機能(フランちゃん会議=エヴァのMAGIシステムのパクリ)も 搭載できるようになりました。 ### 非公式RT 非公式RTをここまで多用するbotもちょっと珍しいかもしれません (暴れん坊将軍botくらいかな?w)。 bot自身が晒し目的のために非公式RTしますし、 他の人がRT(公式・非公式)した場合も 解析して引用部分に反応しないようにしています (RTは一切無視するというbotエンジンも多いようですが…)。 ### botどうしのやりとり botどうしのやりとりはループが発生しやすいという誤解が もともと広まっていたのですが、実はループが発生する条件は結構厳しいのです。 これを明らかにして作者が別のbot同士を絡ませる流れを作ったのは 自分の周辺ではフランちゃんbotが最初でした (同じ作者のbotどうしのやりとりは@reimuと@dazekoで先に実装されていました)。 いままでに@dazeko, @Komeiji_Koishi, @remilia_bot, @SakuyasanBOT, @teruyo_botとのやりとりを実現してきています。 しかし、相手botとまったくかみ合ってないやり取りをさせる人が出てきたこと、 承諾を得ないまま自管理botとの連携を嫌う人がでてきたこと、 ポスト規制でろくに会話が発生しないこと…などの反省点もでてきました。 このため、第2世代botでは先祖がえりして、 主要なbot同士のやりとりは自前のbotで完結するようにしようとしています。 ### ユーザ自身のセリフを取り込む ユーザの発言にキーワードが含まれていた場合に定型文を返すタイプのbotは bot作成サービスのおかげで非常に簡単に作れるようになりました。 しかし、ユーザとしては「自分のついーとに反応したという実感」があったほうが より面白みを感じます。 このため、ユーザが指定した語句をなるべく多く使うようにしていて、 呼び名・爆発(〜爆発しろ!のアレ)・吹き出し(\こういうやつ/)・食べ物系などで ここらへんが反映されています。 もちろん変な反応が入りやすいところですが、分かってて弾いていません。 キーワードで規制することは技術的には可能ですが、 どうせいたちごっこになるのがオチですし、 そもそも規制することになんのメリットがあるのでしょうか? だとしたら、よりフリーダムに楽しんでもらえるように作るのが良いと考えています。 ### わけのわからない実験的機能 4つの数字から10を作るとか、ついったー鯖の応答速度調べるとか、 ツイート文字列を16進ダンプするとか、わけのわからない機能も多かったですw ### 浮気監視 調査したことはないですけど、 第三者へのリプ内容を監視するbotはちょっと珍しいような気がします… ### セクハラ反応 趣味に走りまくって実装してしまいました…w 同時期に開発された白蓮botも非常に反応がエロいですが、これと好対照ですね。 最近は@RumiaBotに注目していますw ### 診断メーカー応答 botがついったらーであるかのように振る舞うのが個人的な目標だったりします。 なので、多くのついったらーが遊んでいる診断メーカーを bot自身が遊べれば面白いなあと思っていたのですが、 幸い簡単に実装できました。 搭載当初は中の人が手動でやってると思う人が多くて、 ひそかにほくそ笑んでいたりしましたw 最近は結構多くのbotがこの機能を搭載しているようです。 また、ユーザが特定の診断(△△RTしたら××する系)をしたら 反応するようにもしています。なかなか被害者が多くて見てる方は愉快ですw ### ソース公開 ひょっとしたらbotを作ろうと思っている人に 参考になるかもしれないと思ってソースを公開しています。 http://scarlets.dyndns.info/3rd-gen/ しかし、「ソースを公開するのが恥ずかしい」という繊細な人もいるんですよねえ… 理解できん… だったらbot自体公開するのやめればいi(ry ### 応答処理はハードコーディング キーワードと応答文は別のデータファイルにするのが普通だしスマートなんですが、 細かい小細工を使いたいのでそうはしてません。 ドメイン固有言語みたいなのを作って汎用性を持たせてもいいんですが、 そもそも一番汎用性の高いDSLってプログラミング言語自体なんですよね… ### データファイルはYAML CSVだと原始的すぎ、XMLはパーザ書くの面倒、DBMSは柔軟性がなくて面倒… ということでYAMLを使ってます。 ユーザごとの設定値保存のほかにもbotの設定設定ファイルもYAMLにしています。 テキストエディタで値を編集できる点、 保存データのメンバを簡単に追加できるのが便利ですが、 日本語が入った文字列がエンコードされて保存される点、 処理に時間かかる点が欠点です。 ### 応答速度 開発当初、1時間あたり150回使用できるAPIを均等割りして 25〜30秒ごとにリプライを返していました。 特に深く考えずにこの間隔を設定していたのですが、 返答に1〜2分かかるのが一般的だった中では、 かなり応答速度が速いと感じる方が多かったようです。 しかし、当時のサーバは無印の玄箱で動いていたので、複数bot制に移行したときに サーバの処理能力不足で応答が5〜20分近く遅れるようになっていた時期がありました。 一度処理が遅れると未処理のついーとが溜まり、 その多量の未処理ついーとを取得するためにTwitter側の処理時間が増え、 さらに処理が遅れてしまうという悪循環が発生していました。 これはサーバの買い替えと、botの並行実行によって解消しました。 また、第2世代のbotエンジンではついーとも分散して取得しています。 いずれかのbotが新しいツイートを発見した時点で応答生成処理に入りますので、 botが個別に応答していたときよりも反応が早くなります。 さらに、APIの使用回数制限は現在1時間あたり350回までに増えており、 まだフォロワーが少ないこともあって相当応答速度は速くなっているかと思います。 ### 挨拶応答 botのポスト規制を回避するいちばん手っ取り早い方法は挨拶応答を間引くことです。 しかし、挨拶を返してもらえる人がいる中で自分だけbotに無視されるというのも寂しすぎますし、botから返事をもらうまでに何度もリプするというのはなんだか変な話です。 挨拶応答はbotの大事な仕事ですし、間引くくらいなら規制されてしまおうという方針でやっています。 ### 時刻指定ポスト よるほ対応のためですw cronからCGIを駆動するタイプのbotで同じようなことをしようとすると、 cronの時間精度にも大きく依存することになり、 WebサーバおよびCGIの応答オーバーヘッドも考慮する必要があります。 一方、このbotエンジンで時刻指定のポスト要求が発生すると、 いったん通常のフェッチ動作・ポスト動作を一時停止し、その時刻までフリーズします。 いわゆるよるほ待機状態ですねw 残念ながらついったー側でも遅延が発生してしまうので、 百発百中でよるほできるわけではありません。 ### ポスト規制の自己申告 ポスト規制されたときにアイコン画像を変更するbotはありましたが、 復帰したときに報告するbotは自分の知ってる範囲ではありませんでした。 ポスト規制状態は割と積極的に利用しており、 復帰時にbotどうしがおかえりを言うようになってたり、 第2世代botでは負荷分散に利用しています。 ### timelineとmentionの使い分け いわゆるTL応答を行うため、応答を生成するかの判定はtimelineからの取得データに対して行っています。 mentionから取得した入力は、フォロー申請かどうかの判定にのみ使っています。 これはフォロー外からのリプにはなるべく応答しないようにするためでもあります。