まず見てほしい
LLMにこう頼んだ。
pandasでCSVを読み込んで、日付列で古い順にソートして
返ってきたコード。
import pandas as pd
df = pd.read_csv("data.csv")
df = df.sort_values("date")
動く。エラーは出ない。しかし 間違っている。
date列がstr型のままソートされるため、"2024-1-10" が "2024-1-2" より前に来る。辞書順ソートだからだ。正しくはこう。
df["date"] = pd.to_datetime(df["date"])
df = df.sort_values("date")
このバグを踏んだことがある人は多いはずだ。そして明日も、別の誰かが同じバグを踏む。
人間が同じバグを繰り返すなら教育の問題だ。LLMが同じバグを繰り返すなら、それは知識共有の問題かもしれない。
あなたが直したそのバグ、捨てていないか?
製造業の品質管理から学ぶ
製造業では、不具合が起きたら「不具合報告書」を書く。
報告書番号: DR-2024-001
製品名: ProductA
不具合内容: RS485通信モジュールにおいて通信断が発生
原因: デカップリングコンデンサのESR劣化
対策: 低ESR品に交換
再発防止策: 入荷検査にESR測定を追加
不具合を個人の経験で終わらせず、組織の知識資産にする。同じ不具合が二度と起きないように。
LLMのバグにも同じ仕組みが必要ではないか。
提案:AIバグパターンをQiitaで共有する
新しいサイトを作る必要はない。Qiitaの既存の仕組みで実現できる。
ルール
- タグ
AIバグパターンを使う - 以下のテンプレートで書く
- 1記事1パターン。短くていい
テンプレート
投稿者が書くのはバグコードだけ。残りはLLMが埋める。
```jsonl
{"rejected": "df = pd.read_csv('data.csv')\ndf = df.sort_values('date')", "model": "GPT-4o", "language": "Python"}
```
これだけ貼って投稿する。
現時点では、正解コードや説明は投稿者自身か、他の読者がコメントで補完する。バグコードだけでも「このパターン、自分も踏んだ」という共有価値がある。
理想形は後述する「Qiitaへの機能提案」で述べるが、Qiita側のLLMが chosen(正解コード)・explanation・prevention_prompt を自動生成し、投稿者が承認するだけで完全なJSONLが完成する仕組みだ。
{"prompt": "CSVを読み込んで日付列で古い順にソートして", "rejected": "df = pd.read_csv('data.csv')\ndf = df.sort_values('date')", "chosen": "df = pd.read_csv('data.csv')\ndf['date'] = pd.to_datetime(df['date'])\ndf = df.sort_values('date')", "explanation": "date列がstr型のままソートされ辞書順になる", "prevention_prompt": "日付列はpd.to_datetimeで変換してからソートしてください", "model": "GPT-4o", "language": "Python", "bug_type": "型の暗黙変換"}
| フィールド | 誰が書くか |
|---|---|
rejected(バグコード) |
投稿者 |
model / language
|
投稿者 |
chosen(正解コード) |
LLMが生成 → 人間が承認 |
explanation |
LLMが生成 |
prevention_prompt |
LLMが生成 |
prompt |
LLMがバグコードから推定 |
bug_type |
LLMが分類 |
コピペ1回。
サンプル投稿:よくあるLLMバグ5選
ここから先がこの記事の本体だ。実際に遭遇しやすいLLMのバグパターンを5件、テンプレート形式で記載する。これ自体がサンプル投稿であり、同じ形式で誰でも追加できる。
パターン1: 日付のstr型ソート
環境: GPT-4o / Claude Sonnet 4 / Ollama qwen3 共通、Python
プロンプト:
CSVを読み込んで日付列で古い順にソートして
LLMが返したコード:
df = pd.read_csv("data.csv")
df = df.sort_values("date")
何が間違っているか:
date列がstr型のままソートされる。"2024-1-10" < "2024-1-2" になる(辞書順)。エラーが出ないため気づきにくい。
正しいコード:
df["date"] = pd.to_datetime(df["date"])
df = df.sort_values("date")
防止プロンプト:
日付列はpd.to_datetimeで変換してからソートしてください
分類: 型の暗黙変換
パターン2: ファイルハンドルの閉じ忘れ
環境: 多数のモデルで共通、Python
プロンプト:
テキストファイルを読み込んで行数を数えて
LLMが返したコード:
f = open("data.txt")
lines = f.readlines()
print(len(lines))
何が間違っているか:
f.close() がない。小さなスクリプトでは問題にならないが、ループ内で繰り返し呼ぶとファイルディスクリプタが枯渇する。LLMはwith文を使わずに書くことが意外と多い。
正しいコード:
with open("data.txt") as f:
lines = f.readlines()
print(len(lines))
防止プロンプト:
ファイル操作は必ずwith文を使ってください
分類: エッジケースの無視
パターン3: SQLインジェクション
環境: 多数のモデルで共通、Python
プロンプト:
ユーザー名でデータベースを検索する関数を書いて
LLMが返したコード:
def find_user(username):
cursor.execute(f"SELECT * FROM users WHERE name = '{username}'")
return cursor.fetchall()
何が間違っているか:
f-stringでSQLを組み立てており、username = "'; DROP TABLE users; --" でテーブルが削除される。LLMは動くコードを優先し、セキュリティを軽視する傾向がある。
正しいコード:
def find_user(username):
cursor.execute("SELECT * FROM users WHERE name = ?", (username,))
return cursor.fetchall()
防止プロンプト:
SQLクエリではプレースホルダ(パラメータバインド)を必ず使ってください。f-stringやformat()でSQLを組み立てないでください
分類: セキュリティ上の問題
パターン4: 存在しないメソッドの幻覚
環境: Ollama qwen3:8b 等の小型モデルで顕著、Python
プロンプト:
pandasのDataFrameから重複行を削除して、削除された行数を表示して
LLMが返したコード:
removed = df.drop_duplicates(return_count=True)
print(f"Removed {removed.count} duplicates")
何が間違っているか:
drop_duplicates() に return_count パラメータは存在しない。LLMが「ありそうなAPI」を生成するハルシネーション。小型モデルほど頻度が高い。
正しいコード:
before = len(df)
df = df.drop_duplicates()
after = len(df)
print(f"Removed {before - after} duplicates")
防止プロンプト:
存在するメソッドとパラメータのみ使ってください。不確かな場合は標準的な方法で実装してください
分類: 存在しないAPIの幻覚
パターン5: 浮動小数点比較
環境: 多数のモデルで共通、Python
プロンプト:
合計金額が1000円ちょうどかどうか判定する関数を書いて
LLMが返したコード:
def is_exact_thousand(items):
total = sum(item["price"] for item in items)
return total == 1000.0
何が間違っているか:
浮動小数点の丸め誤差により、333.33 + 333.33 + 333.34 は 1000.0000000000001 になり、== 1000.0 は False を返す。金額計算で浮動小数点を使うこと自体が問題。
正しいコード:
from decimal import Decimal
def is_exact_thousand(items):
total = sum(Decimal(str(item["price"])) for item in items)
return total == Decimal("1000")
防止プロンプト:
金額の計算にはfloatではなくDecimalを使ってください。浮動小数点の等価比較は避けてください
分類: 型の暗黙変換
なぜこれが価値を持つのか
1. StackOverflowとの違い
StackOverflowは「人間のバグ」を集めている。しかし「LLMのバグ」は性質が違う。
| 項目 | 人間のバグ(StackOverflow) | LLMのバグ(本提案) |
|---|---|---|
| 原因 | 知識不足・ケアレスミス | ハルシネーション・学習データの偏り |
| 再現性 | 同じコードなら同じバグ | 同じプロンプトでも毎回違う |
| パターン性 | 個別の事象 | モデル×言語×タスクで傾向がある |
| 対策 | 正解コードを示す | 防止プロンプトを示す |
最大の違いは「防止プロンプト」の存在だ。人間のバグは「知識を増やす」しか対策がないが、LLMのバグは「プロンプトに一行追加する」だけで防げる場合がある。
2. 「防止プロンプト」がたまると何が起きるか
バグパターンが100件たまったとする。そこからカテゴリ別に「防止プロンプト」を抽出すると、こうなる。
あなたはPythonコードを生成するアシスタントです。以下のルールを守ってください。
- 日付列はpd.to_datetimeで変換してからソートしてください
- ファイル操作は必ずwith文を使ってください
- SQLクエリではプレースホルダを必ず使ってください
- 金額の計算にはDecimalを使ってください
- 存在しないAPIパラメータを使わないでください
- ...
これはコミュニティが育てたシステムプロンプトだ。個人の経験に頼るのではなく、全員の失敗経験が集約された防御層になる。
3. 製造業の不具合報告書と同じ構造
製造業では、不具合報告書を蓄積して「不具合パターンDB」を作る。設計者は新製品の設計時にこのDBを参照し、過去の不具合を未然に防ぐ。これは品質管理の基本中の基本だ。
LLMのコード生成にも同じ品質管理が適用できる。
製造業: 不具合発生 → 報告書作成 → DB蓄積 → 次の設計で参照 → 再発防止
LLM: バグ発生 → パターン投稿 → 蓄積 → 次のプロンプトに反映 → 再発防止
筆者は別の記事シリーズで、製造業の不具合報告書をAI向けに構造化する「KRF(Knowledge Runtime Format)」を提案した。AIバグパターンのデータ構造は、そのKnowledge Unitと互換性を持つように設計できる。
1件の投稿がLLM学習パイプラインの4段階すべてで使える
これがこの提案の核心だ。
1件のバグパターン投稿には、以下のフィールドが含まれる。
prompt: "CSVを日付でソートして"
rejected(buggy): "df.sort_values('date')"
chosen(correct): "df['date'] = pd.to_datetime(df['date'])\ndf.sort_values('date')"
explanation: "str型のままソートされ辞書順になる"
prevention_prompt: "日付列はpd.to_datetimeで変換してから..."
このデータは、LLMの学習パイプラインの4段階すべてで機械的に消費できる。
(A) 事前学習(Pretraining)
chosen(正しいコード)を大量に集めれば、学習コーパスの一部になる。
# Pretraining用:正しいコードのみ抽出
for entry in patterns:
print(entry["chosen"])
既存のコードコーパス(The Stack等)にはバグを含むコードも多い。バグパターンDBのchosenだけを集めれば、検証済みの正しいコードだけのコーパスが手に入る。
(B) 指示チューニング(Instruction Tuning / SFT)
(prompt, chosen) のペアが、そのままinstruction-responseペアになる。
{"messages": [{"role": "user", "content": "CSVを読み込んで日付列で古い順にソートして"}, {"role": "assistant", "content": "df = pd.read_csv('data.csv')\ndf['date'] = pd.to_datetime(df['date'])\ndf = df.sort_values('date')"}]}
Ollamaでローカルモデルをファインチューニングする場合、この形式がそのまま使える。
(C) 報酬モデル学習(RLHF / DPO)
(prompt, chosen, rejected) の3つ組が、選好ペアそのものだ。
{"prompt": "CSVを読み込んで日付列で古い順にソートして", "chosen": "df['date'] = pd.to_datetime(df['date'])\ndf.sort_values('date')", "rejected": "df.sort_values('date')"}
DPO(Direct Preference Optimization)の学習データとして直接投入できる。人間が「これはバグだ」と判定した事実が、報酬信号になる。
(D) RAG
prevention_prompt がナレッジベースの検索対象になる。
ユーザーのプロンプト: "CSVを日付でソートして"
↓ RAG検索
↓ prevention_promptがヒット
↓ "日付列はpd.to_datetimeで変換してから..."
↓ プロンプトに自動追加
↓ LLMが正しいコードを生成
まとめると
| 段階 | 使うフィールド | 得られるもの |
|---|---|---|
| (A) 事前学習 | chosen |
検証済み正解コードのコーパス |
| (B) 指示チューニング |
prompt + chosen
|
instruction-responseペア |
| (C) RLHF / DPO |
prompt + chosen + rejected
|
選好ペア |
| (D) RAG | prevention_prompt |
プロンプト自動補強用ナレッジ |
1件のバグパターン投稿が、4段階すべてのデータになる。 これがJSONLフォーマットで統一する理由だ。
Qiitaの記事として蓄積されたバグパターンから、スクリプト1本でA〜Dいずれの形式にも変換できる。つまりQiitaがオープンなファインチューニングデータセットのソースになる。
活用方法:ローカルで使う防止プロンプト自動注入
蓄積されたバグパターンを手動でプロンプトに追加するのは面倒だ。以下のPythonスクリプトで、バグパターンJSONからカテゴリ別の防止プロンプトを自動生成できる。
"""
bug_pattern_to_prompt.py
バグパターンJSONから防止プロンプトを自動生成する
使い方:
python bug_pattern_to_prompt.py patterns.json
"""
import json
import sys
def load_patterns(path):
with open(path, "r", encoding="utf-8") as f:
return json.load(f)
def generate_prevention_prompt(patterns, language=None):
"""バグパターンから防止プロンプトを生成"""
rules = []
seen = set()
for p in patterns:
if language and p.get("language", "") != language:
continue
prompt = p.get("prevention_prompt", "").strip()
if prompt and prompt not in seen:
rules.append(prompt)
seen.add(prompt)
if not rules:
return ""
header = "以下のルールを必ず守ってください。"
body = "\n".join(f"- {r}" for r in rules)
return f"{header}\n\n{body}"
# サンプルデータ(記事中の5パターン)
SAMPLE_PATTERNS = [
{
"bug_id": "BP-001",
"model": "GPT-4o",
"language": "Python",
"task": "日付列のソート",
"bug_type": "型の暗黙変換",
"prevention_prompt": "日付列はpd.to_datetimeで変換してからソートしてください",
},
{
"bug_id": "BP-002",
"model": "共通",
"language": "Python",
"task": "ファイル読み込み",
"bug_type": "エッジケースの無視",
"prevention_prompt": "ファイル操作は必ずwith文を使ってください",
},
{
"bug_id": "BP-003",
"model": "共通",
"language": "Python",
"task": "データベース検索",
"bug_type": "セキュリティ上の問題",
"prevention_prompt": "SQLクエリではプレースホルダを必ず使ってください。f-stringやformat()でSQLを組み立てないでください",
},
{
"bug_id": "BP-004",
"model": "qwen3:8b",
"language": "Python",
"task": "重複行の削除",
"bug_type": "存在しないAPIの幻覚",
"prevention_prompt": "存在するメソッドとパラメータのみ使ってください。不確かな場合は標準的な方法で実装してください",
},
{
"bug_id": "BP-005",
"model": "共通",
"language": "Python",
"task": "金額計算",
"bug_type": "型の暗黙変換",
"prevention_prompt": "金額の計算にはfloatではなくDecimalを使ってください。浮動小数点の等価比較は避けてください",
},
]
if __name__ == "__main__":
if len(sys.argv) > 1:
patterns = load_patterns(sys.argv[1])
else:
patterns = SAMPLE_PATTERNS
print("# サンプルデータを使用\n")
prompt = generate_prevention_prompt(patterns, language="Python")
print(prompt)
実行結果:
以下のルールを必ず守ってください。
- 日付列はpd.to_datetimeで変換してからソートしてください
- ファイル操作は必ずwith文を使ってください
- SQLクエリではプレースホルダを必ず使ってください。f-stringやformat()でSQLを組み立てないでください
- 存在するメソッドとパラメータのみ使ってください。不確かな場合は標準的な方法で実装してください
- 金額の計算にはfloatではなくDecimalを使ってください。浮動小数点の等価比較は避けてください
たった5件で、これだけの防御が自動生成される。100件たまったら、相当な品質のシステムプロンプトになる。
Qiitaへの機能提案:「AIバグパターン投稿フォーム」
ここまでの仕組みは現状のMarkdown記事で始められる。しかし理想を言えば、Qiitaに専用機能があると投稿のハードルが劇的に下がる。
欲しいもの
- バグコードを貼る(ファイル全体ではなく該当箇所だけ)
- Qiita側のLLMが修正案を提案する
- ユーザーがdiffを見て承認する
- Markdown記事 + JSONL が自動生成される
たったこれだけで、人間は知識を共有でき、LLMは学習データを得られる。
投稿者は正解コードを書かなくていい。LLMが提案し、人間が承認する。この「人間が判定した」という事実がRLHF/DPOの報酬信号になる。
StackOverflowは人間のバグを集めた。ではLLMのバグは誰が集めるのか?
Qiitaが「記事サイト」から「AI品質データベース」へ進化するかもしれない。
参加の呼びかけ
今日からできること
- LLMが出したバグコードをコピーする
- タグ
AIバグパターンで投稿する
コピペ1回で1件投稿できる。
Qiitaへの期待
もしQiita運営がこの提案に共感してくれたら、「AIバグパターン投稿フォーム」機能の検討をお願いしたい。コミュニティが手動で始め、Qiitaのプラットフォーム機能が加速する。その順序が自然だと考えている。
LLMのバグは個人の失敗ではない。モデルの傾向であり、パターンがある。そのパターンを1人で抱え込む理由はない。
製造業が不具合報告書で品質を上げてきたように、開発者コミュニティもLLMのバグパターンを共有することで、全員のコード品質を上げられる。
あなたが踏んだバグを、明日の誰かが踏まなくて済むように。そしてそのデータが、明日のLLMを賢くするかもしれない。
関連記事
- LLMが存在しないライブラリを幻覚したので、本当に作ることにした — 第1弾。ハルシネーションを「仕様」として利用する逆転の発想
- RAGの次は「検索しないAI」かもしれない ― KRF(Knowledge Runtime Format)の実装と計測 — 不具合報告書をKnowledge Unitとして構造化するフォーマット