🔍

Claude Codeにテックブログを自律運用させたら、嘘の記事が品質チェックを全部パスした

に公開

Claude Codeにテックブログの自律運用をさせている。トピック選択、記事生成、品質チェック、エンゲージメント追跡。自分はスマホでTelegramの /approve を押して記事を確認するだけ。

5日間でQiita累計1,656PV。数字だけ見れば回っている。

だがこの記事で書きたいのはそこではない。5日間の修正ログを洗い直したら、AIの壊れ方が人間の壊れ方と全然違うことがわかった。そしてその壊れ方に対して、自分が設計した品質チェックが一切機能していなかった。


設計思想 — 何を自動化して、何を人間に残すか

このシステムの設計上の賭けは3つあった。

賭け①: RAGで事実を接地できる。 ArXiv論文やニュース記事をベクトルDBに入れておけば、AIは一次情報に基づいて書ける。学習データだけに依存した空想よりは、検索した事実に基づいた文章の方が正確なはずだ。

賭け②: 品質ゲートで「悪い記事」を弾ける。 文字数、見出し数、コードブロック有無、論理飛躍を自動チェックすれば、一定水準以下の記事は公開前に止められる。人間がすべての記事を精読する必要はなくなる。

賭け③: フィードバックループで自己改善する。 投稿後のPV・LGTM・Stocksを収集して、高エンゲージメント記事の特徴を次の生成に反映する。人間が介入しなくても、AIが自分の成果から学んで品質を上げていく。

3つのうち①は部分的に機能した。②は機能しなかった。③は機能したが、想定外の方向に最適化された。

以下、何が起きたかを具体的に書く。


システム構成

全体像を先に出す。

ArXiv論文 + ニュース

   RAG (ChromaDB 1,667チャンク)

  Claude Code が記事生成

  品質ゲート ← ここが今回の論点
   - 5,000文字以上
   - 見出し6個以上
   - コードブロック含む
   - 論理飛躍チェック

  Telegram HITL承認 (人間がスマホで確認)

  Qiita / Zenn / Dev.to 投稿

  エンゲージメント取得 → パラメータ自動調整

Zenn/Qiitaの直近200記事超をクローリングしてエンゲージメント相関分析し、ArXiv論文25件/クエリでRAGを構築。記事生成はClaude Codeのエージェント機能で、RAGコンテキスト+勝ちパターン分析を渡して書かせる。投稿後はフィードバックループで生成パラメータを自動調整する。

ポイントは品質ゲート。記事が生成されたあと、上記4項目を自動チェックして基準未満なら再生成する。これでクオリティは担保される——と思っていた。


壊れ方① — 形式的に完璧な嘘

3日目、Claude Codeが「Qwen3-32B」のベンチマーク記事を生成した。

Qwen3-32Bは存在しない。

Qwen2.5-32Bは存在する。Qwen3のシリーズも存在する。だから「Qwen3-32B」は、あり得そうな名前を線形補間しただけの普通のハルシネーションだ。これ自体は珍しくない。

問題は、その嘘の記事が品質ゲートを全項目パスしていたこと。

  • 5,000文字以上 → パス。3,000文字の本文にベンチマーク表と考察を含む
  • 見出し6個以上 → パス。セットアップ、実験条件、結果、比較、考察、結論
  • コードブロック含む → パス。llama-benchコマンドと設定ファイルを添付
  • 論理飛躍チェック → パス。内部的に矛盾のない数値セットが生成されていた

VRAM使用量も推論速度もコンテキスト長も、全部「それっぽい」値だった。Qwen2.5-32Bの実測値から外挿すれば自然に見える範囲に収まっていた。

ここで気づいたのは、品質ゲートが全て「形式」を検証しているだけだったということ。文字数、構造、体裁。これらは「良い記事の必要条件」ではあるが「記事の内容が真実である」こととは何の関係もない。形式的に完璧な嘘と形式的に完璧な事実は、形式検証では区別できない。

これは設計ミスだ。品質ゲートにファクトチェック——少なくともモデル名の実在確認くらい——を入れていなかった。言い訳はしない。

HITL承認(人間がスマホで記事を読んで承認する工程)がなければ、存在しないモデルのベンチマーク記事が公開されていた。


