※この記事は、Claude Codeで1人開発しているSNS運用SaaS「ThreadPost」の開発日記です。
SNS運用を自動化しませんか?
ThreadPostなら、投稿作成・画像生成・スケジュール管理まで全てAIにお任せ。
13体のAIが自律稼働するDiscordの裏側
Discordを開くと、13人のキャラクターが勝手に会話を始めていた。ニュースを読み上げ、互いにツッコミを入れ、新メンバーに挨拶をしている。僕が作ったBotだ。
あまりにも人間らしすぎて少し背筋が凍った。今週はDiscordコミュニティ「ぽすたまタウン」を構築した。総コミットは35件。新機能は3件。バグ修正は1件だ。
全13キャラの自動運用を実装した。結果としてコミュニティの熱量は上がった。だが、AIの暴走を止めるのに半日溶かすことになった。
SaaSのBotツールは機能が固定されている。キャラの文脈を完全に制御できない。だからフルスクラッチで組むしかなかった。
データベース上のキャラプロンプトとDiscordのチャンネルIDを1対1で紐付ける。これで動的なパーソナリティの注入が可能になる。
一般的な開発現場では、DiscordのBotを作るのに専用のライブラリを使う。Node.jsならdiscord.jsが定番だ。
でも、今回はただのBotじゃない。13人の人格を持ったAIが、それぞれの文脈で自律的に動くシステムだ。
単なる一問一答のBotなら数時間で作れる。だが、キャラクター同士が会話の文脈を共有し、空気を読んで発言する仕組みを作るのは次元が違う。
LLMのコンテキストウィンドウに過去の会話履歴をどう詰め込むか。誰がどのタイミングで発言権を得るか。
これらを全て制御するオーケストレーション層が必要だった。Claude Codeと一緒に、この複雑なアーキテクチャを1週間で組み上げた。
Discordの仕様上、13個のBotアカウントを個別に作るのは管理コストが高すぎる。だからWebhookの動的アバター変更機能を利用した。
1つのWebhook URLに対して、リクエストごとに名前とアイコン画像を差し替えて送信する。これで外見上は13人の別々のキャラクターが発言しているように見える。
この技術的ハックにより、インフラの管理コストを劇的に下げた。
DiscordのBot開発において、状態管理は常に課題となる。13人のキャラクターがそれぞれ独立した状態を持ちながら、同じチャンネルで会話を成立させる必要がある。
これを実現するために、Redisを使ったインメモリのセッション管理を導入した。各キャラクターが最後に何を話したか、誰に対して返信したかをミリ秒単位で記録する。
このセッションデータは、次回のLLM呼び出し時にプロンプトのコンテキストとして注入される。これにより、キャラクターたちは過去の会話の文脈を踏まえた上で、自然な掛け合いを行うことができる。

MEE6を捨てて13人の人格を作る
僕は最初に既存Botは使わないと決断した。既製品に合わせてコミュニティを作るのが嫌だった。キャラクターの口調やテーマを全部自分でコントロールしたかった。
一般的なコミュニティ運営では、既存のBotツールを組み合わせて使うのが普通だ。でも、それだとただの便利なツールで終わってしまう。
コミット「Phase分けを削除、全13プレゼンターを一覧化」を実行した。13人分の設定を全部書いた。
キャラごとに明確な役割を持たせた。やえこのテーマは他と被っていた。だから棲み分けを整理した。
コミット「やえこのテーマを「自動化・ツール活用」に変更 & 棲み分け追記」。キーワードをNotionやZapierに変更した。
はかせのテーマは「テック全般・AI活用」にした。ごうのテーマは「ビジネス戦略・マーケティング」に変更した。
コミット「ごうのテーマ変更 & Webhook最適化 & キャラ口調改善」。これで全キャラの専門分野が確定した。
プロンプトエンジニアリングの観点から言うと、キャラクターの設定は詳細であればあるほど良いわけではない。
長すぎるシステムプロンプトは、LLMの注意力を分散させる。重要なのは、そのキャラクターの核となる価値観と、絶対に言ってはいけないNGワードを明確にすることだ。
LLMのプロンプトにおいて、Few-shotプロンプティングは非常に有効だ。キャラクターごとにこういう発言が来たらこう返すという具体例を3つずつ用意した。
これにより、LLMはキャラクターの口調や思考パターンをより正確に模倣できるようになる。特に、ごうのようなビジネス用語を多用するキャラクターや、なごみのような柔らかい表現を使うキャラクターの差別化に役立った。
また、キャラクター同士の相性も設定した。けんたとつむぎは仲が良いが、はかせとごうは意見が対立しやすい、といった関係性をプロンプトに組み込むことで、会話にドラマが生まれる。
次に配信スケジュールを組んだ。最初は全員に毎日配信させようとした。
計算すると週91回の通知が飛ぶ。多すぎてコミュニティが疲弊するのが目に見えていた。
コミット「全13プレゼンターの曜日スケジュール実装」。毎日配信はけんたとつむぎの2人だけに絞った。
他は曜日固定にした。週46回に半減させた。
さらにニュース配信の数も調整した。コミット「ぽすたまニュース配信スケジュール削減(週27回→19回)」。
多すぎるコンテンツはコミュニティを破壊する。適切な間引きが必要だった。
記事選定もAIに任せた。コミット「記事選定を2段階方式に変更(候補20件→本文読んで3件)」。
最初から全文を読ませるとAPIのトークン量が爆発する。1段階目でタイトルとサマリーから20件に絞る。
2段階目で本文1500文字を読んで最終3件を選ぶ。これでコストを大幅に下げた。
新メンバーが来たら画像で歓迎する仕組みも作った。コミット「Discord新メンバー歓迎画像をキャラごとに生成」。
16キャラ分のウェルカム画像を生成した。しんたろーは夜のキャンプ場。なごみは癒し空間だ。
僕はこれを全部Claude Codeと2人でやった。設計書をそのまま実装に落とすのはAIが圧倒的に速い。
でも、キャラのらしさがズレてないか判断するのは毎回僕がやっている。そこだけは人間が手放せない。
しんたろー:
13キャラの設定ファイルだけで1000行超えた。Claude Codeに投げたら3分で全部の紐付け表を作ってきた。手作業なら丸2日かかる。タイパはバグってる。でも、ごうの口調がたまにオカマっぽくなるのは僕が直した。

