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に減らす」システムなら作れるし、今それを作っている途中。