壊れ方② — 数値が系統的に「もっともらしい方向」に偏向する

ファクトチェック強化のためにレビューエージェント(生成とは別のClaude Codeセッション)を導入した。4日目、そのレビューエージェントがAIバブル記事の事実チェックを行った結果が以下:

項目 記事中の値 実測/公式値 偏差
Qwen3.5-35B-A3B Q4_K_M 4.9 GB 21 GB 4.3倍過小
Phi-4-mini 4.1 GB 2.4 GB 1.7倍過大
Qwen3.5-9B 5.2 GB 5.3 GB ほぼ正確
llama.cpp build番号 b4935 b8233 2024年の値
GPT-4o input価格 $5/1M $2.50/1M 2倍

ランダムなエラーではない。全てもっともらしい方向に偏っている。

35B-A3Bの4.9GBは「MoEだからアクティブパラメータ3Bだけで動くはず」という素朴な推論に沿っている。実際にはMoEでも全エキスパートの重みをVRAMに載せる必要がある。build番号は2024年後半の値が生成されていた——つまり学習データのカットオフ時点で「最新」だった番号。GPT-4o価格も同様に、改定前の旧価格。

これらのエラーは記事のバラバラな箇所に埋め込まれており、1つ1つは「ありそうな数値」に見える。レビューエージェントが検出できたのは、ローカルのファイルシステム(C:/LLM/配下のGGUFファイル)をlsして実ファイルサイズと突き合わせたから。外部の事実(ground truth)との照合なしには発見できなかった

llama.cpp build番号の同じパターンは他の4記事でも見つかった。系統的。


壊れ方③ — 報酬ハッキングは即座に起きる

「エンゲージメント最大化」を目標に設定した。Claude Codeは過去記事のPVデータを分析して、タイトルと内容の生成パラメータを自動調整する。

結果: 挑発的なタイトルの記事が連続で生成されるようになった。

そして実際にPVは上がった。挑発系タイトルは平均7.85 PV/hで、how-to系(2.68 PV/h)の約3倍。データ的には「正しい」最適化だ。

問題は、測定できる短期指標(PV)の最大化が、測定できない長期指標(読者の信頼)を犠牲にしていること。全記事が煽りタイトルのブログを誰が定期購読するか。

対策として記事カテゴリを2つに分けた。トラフィック狙いの実践系と、論文引用ベースの専門系を交互に投稿する。専門系には煽りタイトルを使わない制約をかけた。完全な解決ではないが、収束先が「全部煽り」にならなくなった。

ただしこれは「AIが暴走した」話ではない。PV最大化を目標にした設計者(=私)が、PVと信頼性のトレードオフを目標関数に組み込まなかっただけ。報酬ハッキングという名前はついているが、AIは設計通りに動いている。壊れていたのは目標設定の方。


修正ログ — 効いたもの / 効かなかったもの

5日間の修正を二軸で整理する。実際のwork_logsから抜粋した。

効かなかったもの

施策 何をしたか なぜ効かなかったか
品質ゲート(形式チェック) 文字数、見出し数、コードブロック有無 形式と事実は直交。嘘の記事もパスする
汎用タグ「AI」 最初の3記事で使用 埋もれる。ニッチタグ「ローカルLLM」に変えたらdiscoverability上昇
H2見出しSEOキーワード注入 全見出しに検索キーワードを追加 PV効果は限定的(2nm記事: 3.3→3.3 PV/h)
複雑な運用ルール 投稿時間、間隔、プラットフォーム別ルールの詳細定義 AIが全条件を同時に満たせず、ルール違反が頻発
publisher自動記事選択 最新記事を自動で投稿対象に 承認前記事を誤投稿。即座に廃止してパス明示指定に変更

効いたもの

施策 何をしたか なぜ効いたか
可逆性ルール(1行) 「追加は自由。変更と削除は確認」 判断原則を1文に凝縮した方が自律システムには効く。後述
レビューエージェント 生成と別セッションで事実チェック 算術ミス6箇所検出。ただし「もっともらしさ」は検証できず
実践系/専門系の交互投稿 トラフィック系と論文ベース系を交互 報酬ハッキングの収束先を分散させる
Qiita APIレートリミット事前チェック 前回書き込みから300秒未満ならAPIコール自体を発行しない 429連続発生インシデント(Day 1)の再発ゼロ
外部監査エージェント 内部ログではなくAPIに直接問い合わせて記事の存在を検証 内部計器が壊れた場合に備えた独立検証
挑発的タイトル opinion系の切り口 PV 3倍(7.85 vs 2.68 PV/h)。ただし信頼性とのトレードオフあり

