概要
ローカルで Gemma 4:e4b を動かし、ステラソラ攻略有志 Wiki のコメントがルールに違反していないか自動判定し、Discord に通知する仕組みを作ってみました。実際に運用してみたところ、クラウドモデル(Gemini 等)との知能差やローカルモデルのルール設定の難しさが見えてきたので、備忘録としてまとめてみました。
私は上記 Wiki の運営関係者ではなく、一閲覧者として「ローカルLLMでどこまで判定できるか」を試した個人的なプロジェクトです。実際にこのボットで投稿を削除したり、公式に導入したりするものではありません。
リポジトリ
プログラムの全容はこちらに置いてあります。
実行環境
自宅の PC 環境で Docker を使って動かしています。
- OS: Windows 11
- メモリ: 32GB
- GPU: GeForce RTX 2070 SUPER
- コンテナ: Docker Desktop (Ollama / Python)
やったこと
掲示板を定期的にスクレイピングし、取得したコメントを AI に投げて、ルールに基づいた違反判定を行わせるシンプルなツールです。
1. 掲示板のコメントをスクレイピング
Python + BeautifulSoup4 を使用しています。掲示板の各投稿から、本文、投稿者 ID、日時を抽出します。
def comment_iterator(soup: bs4.BeautifulSoup) -> Iterator[Comment]:
li_list = soup.select('div#body li')
for li in li_list:
texts = li.find_all(string=True, recursive=False)
if not texts: continue
text = ''.join(texts).strip().replace('\n', '')
match = re.match(r'^(.+) -- \[(.+)\]', text)
if not match: continue
comment = match.group(1)
date_userid = match.group(2)
span_date = li.select_one('span[data-mtime]')
if not span_date: continue
date = datetime.fromisoformat(span_date['data-mtime'])
yield Comment(date_userid, comment, 'コメント/雑談掲示板', date)
2. Gemma 4:e4b でルール違反を判定
Ollama 上で Gemma 4:e4b を使用し、コメントが掲示板ルールに違反しているか判定させます。e2b も試してみましたが、精度に難(※)があったので e4b 以上のモデルをお勧めします。
※超明確な違反(「死ね」とかドストレートなコメント)じゃない限り違反と判定してくれませんでした。
2.1. Modelfile
掲示板の具体的なルールを SYSTEM プロンプトに埋め込んだカスタムモデルを作成しました。
FROM gemma4:e4b
PARAMETER temperature 0
PARAMETER num_ctx 4096
SYSTEM """
あなたは「ステラソラ攻略有志 Wiki」の雑談掲示板の管理人です。
後述する**判定プロセス**に従い、判定対象のコメントが **掲示板のルール** に違反するかを判定します。
(中略)
TEMPLATE """{{ .System }}
# 判定対象
以下のコメントを判定してください。
<<<
{{ .Prompt }}
>>>
"""
2.2. ollama-python で API を叩く
レスポンスを安定させるため、JSON フォーマットを指定してリクエストを投げます。
from ollama import Client
client = Client(host='http://ollama:11434')
def post(self, comment: Comment) -> OllamaServiceResponse:
response = client.generate(
model='wiki-moderator',
prompt=comment.comment,
stream=False,
format={
'type': 'object',
'properties': {
'is_violation': {'type': 'boolean'},
'violation_type': {'type': 'string'},
'violation_description': {'type': 'string'},
},
'required': ['is_violation', 'violation_type', 'violation_description'],
},
options={
'temperature': 0,
'num_ctx': 4096,
}
)
response_dict = json.loads(response.response)
return OllamaServiceResponse(
is_violation=response_dict['is_violation'],
violation_type=response_dict['violation_type'],
violation_description=response_dict['violation_description'],
)
3. Discord Webhook で通知
判定結果が is_violation: true の場合のみ、Discord に詳細を投稿します。
def post_to_discord(self, content: str) -> None:
payload = {'content': content}
res = requests.post(self.webhook_url, json=payload)
res.raise_for_status()
実際に動かしてみた結果
とりあえず形にはなり、自動で違反を拾ってくれるようにはなりました。
レスポンス速度もおおよそ2秒前後と問題ありません。
が、精度面に問題があり 「実運用にはもう一工夫必要」 というのが正直な感想です。
1. 用語の理解不足
ゲーム特有の用語や略称を正しく理解できていません。
例えば、 「ハフバ(ハーフアニバーサリー)」 を含むコメントを Gemma に判定させると、以下のような回答が返ってきました。
「『ハフバ』という、ステラソラとは別のゲーム作品名を挙げて言及しているため、他のゲーム作品を引き合いに出す行為(ルール違反)に該当します。」
「ハフバ」というイベント略称を「他作品の名前」と勘違いして、ルール違反とみなしています。
掲示板のルールに加えてゲーム用語を補強する必要がありそうです。
2. 文脈理解の過敏さ
少しでも強い表現があると、過剰に反応する傾向があります。
「(説明を読もう!)」という強い命令口調と、相手を責め立てるような威圧的な口調が認められるため、ルール違反(喧嘩腰な書き込み)に当たります。
掲示板のルールに「~しろよ、~やれよ。等の強い命令口調」という項目があるため、それに忠実すぎた結果、一般的な注意喚起まで違反として拾ってしまいます。
ルールの例外を読ませるのは、このサイズのモデルにはまだ荷が重いのかもしれません。
Gemini との比較
同じプロンプトを Gemini に投げると、これらは完璧にクリアされます。
ハーフアニバーサリーという祝事に対し(云々)
「(説明を読もう!)」という表現は、人によっては「上から目線」「喧嘩腰」と感じる可能性があります。掲示板のルールにある「他者への誹謗中傷・喧嘩腰な書き込み」に抵触するかどうかの瀬戸際ですが、内容自体が正しいアドバイスであるため、これだけで即座に削除されることは稀です。
(後者は過敏すぎる気もしますが、まあ問題なしと判定はしているので良しとします)
このようにスラング、文脈まで汲み取った上でかなり正確に返してくれます。
やはり「知能のベースライン」に差を感じました。
今後の展望
トークン数(コンテキストサイズ)にはまだ余裕があるので、以下の改善で精度向上が狙えそうです。
- 用語集の埋め込み: 「ハフバ=イベント名」などの定義を SYSTEM プロンプトに追加する
- 用例の追加: 「こういうのは違反、こういうのはセーフ」という具体例をいくつかプロンプトに含める
さいごに
今回、初めて Gemma 4 と Ollama を触ってみましたが、環境構築から実装まで 2 日足らずで完了しました。ローカル LLM の開発体験は非常に良く、API 料金やプライバシーを気にせずガシガシ試行錯誤できるのは大きな魅力です。精度面ではクラウドモデルに及びませんが、補助的な「一次検閲ボット」としては十分に可能性を感じるツールになりました。興味がある方はぜひ試してみてください。