ファクトチェックを3段に畳む——320件の主張と143本の出典を、ひとつのコンテキストに入れずに裏取りする
解説マンガ1話ぶんの主張325件と出典143本を、ひとつのコンテキストに入れずに裏取りした。原稿を部ごとに切り出して「主張のリスト」に畳む、リストを「検証チェックリスト」に畳む、最後にリンクの実在と内容の直接関連性を別パスで検査して「報告」に畳む。3段の fan-out で巨大コンテキストを段ごとに圧縮する subagent パイプラインの組み方を、修正の確定権は人間に残すという境界線と一緒に書いた。
前回、ファクトチェックは7工程を踏むという話を書いた。抽出 → 優先度付け → 証拠収集 → 照合判定 → 訂正 → 実在チェック → 合成。工程ごとに向いているエージェントの使い方が違う、という地図の話だった。題材は同じ AInic ステーブルコイン編の325件の裏取りで、あちらは 何を順に確かめるか の軸で書いている。本記事はそれを subagent をどう並べるか の軸で見直したものだ。同じ仕事を、業務分解の地図と実装の境界の2つの軸でセットにする。
地図はそれで読める。だが実装に入った瞬間、別の壁にぶつかる。原稿が長い。 解説マンガ1話の確定原稿は約3万字、検証できる主張は325件、引用される出典は143本になった。これを1つの会話に押し込むと、コンテキストが膨れて注意が散漫になり、件数や粒度が安定しない。再現できないものはパイプラインと呼べない。
この記事は、そこを実装側から書く。主張325件をひとつのコンテキストに入れずに裏取りする ために、subagent を3段に並べた。各段は前段の出力を1桁ずつ圧縮して次段に渡す。fan-out の幅ではなく 段で畳む ことで、長い原稿が扱えるようになった。最後に置く「訂正」のループだけは、subagent ではなく人間がセッションで指示する——その境界の引き方の話だ。
全体像:原稿 → リスト → チェックリスト → 報告
組んだものはこうなる。
入口は確定原稿、出口は判定済みの出典付き報告。間の3段は、すべてサブエージェントで分担する。ただし、報告から実際の訂正に進むところだけは、subagent に閉じさせない。 ここは別節で書く。1段ずつ何をしているかを開けていく。
各段の節の終わりに、その段の subagent に渡したプロンプトの骨格 を載せる。実プロンプトの原文ではなく、出力スキーマ・粒度・禁止事項から逆算した 再現プロンプト だ。読者が自分のドメインで組むときの叩き台として使える形を意識した。
前回の7工程は、この4段に畳まれる
前回の地図 の7工程と、今回の4段(3段+人間ゲート)は1対1ではない。工程の名前で並べると7つだが、subagent の境界で切ると束ねられたり並走したりする。
| 前回(セッション編)7工程 | 今回(パイプライン編)の位置 |
|---|---|
| 1. 主張の抽出 | Stage 1(分担リストアップ) |
| 2. 分類・優先度付け(Routing) | Stage 1 のスキーマで吸収(部の境界=そのまま Routing) |
| 3. 証拠収集(Parallelization) | Stage 3 前半(候補 URL 取得) |
| 4. 照合・判定(Evaluator) | Stage 3 本体(直接関連性チェック) |
| 5. 訂正(Optimizer) | Stage 4(4ラベル仕分け)→ 人間ゲート |
| 6. 出典の実在チェック | Stage 3 と並走(curl・決定論) |
| 7. 合成 | 集約・出典ページ反映(決定論) |
Stage 2(チェックリスト生成)はセッション編に対応する工程がない——あちらは「並列単位」を暗黙に主張で取っていたが、ここでは並列単位を1段使って明示している。逆に、セッション編で独立工程だった Routing と実在チェックは、こちらでは Stage 1 や Stage 3 に吸収されて表に出てこない。地図と実装の差 はここに現れる。
Stage 1:分担してリストアップ ── 散文を構造に畳む
最初の subagent 群がやるのは、原稿から検証可能な主張を抜き出してリスト化する ことだ。今回はプロローグ+第1〜6部の7セクション構成だったので、subagent を7体立てて、1体に1セクションだけ読ませた。各セクションの原稿は別ファイル(selected_promptize_partN.md)になっているので、分担の境界は元データの境界をそのまま使える。
ここで先に2つ決めておく。リストの行のスキーマ と、1主張1行の粒度 だ。
ID | 主張本文 | カテゴリ(年代/金額/人物/条文/技術仕様/事件) | 候補ソース
スキーマを最初に固めずに走らせると、6体が違う形で返してくる。次段で集約しようとした瞬間に詰む。「全部ていねいに抜き出して」と頼まない——件数と粒度がブレるからだ。粒度は 「これ1つで判定できる単位か?」 で揃える。「2017年にステーブルコイン X が1ドルを割った」は1主張。「2017年と2018年にそれぞれ起きた」は2主張に割る。割った時点で並列検証の単位が決まる。
それと、subagent 同士を会話させない。 1体は1部だけを見て、他の部の主張を知らないまま自分の範囲を出す。「全部の原稿を1人に読ませる」のと逆向きの設計だ。なぜそうするかというと、全部読ませた1体は「重要そうな主張」を勝手に取捨選択しはじめる。網羅性が壊れる。盲目な6体に分担させたほうが、漏らさず構造化される。
1段目の出力は、325行の構造化リストになる(プロローグ4・第1部34・第2部49・第3部94・第4部42・第5部56・第6部46)。3万字の散文が、機械が読める形に1桁圧縮された状態だ。第3部だけが突出して密なので、後段の優先度づけはここの分布を見て決まる。
# Stage 1 プロンプト(再現)
あなたはファクトチェックの抽出担当です。次の原稿(第{N}部)から、
検証可能な事実主張を1件ずつ抜き出してリスト化してください。
# 出力スキーマ(1行1主張)
ID | 主張本文 | カテゴリ | 候補ソース
- ID: `P{N}-{連番3桁}`(例: P3-042)
- カテゴリ: 年代 / 金額 / 人物 / 条文 / 技術仕様 / 事件 から1つ
- 候補ソース: 既知の URL または書誌(無ければ「未指定」)
# 粒度
「これ1つで判定できる単位」で割る。一文に複数事象が混ざっていれば分ける。
# 禁止
- 重要そう/軽そうで取捨選択しない(全部抜く)
- 他セクションの知識を持ち込まない
- 前置きや所感を返さない(リスト本体のみ)Stage 2:リストからチェックリスト ── 並列単位に分解する
2段目の subagent は、主張リストを受け取って チェックリスト に変換する。チェックリストとは、その主張を支えるために(あるいは倒すために)何を確かめる必要があるか を項目に分解したものだ。
主張: 2017年、ステーブルコイン X が1ドルを割った
→ チェックリスト:
① 事件の日付(年月日)が正しいか
② 1ドル割れの最安値が公的に記録されているか
③ 同種事件(別の年・別の銘柄)との取り違えがないか
④ 一次報道または市場データに到達できるか
ここでも subagent は数十主張に1体ずつ割り当てる。1リストを1段目の出力単位(部)と同じ区切りで分けて、それぞれにチェックリスト生成役を当てる。
設計上のポイントは2つある。
ひとつは、主張を抽出した subagent と、チェックリストを作る subagent を分けている ことだ。同一の subagent に「抜き出して、検証項目も書いて」と頼まない。抽出担当が自分でチェックリストを書くと、抽出時に見えた範囲に検証項目が引きずられる。「私はこういう根拠でこの主張を抜いた、だからこういう項目で検証すれば足りる」というバイアスが入る。別の人格に渡す ことで、抽出側が無自覚に省いた論点(例:その事件と同年に別の銘柄でも起きていないか)を持ち込ませる。
もうひとつは、ここで次段の並列単位が決まる こと。チェックリストの各項目が、3段目で独立に1 subagent に渡される最小単位になる。だから「①と②をまとめて検証」と書かない。粒度が混ざった項目は、独立並列に乗らない。
2段目の出力は、325主張 × 平均3〜4項目 ≒ 1000項目のチェックリストになる。リストが増えるように見えるが、1項目が一意のソース URL を期待する形 になっているので、次段の検証単位としては圧縮されている。
# Stage 2 プロンプト(再現)
あなたはファクトチェックの検証設計担当です。次の主張リストの各項目に対して、
その主張を支える(または倒す)ために確かめるべき検証項目を分解してください。
# 入力(1主張ぶん)
{ID, 主張本文, カテゴリ, 候補ソース}
# 出力(1主張に対して 3〜5 項目)
主張ID: P3-042
チェック項目:
① 主張が含む特定要素(年・金額・銘柄・条文番号など)を 1 項目で 1 要素
② 同種事件・別年・別銘柄との取り違えがないか
③ 一次/準一次ソースへの到達経路(検索キー候補つき)
# 禁止
- 「全体が正しいか」「総合的に判断」のような粒度の項目を作らない
- 主張本文を書き換えない(検証項目だけ設計する)Stage 3:実在 + 直接関連性 ── verifier を verify する
3段目で初めて Web に当たる。各チェック項目に独立した subagent を当てて、URL を取りに行き、その URL の内容が主張を直接サポートするかを判定する。 ここが一番細かく並列化される。
ここで気をつけているのは、「実在チェック」と「内容チェック」を別物として扱う ことだ。
URL の生死は LLM に判定させない。curl -sIL -o /dev/null -w "%{http_code}" で叩く。これを LLM の判断に混ぜると、「もっともらしい URL」を生きていることにしてしまう。実際、今回の出典143本を後から一括検査したら、判決 PDF 3本が404で死んでいた。主張の判定は正しかったのに、出典 URL が消えていた——courts.go.jp が URL 構造を変えていて、旧形式の /app/files/hanrei_jp/... が全滅していたのだ。事件の同一性は内容判定で取れていたから、人間が読んでも見逃す。決定論で叩いて初めて落ちた。
その上で、生きていた URL に対して別の subagent が 内容の直接関連性 を判定する。ここが肝で、「同じトピックの URL」は素通りさせない。
主張: 2017年、ステーブルコイン X が1ドルを割った
URL: ステーブルコイン全般の解説記事(2026 年版)
→ ❌ 直接関連性なし
理由: URL は「ステーブルコインとは何か」を解説しているが、
2017年の X の1ドル割れには言及していない。
主張のトピックを含むが、主張を支えてはいない。
トピックが同じでも、主張の 特定の要素(年・銘柄・金額・条文番号)を直接含まない URL は弾く。これを LLM の自由判定に任せると、「関連していそう」で素通りする。だから判定スキーマで 「主張のどの要素を、URL のどの段落が支えているか」を明示させる 。要素を1つも引けない URL は ❌ で帰る。
3段目の出力は、項目ごとの判定(✓/△/⚠/❓/❌)+確信度+根拠抜粋+必要なら修正提案、の構造化レコードになる。1000項目を独立並列で回しても、各 subagent が見るコンテキストは「1主張+数本の候補 URL」だけなので、長くなりすぎない。
# Stage 3 プロンプト(再現)
あなたはファクトチェックの検証担当です。次のチェック項目について、
Web で一次/準一次ソースに当たり、内容が主張の特定要素を直接サポートするかを判定してください。
# 入力
{主張ID, 主張本文, チェック項目}
# 出力スキーマ(JSON)
{
judgment: "✓|△|⚠|❓|❌",
confidence: "高|中|低",
supports: { element: "主張のどの要素を支えるか", excerpt: "その要素を支える抜粋" }, // 必須
source_url: "...",
correction_suggestion: "△/❌ のとき"
}
# 判定原則
- supports.element に「主張のどの要素を支えるか」を具体に書けないなら ❌
- トピックが同じだけの URL は ❌(孫引きを通さない)
- 推測で confidence を上げない。低確信は「低」で残す訂正の判定だけ、人間がセッションで指示する
ここまでの3段はすべて subagent に閉じる。だが、「報告」から「実際の訂正」に進む工程は、subagent ではなく人間が会話セッションで指示した 。前回の地図でいう Optimizer に当たるところを、ループに自動化せず、人間の判断ゲートにしてある。
理由は2つある。
ひとつは、判定の分布が「ほぼ正しいが細部を直す」型だったこと。今回の内訳は ✓ 272 / △ 30 / ⚠ 9 / ❓ 4 / ❌ 2。骨格の誤りは2件、大半は例示・引用・単純化の精度だった。全自動の Optimizer ループは、こういう分布だと過剰修正に振れる。 「念のため言い回しを和らげる」「断定を弱める」を機械が連発し、原稿の骨が痩せていく。どこまで直してどこで止めるかは、原稿の声を持っている書き手にしか決められない。
もうひとつは、本編マンガの誤りを 直さない判断もあり得た ことだ。実際、今回は本編側は修正せず、出典ページ側で正しい記述を載せる構成を取った。修正案を持つ subagent には、こういう「載せ方の決定」はできない。AI が指摘を足し、人間がセッションでどれをどう適用するかを引いて確定する——前回 翻訳QAパイプライン で書いた「足し算 AI/引き算人間」の配置が、ここでも同じ形で出てくる。
人間が判断しやすいように、Optimizer 側で 4 段階に仕分ける
ただ「人間に渡す」と言っても、325件×複数の検証項目を上から流し読みさせるのは現実的でない。だから、subagent パイプラインを止めずに、Optimizer 用の優先度ラベルを 4 段階に整理してから人間に渡す 。Stage 3 の severity(✓/△/⚠/❓/❌)とは別軸で、「人間が何を決めるか」の粒度で割り直したラベル だ。
| ラベル | 元の severity | 人間が決めること |
|---|---|---|
| 要修正 | ❌ | 本編で直すか、出典ページで直すかを決める。直さない選択はしない |
| 正確性 | △(重要) | 直す方向は同じ。文言だけ採否を確認 |
| 任意 | △(軽微) | 直すかどうかも含めて判断(断定の緩和・言い換え系) |
| 見送り/注記 | ⚠ / ❓ | 直さず、出典ページに注記を添える/触らない |
何が嬉しいかというと、人間の判断モードが各ラベルで一定になる ことだ。要修正だけを束ねて見れば「どこで直すか」だけを連続で決めればよく、任意だけを束ねて見れば「直す/放置」の二択を高速で回せる。判断の文脈が混ざらない。
# Stage 4 プロンプト(再現)— Optimizer 用の4ラベル仕分け
あなたは Optimizer の前段、人間への提示準備担当です。
受け取った検証報告を、人間が判断しやすいように 4 段階に再分類してください。
# 入力
Stage 3 の判定レコード(複数件)
# 出力(各レコードに付与)
{
label: "要修正|正確性|任意|見送り",
decision_for_human: "人間が何を決めるかを1文(例: 本編で直すか出典ページで直すか)",
ready_text: "採用時にそのまま OVERRIDES に入る修正後テキスト"
}
# 4 ラベル定義
- 要修正: ❌。骨格の誤り。直さない選択肢なし
- 正確性: △ で数値・年代・呼称が事実と食い違う。直す方向は決まっている
- 任意: △ で断定の緩和・言い換え。直さなくても破綻しない
- 見送り: ⚠/❓。直さず注記を添えるか、触らない
# 並べ替え
ラベル順 → 影響範囲(本編 > 出典ページ)→ 部の登場順セッション中の手順はこれだけだ:
- Stage 3 の報告を、上の4ラベルでチャンクに分けて提示する(subagent 側でラベル付け+並べ替え)
- 要修正 チャンクから順に、人間が「どこで直すか」を口頭指示
- 正確性/任意 チャンクは採否を流して判断
- 採用した訂正は、生成スクリプトの 上書きマップ(
OVERRIDES)に追記して再生成に残す
最後の上書きマップが効くのは、元データを再生成してもページ側で訂正が残るためだ。訂正を本文や出力 HTML に直接書き込まない。 元のチェック結果ファイルを直し、上書きマップで衝突を解消し、生成スクリプトでページを作り直す——この経路にしないと、再生成1回で人間の判断が消える。Optimizer を人間に置くからこそ、判断モードを揃えるラベル付け と 判断をデータに残す経路 の両方をセットで用意する必要がある。
段ごとに何を圧縮しているか
3段が何をしているかを抽象に落とすと、こうなる。
各段で 次段に渡す材料の体積が変わり、形式も変わっている 。散文→構造化リスト→独立項目→根拠付き判定。後段ほど抽象度は上がり、見るべきコンテキストは狭くなる。fan-out の幅で殴るのではなく、段で形を畳む ことで、長い原稿全体を一望せずに済むようにしている。
ひとつのサブエージェントに「原稿を全部読んで全部検証して」と頼むと、コンテキストが膨れて注意が散漫になり、件数の網羅も判定の精度も落ちる。段で畳む設計は、コンテキスト管理の話だ。 並列化で速くしているのは結果論で、本来の効用は「各 subagent が見るべき範囲を、人間が手で切り分けて渡している」ことにある。
段の境界で踏んだ罠
実装で踏んだ落とし穴を3つだけ書いておく。
1. リストのスキーマを最初に固めずに走らせた回。 7体に「主張をリストアップして」とだけ頼んだら、ID の付け方も、カテゴリの粒度も、候補ソースの書き方も全部バラバラで返ってきた。集約しようとしたら3割が同一主張の重複だった。Stage 1 のスキーマは、走らせる前にプロンプトで固定する。 「ID は P3-042 形式、カテゴリは {年代, 金額, 人物, 条文, 技術仕様, 事件} の6択、候補ソースは URL または書誌情報」。これだけ書けば再現できる。
2. チェックリストを抽出担当の subagent に書かせた回。 「主張を抜き出して、ついでに検証項目も書いて」とまとめたら、検証項目が抽出時の文脈に張り付いた。主張の 特定の要素 を独立に検証する形にならず、「この主張全体が正しいか確かめる」みたいな粒度の項目が並んだ。3段目の独立並列に乗らない。Stage 1 と Stage 2 を別 subagent に分ける のは、速度のためではなく粒度のための設計だ。
3. 関連性を「もっともらしさ」で判定させた回。 3段目のプロンプトを「URL の内容が主張に関連しているか答えて」とだけ書いたら、同じトピックの記事を片端から ✓ にしてきた。スキーマを {relevant: boolean} から {relevant: boolean, supports: { element: string, excerpt: string }} に変えて、「主張のどの要素を、どの抜粋が支えるか」を必ず引かせる ようにした。要素を1つも引けない URL は relevant=false で帰るようになり、孫引きの撃退率が上がった。
部品表に載せ直す
前々回の「借りる部品と自作部品」 の語彙にそのまま乗る。
| 工程 | 実装 | 部品表での位置 |
|---|---|---|
| 原稿の分担 | プロローグ+6部の 7 セクションに切って 1 subagent ずつ | 自作:決定論的なチャンク分け |
| 主張抽出 | subagent fan-out + スキーマ固定 | 借りる:subagent + schema 契約 |
| チェックリスト生成 | 別 subagent で粒度分解 | 借りる:subagent(人格分離) |
| 候補 URL 取得 | WebSearch / WebFetch 並列 | 借りる:並列ツール |
| 実在チェック | curl で HTTP ステータス | 自作:決定論ゲート |
| 直接関連性チェック | subagent + 引用要素を必須化 | 借りる:subagent + schema 契約 |
| Optimizer 用ラベル付け | subagent で4段階(要修正/正確性/任意/見送り)に再分類 | 借りる:subagent + 自作:判断モードを揃えるラベル |
| 訂正の判定(Optimizer) | 人間がセッションでラベル別に採否を口頭指示 | オラクル=人間 |
| 訂正の保存 | 上書きマップ(OVERRIDES)に追記 | 自作:state 外部化 |
| 集約・出典ページ反映 | 生成スクリプトで TS データに | 自作:決定論的合成 |
派手な自律オーケストレーションは、ここでも前に出てこない。subagent を段で畳み、段の境界で形式を変え、検証可能なところは決定論に降ろし、確定権は人間に残す——これだけで325件は捌けた。
これは「マンガのファクトチェック」の話ではない
6つの設計判断を抜き出すと、どれもファクトチェック固有のものではない。
- 長い原稿を1コンテキストで処理させない(部・章・節で切って分担させる)
- 段で形式を変え、次段の粒度を直前段で決める(散文→リスト→チェックリスト→報告)
- 抽出担当と検証担当を別人格に分ける(自分の抽出範囲に検証を引きずらせない)
- 「もっともらしい」で判定させず、引用要素をスキーマで必須化する(孫引きの撃退)
- 実在チェックを LLM に渡さず独立パスで叩く(curl で生死、subagent で関連性、混ぜない)
- Optimizer ループに自動で閉じない(訂正は subagent で4ラベルに仕分けてから人間に渡し、採否は人間がセッションで指示、上書きマップに残して再生成で消えないようにする)
そのまま、判例リサーチの引用照合にも、論文レビューの参考文献検証にも、契約書ドラフトの条文整合チェックにも置き換わる骨格になる。題材が解説マンガ1話の325件だっただけで、見せているのは 前回の7工程の地図 を 長い原稿で動かすための、実装の境界の引き方 だ。
この3段パイプラインで仕上げた出典ページの実物は ステーブルコイン編 出典・裏取り にある。325件の判定と143本の出典が、開いて確かめられる。
姉妹記事:ファクトチェックの典型手順と、工程ごとのエージェントの使い分け(セッション編) — 同じ325件の裏取りを「何を順に確かめるか」の軸で書いた地図。先に読めば全体像が掴め、そのうえで本記事の「どう並べるか」が深く読める。