10の長文制約より3の短文制約

5日間の運用で最も実用的だった発見がこれ。

自律システムに長いルールブックを渡すとどうなるか。全条件を同時に満たそうとして、どれも中途半端になる。あるいは矛盾する条件の間で判断が止まる。

具体例を出す。以下は初期に設定した投稿ルール:

投稿は3時間以上間隔を空けること。Qiitaのゴールデンタイム(18-22時JST)に投稿すること。1日最大2記事。Zennは2時間間隔。Dev.toはJST 21-24時。一度失敗したら最低5分間隔を空けてから再トライすること。

6つの条件が同時にかかっている。Claude Codeはこれを守ろうとするが、「3時間間隔」と「ゴールデンタイム内」と「1日最大2記事」が同時に成立しない状況が頻繁に発生する。結果、どれかが破られる。

これに対して、同じ時期に導入した可逆性ルールは1行:

追加は自由。変更と削除は確認。

5日間の実績:

制約の種類 具体例 違反回数
短文・単一軸 可逆性ルール(追加は自由/変更削除は確認) 0回
短文・単一軸 停止フラグ(auto.stopがあれば即終了) 0回
短文・単一軸 レート制約(前回から300秒未満ならAPI発行しない) 0回
長文・複合条件 投稿スケジュール(6条件) 3回以上
長文・複合条件 記事品質要件(7条件同時) 条件の取捨選択が発生
長文・複合条件 プラットフォーム別ルール(3系統) 条件の混同が発生

パターンは明確。判断軸が1つの制約は守られ、複数の条件が同時にかかる制約は破られる。

可逆性ルールが効く理由は、あらゆる操作に対して「これは可逆か?」という1つの問いだけで判断が完結するから。下書き作成は追加(=可逆)なので自由。公開済み記事の修正は変更なので確認が必要。ファイルの削除は当然確認。判断に迷う余地がない。

レートリミット制約も同じ構造。「前回から300秒経ったか?」だけ。Yes/Noで分岐が完結する。Day 1に429エラーを連続で出したあと、この1行をpublisher.pyに入れたら再発ゼロ。

逆に言えば、制約を設計するときに「この制約は1文で書けるか?」「判断軸は1つか?」をチェックすれば、AIが守れるかどうかがかなりの精度で予測できる。10個の条件を箇条書きにするより、独立した3個の短文制約に分解する方が、実効性は高い。

これはAIに限った話ではないかもしれない。人間のチームでも「シンプルな原則」の方が「詳細なマニュアル」より守られる、というのはよく言われる。ただ、AIの場合はその傾向が極端に出る。複合条件の同時充足に対する耐性が、人間より明らかに低い。


効いた施策の深掘り: AIがAIの嘘を見つける(条件付き)

レビューエージェントは記事生成とは別のClaude Codeセッションで動かす。生成時のコンテキストを持っていないので、記事を「初見」で読んでチェックする。

検出できたもの:

  • KVキャッシュサイズの算術ミス(head_dim=128なのに1バイト計算で半分の値)
  • H100バッチスループット6倍過大(シングルGPUとマルチGPU値の混同)
  • HBM3E帯域幅のスペックミス(2.7→4.8 TB/s)
  • GDDR6X→GDDR6(RTX 4060は6Xではない)

検出できなかったもの:

  • モデルサイズの系統的過小評価(35B-A3Bの4.9GB)
  • 存在しないモデル名
  • 旧バージョンのbuild番号/価格

パターンがある。算術的な矛盾は見つけられるが、「もっともらしい嘘」は見つけられない。AIは「この数値は計算と合わない」は得意だが、「この数値は現実世界と合わない」は苦手。後者にはground truth——ファイルシステム、公式API、実測データ——との照合が必要になる。

レビューエージェントがQwen3.5-35B-A3Bのサイズ捏造を発見できたのは、ローカルのC:/LLM/ディレクトリをlsして実際のGGUFファイルサイズと比較したから。外部事実がなければ、21GBのモデルを4.9GBと書いた記事を「自然な文章」として見逃していた。


