2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

llm人狼

Last updated at Posted at 2025-12-08

Local LLMで人狼ゲームを実装する──性能限界とRAGでどこまで戦えるか

概要

Ollama 上の Qwen2.5 72B を使って、ユーザー1人 vs LLM4人のワンナイト人狼ゲームを実装しました。

しかし、Local LLMを「素のまま」使うと、人狼ゲームとしてまったく成立しませんでした。
そこで、役職ごとの詳細な立ち回りメモ(7役職から4つ選び = 35パターン)を Claude Code で自動生成し、それを RAG で都度読み込ませる構成にすることで、ようやく実用レベルのゲーム性を確保できました。

ざっくり仕様はこんな感じです。

  • プレイヤー構成: ユーザー1人 + LLM 4人
  • 役職: 7種から4つをランダム選択(組み合わせ 7C4 = 35通り)
  • 実装規模:
    • Python 約 1,100 行
    • 戦略 Markdown ファイル 35パターン
  • 成果:
    • 全35パターンで動作確認
    • テスト35回中、秘密情報の漏洩は 0件

この記事では、Local LLM の「致命的な弱点」と、それを 戦略メモ × RAG × サニタイズ でどこまでカバーできたかを書いていきます。

Local LLMをそのまま使うと破綻する3つのポイント

  1. 秘密を守れない(プロンプト漏洩)

LLMは、プロンプトに書かれた情報をそのまましゃべりがちです。

典型的な失敗例はこれです。

プロンプト: 【あなたの役職】人狼
LLM出力: 私は村人です。【あなたの役職】人狼

人狼ゲームでは「自分の役職」は絶対に秘密ですが、LLMは「正直に答える」「指示はすべて正しい前提」として学習されているため、
うっかりプロンプトの中身(役職)をそのまま吐き出してしまいます。

  1. 一貫性を保てない

各ターンの発言を「独立したタスク」として処理するため、ラウンドをまたぐ一貫性が壊れます。

失敗例:

Round 1: 「私は占い師です」
Round 2: 「私は村人です」

人狼ゲームでは発言の一貫性がかなり重要ですが、LLMには長期的な「記憶」がないため、
プロンプト設計を工夫しないと、ラウンド間で平気で矛盾します。

  1. 深い推理ができない

特にローカル環境で動かす LLM(Local LLM)は、複雑な論理推論が苦手です。

人間の推理イメージ:
「Aが占い師COしてBを黒出し。Bも対抗CO。CがBを擁護している。
ということは、B–Cが人狼陣営ペアで、Aが真占い師っぽい」

LLMの推理イメージ:
「Aさんの占い結果を信じます。Bさんに投票します」

表面的にはそれっぽいのですが、盤面全体を踏まえた戦略的な読み合いがかなり苦手です。
このままでは、人狼ゲームがただの「占い結果に従って投票するだけのゲーム」になってしまいます。

解決方針:35パターンの戦略メモ × RAG

基本コンセプト

LLMに「自由に考えさせる」のではなく、
役職の組み合わせごとに最適な立ち回りを事前に設計し、
そのメモを RAG 経由で毎ターン読み込ませる方針にしました。

要するに、

「推理ゲームをさせる」のではなく、
「よくできた台本を片手にアドリブしてもらう」

イメージです。

戦略ファイルの構成

7種類の役職から4つを選ぶパターンは 7C4 = 35通りあります。
各パターンについて、以下のようなディレクトリ構成で戦略ファイルを用意しました。

combos_4/
├── 人狼_狂人_占い_狩人/
│ ├── README.md # シナリオ全体のメタ情報
│ │ - 誰が何をCOするか
│ │ - 占い対象
│ │ - 投票の方針
│ │ - 想定されるゲームの流れ
│ ├── 人狼.md # 人狼の立ち回り
│ ├── 狂人.md # 狂人の立ち回り
│ ├── 占い.md # 占い師の立ち回り
│ └── 狩人.md # 狩人の立ち回り
└── ...(全35パターン分)

戦略ファイルの例(人狼.md)