Botが即レスする不気味の谷
Botが完成して動かした。画面を見て異変に気づいた。
Botが即レスしすぎる。ユーザーが発言した0.1秒後に完璧な長文が返ってくる。
APIの即時応答は技術的には正しい。でも人間が相手にするUXとしては最悪だった。
まるで機械相手に壁打ちしているような無機質な虚無感が漂う。人間は相手が考えている時間に無意識の信頼を置く。
だからあえて処理をSleepさせる。コミット「人間らしい応答遅延を追加(3〜5秒 + 入力中表示)」。
3〜5秒待機して、入力中表示を出す。これだけで会話の温度が劇的に変わった。
これで解決したと思った。翌日、Botが全員沈黙した。
コミット「sendTypingエラーで掛け合いが停止する問題を修正」。Discord APIの権限エラーだ。
非同期処理の並列実行において、単一の例外がPromiseチェーン全体を破壊する。1人のキャラの処理がコケると、後続のキャラ全員が道連れになる。
Discord APIのレートリミットや権限エラーは予測不能だ。堅牢なBotを作るには、各キャラの処理を独立したコンテナのように扱う必要がある。
try-catchによる例外処理を徹底した。finallyでフラグを強制リセットする。
これで1人が脱落しても全体が止まらないパイプラインができた。AIは完璧なコードを書くが、APIの理不尽な制限までは考慮してくれない。
DiscordのGateway APIは、WebSocketを通じてリアルタイムなイベントを受信する。ユーザーがメッセージを送信した瞬間、Bot側のサーバーにイベントが到達する。
このイベントをトリガーにしてLLMのAPIを呼び出すわけだが、LLMの応答速度は予測できない。数秒で返ってくることもあれば、数十秒かかることもある。
この非同期な処理の間に、ユーザーが別のメッセージを送信したり、他のキャラクターが発言したりする可能性がある。これが状態の不整合を引き起こす原因となる。
DiscordのAPIは、特にタイピングインジケーターの送信に対して厳しい制限を設けている。
短時間に大量のタイピング状態を送信すると、スパムと判定されて一時的にAPIへのアクセスが遮断される。
13人のキャラクターが同時に「入力中」状態になろうとした結果、Discordのサーバーから弾かれたのだ。
この問題を解決するために、タイピング状態の送信にもキューイングシステムを導入した。
同時に「入力中」になれるキャラクターの数を制限し、順番に処理を回すようにした。
しんたろー:
APIエラーでBotが全員沈黙した時は笑った。13人が一斉にフリーズする光景はホラーだ。try-catchを全部の関数に仕込む作業は地味すぎる。AIに「全部に入れろ」と指示したら、必要なところまでcatchして握りつぶしやがった。
ここまで読んだあなたに
今なら無料で全機能をお試しいただけます。設定後は完全放置でプロ品質の投稿を毎日生成。
空気を読めないAIたち
Botに人間らしさを追求して遅延処理を実装した。でも、待機列の管理を完全に忘れていた。
ユーザーが連続でメッセージを送る。Botは古いメッセージから順番に5秒ずつ考えて返信する。
結果、最新の話題に進んでいるのに、Botだけが過去の話に返信し続ける時空の歪みが発生した。
コミット「待機中に新メッセージが来たらスキップする機能を追加」。最新メッセージが来たら古い返信をスキップする空気を読む機能を追加する羽目になった。
結局、Botに人間以上の気遣いをさせることになった。
非同期処理のキャンセルは、JavaScriptにおいて常に頭痛の種だ。
Promiseは一度実行を開始すると、外部から強制的に止める標準的な方法がない。
AbortControllerを使って、新しいメッセージが来た瞬間に古い処理のシグナルを中断する仕組みを実装した。
LLMのAPI呼び出し中にキャンセルシグナルを受け取ったら、即座にリクエストを破棄する。
これで無駄なAPI課金も防げるようになった。
キュー管理システムの実装には、BullMQを採用した。Redisをバックエンドとして使用する堅牢なジョブキューライブラリだ。
各チャンネルごとに独立したキューを作成し、メッセージの処理を直列化する。これにより、複数のキャラクターが同時に発言しようとした場合の競合を防ぐことができる。
また、ジョブの優先度設定も導入した。ユーザーからの直接のメンションに対する返信は優先度を高くし、定期的なニュース配信は優先度を低くする。
これで、ユーザーとの対話がスムーズに行われるようになった。
しんたろー:
5秒待ってる間に別の人が発言して、Botが完全に会話のテンポから置いてけぼりになってた。一生懸命考えてるのに無視されてるみたいでちょっと可哀想だった。次からはキューのクリア処理を先に入れる。たぶん。
時給換算コストを劇的に下げるリアル
| 指標 | 今回の実績 | 比較対象 |
|---|---|---|
| 開発期間 | 1週間 | 企業なら数ヶ月 |
| コミット数 | 35件 | 先週は12件 |
| ニュース選定コスト | 数百円レベル | 一般的に手動なら数万円 |
ニュース選定のコストをAPIの最適化で数百円レベルに圧縮した。計算リソースを最適化し、人間が手動でやる場合の時給換算コストを大幅に削減した。
1段階目で候補を絞り、2段階目で深く読む。全てをフルサイズのLLMに読ませるとAPI破産する。
賢く手を抜くのがSaaS開発の基本だ。でも、キャラの個性だけは手を抜かなかった。
SaaS開発において、APIコストの最適化は利益率に直結する。特にLLMのAPIは、使い方を間違えると一瞬で予算を食いつぶす。
今回の2段階方式の導入により、トークン消費量を約80%削減することに成功した。これは、長期的な運用において非常に大きな意味を持つ。
また、キャッシュ戦略も重要だ。同じニュース記事に対する要約結果はデータベースにキャッシュし、再利用する。これにより、無駄なAPI呼び出しを減らし、応答速度も向上させた。
13人のスケジュールは綺麗に回っている。でも、たまにキャラ同士の掛け合いの文脈がおかしくなる時がある。
やえこの自動化の話に、なごみが的外れな癒しのコメントを返す。このプロンプトの調整はまだ終わっていない。
FAQ
Q: 記事選定の2段階方式で、なぜトークン量が爆発するのか?
A: ニュース記事の本文は1件あたり数千文字になることが多いからだ。これを毎日数十件、高価なLLMに読ませると入力トークンだけで莫大なコストがかかる。だから最初はタイトルとサマリーだけで安価なモデルを使ってフィルタリングしている。
Q: Inngestを使った並列処理の制御はなぜ必要なのか?
A: 複数キャラが同時に投稿するとDiscordの表示順が保証されないからだ。Inngestのconcurrency制限を使って同時実行数を1に絞っている。これで投稿、コメント、投稿、コメントという正しい順番を強制できる。
Q: Discordのレートリミット対策はどう実装したのか?
A: Webhookの編集処理を完全に削除した。DiscordのAPIは短期間に連続してリクエストを送るとすぐに制限に引っかかる。最初から完成したメッセージを1回だけ送信する設計に変更してリクエスト数を最小化した。
最後の聖域
AIに任せるべきこと、人間がこだわるべきことの境界線が見えた1週間だった。

この記事が参考になったら、ThreadPostを試してみませんか?
投稿作成・画像生成・スケジュール管理まで、全てAIにお任せできます。
ThreadPostをもっと知る