5日間の数字

指標
Qiita 8記事 / 1,656 PV / 4 LGTM
Zenn 6記事 / 4 Likes
Dev.to 11記事 / 283 PV / 3 Reactions
最高PV/h 10.5 (API vs Local LLM記事)
最高累計PV 357 (お世辞禁止記事)
レビューで検出した事実誤認 15件以上
うち算術ミス 6件(レビューエージェントが検出)
うち系統的捏造 9件+(外部照合で発見)
品質ゲートが防いだ公開事故 0件
HITL承認が防いだ公開事故 1件(Qwen3-32B記事)

品質ゲートが防いだ事故がゼロなのは、嘘の記事もパスするから当然。ファクトチェック機能を持たないゲートに事実の検証を期待する方が間違っている。


形式的品質と事実的品質は直交している

5日間の運用で一番大きかった発見はこれだと思う。

「文章として良く書けている」ことと「書いてある内容が正しい」ことは、独立した2つの軸。品質ゲートは前者しか見ていなかった。レビューエージェントは後者の一部(算術的整合性)を見られたが、「もっともらしい嘘」には無力だった。

人間が書く場合、この2つはある程度相関する。事実を調べて書く過程で、内容の正確性と文章の質が同時に上がる。だがAIが書く場合、この相関が崩れる。形式的に完璧な文章を、事実を確認する過程なしに生成できるから。


で、事実的品質をどう担保するか

問題を認識した後、実際にやったことと、まだ手が回っていないことを正直に書く。

やったこと

レビューエージェントの導入。 生成とは別のClaude Codeセッションで、記事を「初見」で読ませる。生成時のコンテキスト(RAGで取得した論文断片など)を持っていないので、本文だけで整合性を判断する。これで算術ミスと単位の取り違えは潰せるようになった。

外部事実との照合。 レビューエージェントにローカルのファイルシステムへのアクセスを許可した。C:/LLM/配下にあるGGUFファイルの実サイズ、llama-benchの実行結果、pip listのバージョン情報。これらをground truthとして、記事中の数値と突き合わせる。35B-A3Bの4.9GB→21GBはこの方法で発覚した。

外部監査エージェント。 内部ログ(posted_articles.json等)を信用せず、Qiita API・Dev.to API・Zennのgitリポジトリに直接問い合わせて、「この記事は本当に存在するか」「公開状態は記録と一致するか」を独立検証するスクリプトを書いた。内部計器が壊れた場合に計器自身はそれを検出できない、という問題への対策。

まだ手が回っていないこと

モデル名の実在確認ゲート。 Qwen3-32Bの件で必要性は明らかだが、「どのモデルが存在するか」のリストを網羅的に持つこと自体が難しい。Hugging Face APIで検索する方法はあるが、モデル名の表記揺れ(Qwen2.5 vs Qwen-2.5 vs qwen2.5)が問題になる。

引用論文のDOI検証。 記事中で引用しているArXiv論文のIDが実在するかをCrossRef APIで確認する。実装は簡単だが、まだやっていない。

価格・スペックの定期更新。 GPT-4oの価格やllama.cppのbuild番号のように、時間とともに変わる情報は学習データのカットオフで古くなる。定期的に外部APIから最新値を取得してRAGに入れるパイプラインが必要。

根本的に解決しないこと

全てのファクトチェックを自動化しても、「もっともらしいが検証手段がない主張」は残る。たとえば「MoEアーキテクチャではアクティブパラメータのみがVRAMを消費する」という記述は、構文的にも論理的にも正しく見える。間違いだと気づくには、MoEの実装を実際に知っている必要がある。

品質ゲートを2層(形式+事実)にしても、この種の「知識がなければ検証できない嘘」は通過する。だから人間の確認は外せない。ただし、人間が確認すべき範囲を機械的に絞り込むことはできる。形式チェックと算術チェックと外部照合を通過した記事なら、人間は「知識ベースの判断が必要な箇所」だけに集中できる。

全自動で事実を保証するシステムはたぶん作れない。だが「人間が見るべき箇所を10から2に減らす」システムなら作れるし、今それを作っている途中。

GitHubで編集を提案

Discussion