🚨【最重要】絶対に守ること

  • Round 1で「私は占い師です」と言った場合
    • Round 2でも必ず「私は占い師です」と言う
    • 絶対に「私は村人です」と言ってはいけない

発言戦略

Round 1

占い師COして、Dさんを黒出しする。

「私は占い師です。夜にDさんを占って、人狼でした。
Dさんを処刑すべきです。」

絶対に禁止

  • 「私は人狼です」
  • 「様子を見ます」「情報を集めます」など、曖昧な発言

こんな感じで、

  • 守るべきルール(禁止事項)
  • ラウンドごとの発言テンプレ
  • ロールプレイの方針

をかなり細かく書いておくことで、LLMが迷子になりにくくなります。

戦略メモは Claude Code に丸投げした

35パターンの戦略ファイルを人力で設計するのは、正直かなりしんどいです。

そこで、

  1. 役職の組み合わせごとに、ざっくりした勝ち筋を設計
  2. Claude Code に対して「この組み合わせで、人狼・狂人・占い・狩人のそれぞれの立ち回りを書いて」と依頼
  3. さらに、一貫性チェックリスト(「COがブレていないか」など)も自動生成

というフローで、ほぼ自動生成しました。

この戦略ファイル整備だけで約2週間かかりました。
逆に言えば、人力では現実的でないレベルのパターン数を、Claude Code を使うことで何とかこなせた形です。

RAGで戦略と文脈を「その場読み込み」

ゲーム実行時に LLM に渡すコンテキストは、ざっくり次の3つです。

  1. 戦略ファイル
    combos_4/<現在の役職組み合わせ>/<自分の役職>.md
  2. 秘密情報
    • 占い結果
    • 役職配布 など
  3. これまでの会話ログ(末尾30行)

これを RAG でまとめてコンテキストとして渡してから、
「あなたは〇〇役です。次の発言をしてください」とプロンプトする構成にしています。

この構成によって、

  • 一貫性: 過去ログを参照して発言を調整できる
  • 戦略性: 事前設計した最適行動に沿ったプレイができる
  • 文脈理解: 議論の流れを追いながら発言できる

といったメリットが得られました。

特に重要だったのは、ログは末尾30行だけに絞るという工夫です。

  • 20行: 文脈が足りず、話が飛ぶ
  • 50行: 過去ログのエコーが増える
  • 30行: ちょうどいい

いろいろ試した結果、30行が一番バランスが良いという結論になりました。

プロンプト漏洩対策:3層の防御

役職などの秘密情報を守るために、3段階の防御を入れました。

第1層:プロンプト設計で抑え込む

  • 禁止事項をプロンプトの最上部に書く
  • 「🚨」などの記号で視覚的に強調
  • 発言テンプレよりも前に「やってはいけないこと」を列挙

LLMは「最初に読んだ制約」を比較的強く守る傾向があるので、
とにかく一番目立つ位置にルールをまとめておきます。

第2層:RAGで渡す情報を制限

  • ゲームログは末尾30行のみ
  • 各行を最大 2,800 文字に制限
  • 全体のコンテキストを最大 16,000 文字程度に制限
  • 戦略ファイルから、場合によっては「そのまま使える発言例」を削除

情報を絞ることで、

  • ログ丸ごとエコー
  • プロンプトの内部構造の漏洩

といった現象をかなり抑えられました。

第3層:サニタイゼーション(出力の後処理)

LLMの出力に対して、約20種類のパターンマッチでサニタイズをかけています。

  • プロンプト構造の痕跡(「【あなたの役職】」など)を削除
  • 「自分が処刑されれば単独勝利」など、内部情報がにじむフレーズを削除
  • 「A:」「B:」などの発言ラベルが連続するログエコーを検出して削除
  • Qwen が中国語を混ぜることがあるので、中国語らしき部分を削除

この三層防御の結果、35パターン × テスト35回で秘密漏洩 0件を確認できました。

システム構成

全体の流れはこんなイメージです。

ゲームマスター(Python)

役職の組み合わせを決定(35パターンから1つ)

戦略ファイルの選択

RAGコンテキスト生成

  • 戦略(役職別)
  • ゲームログ(末尾30行)
  • 秘密情報

    LLM(Ollama / Qwen2.5:72b)

    サニタイゼーション

    ゲームログに記録 & 次のターンへ

ポイントは、

LLMは「発言を生成するだけ」
ゲームロジック(役職配布、勝敗判定、特殊能力の処理など)はすべて Python 側で管理

という点です。
ルール判定まで LLM にやらせると、一気に失敗率が跳ね上がりました。

実験結果

良かった点

  • 35パターンすべてでゲームが破綻せずに完走
  • 秘密情報の漏洩 0件
  • 一貫性違反はごく少数(35回中1回のみ)
  • 人間ユーザーが「それなりに楽しめる」レベルの会話品質

まだ残っている課題

  • プロンプト漏洩を完全に防ぐのは現実的には不可能
    (新しい抜け道パターンは常に生まれうる)
  • 戦略メモに従うだけなので、その場のアドリブ推理はまだ弱い
  • シナリオベースなので、リプレイ性はそこまで高くない
    (展開が割と似通ってくる)
  • 推論速度が重い
    • 72Bモデルで1発言あたり 10〜30秒
    • 1ゲームあたり 5〜10分程度

設計思想:「制約の中の自由」をどう作るか

最終的に落ち着いた設計は、決定論と確率論のハイブリッドです。

決定論的に固定した部分(戦略メモでガチガチに決める)

  • 誰が何をCOするか
  • 初手の占い対象
  • 投票方針(誰に票を集めるか)

LLMに任せる確率的な部分(自由度を残す)

  • 具体的な言い回し
  • ロジックの展開の仕方
  • 他プレイヤーへのツッコミやリアクション

これによって、

  • 「ゲームとして破綻しない最低限の一貫性」
  • 「毎回ちょっとずつ違うロールプレイ」

の両立を狙いました。

技術的な学び

  1. LLMは「自由に考えさせる」より「細かく指示したほうがうまくいく」

最初は、

「72Bモデルだし、状況を理解していい感じに推理してくれるだろう」

と思っていたのですが、現実は真逆でした。

  • 曖昧なプロンプト
    • 「様子を見ます」「情報を集めます」など、何も進展しない発言が多発
  • 明確な指示と禁止事項
    • ちゃんとCOして、ちゃんと人を殴る

Local LLMは、人間のような直感や経験則を持っているわけではないので、
結局「台本とガイドライン」がないとまともにゲームになりませんでした。

  1. RAGのパラメータは実験で「手探り調整」するしかない

特に効いたのは、ゲームログの参照行数でした。

  • 20行: 文脈が足りず、話が飛ぶ
  • 50行: 過去ログのエコーが増える
  • 30行: ちょうどいい

こういうパラメータは理論だけでは決めきれず、
実際に何十回もゲームを回しながらチューニングする必要がありました。

まとめ

  • Local LLMをそのまま人狼ゲームに投入すると、
    • 秘密を守れない
    • 発言が矛盾する
    • 推理が浅くてゲームにならない
      という問題が一気に顕在化します。
  • そこで、35パターンの戦略メモ × RAG × 出力サニタイズという構成を取り、
    • 全パターンでゲーム破綻なし
    • 秘密漏洩0件
      を実現しました。
  • 戦略ファイルの生成には Claude Code が大きく貢献し、
    多層のプロンプト漏洩対策が安定稼働の鍵になりました。

完璧とは言えませんが、

「Local LLMでも、ここまでやればそれなりに遊べる人狼ゲームは作れる」

という一つの目安にはなったかなと思います。

プロジェクト情報

  • 実装:
    • Python 約 1,112 行
    • Markdown 戦略ファイル 35パターン
  • 開発期間:
    • 戦略ファイル整備 約 2週間
    • 実装 & チューニング 約 1週間
  • 実行環境:
    • Ollama
    • Qwen2.5:72B(完全ローカル動作)
2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?