出典接地の被覆率を実データで測り、判定ロジックを調整した

  • #LLM
  • #出典接地
  • #ファクトチェック
  • #自動化
  • #テスト駆動
出典接地の被覆率を実データで測り、判定ロジックを調整した

今回やったこと / この記事について

LLMを用いた記事の追記・改稿パイプラインにおいて、元記事の文脈を無視した「一般論の混入」や「固有名詞の薄い振りかけ」を自動検知するため、出典接地の被覆率(Coverage)測定と判定ロジックの多層化を実装した。

当初机上で設定していた閾値を、実環境の正例データ10件(延べ30回)で測定し直し、正例の実測分布と想定される不正例の数値を突き合わせて判定ロジックを再調整した。本稿では、なぜLLMによる直接判定だけではすり抜けが発生するのか、モデルに判断をさせず「固有要素の列挙」に限定させる設計、そして実測に基づく閾値チューニングの経緯を記録する。

「有無の検査」をすり抜ける一般論と振りかけ

自動化パイプラインにおける公開前検査(リンクの実在確認、画像の有無、HTTPステータス検査など)は、基本的にすべて「対象が存在するか否か」の検査である。しかし、この検査形式では「データは実在するが、中身が的外れである」という異常を構造的に防げない。

実際に発生した例として、キャンプに関する具体的な体験記録(特定の調味料や道具、野鳥の飛来などの固有情報)に対して追記を生成させた際、元記事の要素を1文字も反映せずに「一般的なキャンプの心得」を出力し、出典として英語版Wikipediaと自分自身の記事URLを貼るという動作があった。URL自体は実在するため、従来の存在チェックゲートをすべて満点で通過してしまった。

さらに検知を難しくするのが、「完全な一般論」ではなく「一般論の中に元記事の単語を2〜3個だけ散らした文章(振りかけ文章)」である。これを6種類のLLMに「元記事に基づいているか」判定させたところ、以下の結果となった。

モデル判定結果確信度
Claude Opus 4.6 (thinking)不合格(false)high(正解)
Claude Sonnet 4.6合格(true)medium
GPT-OSS 120B合格(true)high
Gemini 3.7 Flash合格(true)high
Gemini 3.1 Pro合格(true)high
特定用途向け派生モデル合格(true)high

正解したのは深い推論を行う最上位モデルだけであり、他の5モデルは「自信満々に合格(true / high)」と判定した。「安価なモデルを1段目に置き、確信度が低い場合だけ上位モデルへエスカレーションする」という典型的な階層化設計は、下位モデルが迷わず合格を出してしまうため機能しない。

ただし、今回の検証では「弱いモデルが誤って不合格にする例」は6モデル×全テストケースで0件であった。今回の範囲では誤りが「甘く通す」方向に出ていたため、この傾向を前提に判定の枠組み自体を再構築した。

モデルには判断させず「列挙」させる:向きの違う2本の機械層

判定の甘いモデルに「合格か不合格か」を判断させると失敗する。そこで、第1層の機械処理では、LLM(Gemini Flash等)には判断を一切行わせず、「元記事に含まれる固有要素(具体的な名詞・数値・固有名詞)の抽出・列挙」のみを担当させる設計とした。抽出されたリストと追記本文の突き合わせは、スクリプト側の決定的な文字列照合で行う。

さらに、照合の方向性を2本用意した。

  1. ①-a 被覆率(元記事 → 追記): 元記事から列挙された固有要素のうち、追記が何割を拾っているかを測定する。
  2. ①-b 接地率(追記 → 元記事): 追記の各文から名詞を抽出し、元記事に実在するかを測定する。

この「向きの違い」が決定的な役割を果たす。従来の接地率(①-b)だけでは、「自分の文のうち何割が元記事の語を含んでいるか」を計算するため、わずか2〜3個の固有名詞を全体の文へ薄く散らすだけでスコアを稼げてしまう(振りかけ文章が15%で閾値を通過していた)。

一方、被覆率(①-a)は「元記事の固有要素をどれだけ網羅したか」を測るため、元記事の固有要素を一定以上網羅しない限りスコアが上がりにくい。同じ振りかけ文章を測定したところ、被覆率は5%に留まり、明らかな不合格ではなく境界域として捕捉できた。

テストケース別の測定結果:
- ケースA(読んだ正例): 被覆率 50% / 接地率 38%
- ケースB(読まず一般論): 被覆率 0% / 接地率 0%
- ケースC(別記事と食い違い): 被覆率 0% / 接地率 0%
- ケースD(固有名詞の振りかけ): 被覆率 5% / 接地率 15%

実データ10件の実測と閾値の再調整

テスト用の固定データ(fixture)だけでなく、実際に運用環境で生成された正例10件を用いて、それぞれ3回ずつ(n=30)被覆率を実測した。その結果、机上の想定と現実の乖離が浮き彫りになった。

  • 机上の想定: まともな追記であれば、元記事の固有要素を最低でも35%以上は拾うはずである。
  • 実測値の分布: 最小値 10% / 中央値 25% / 最大値 65%

実際の記事追記パイプラインの出力では、固有要素20件のうち2件(10%)を拾って的確に要約している正当なケースが存在した。もし閾値を35%に設定していれば、正常な記事の大部分が機械層で誤って切り落とされることになる。

一方で、振りかけ文章(ケースD)のヒット数は1件(5%)であった。正例の下限(10%・2件)と不正例(5%・1件)の差は、実質わずか1件の差しかない。

この実測を踏まえ、判定ロジックを以下のように調整した。

  1. MIN_COVERAGE を 0.05(5%)に設定: 明らかな一般論(0%)や無関係な記事(0%)を機械層で確実に排除しつつ、正当な記事を誤認で落とさない下限を確保する。
  2. 境界域の迎撃を第2層(Opus)へ一本化: 5%〜10%の境界線上にある疑わしいケースは、機械層で無理に白黒をつけず、第2層の高度な推論モデルによる最終判定に委ねる。
  3. 書式起因の接地率低下への配慮: 生成モデルが固有情報を表形式(Markdown Table)の1セルに集約する癖を持つ場合、文単位の接地率が低下する現象を確認したため、被覆率チェックによる防御を前提に、接地率の閾値(MIN_RATIO)も0.15から0.05へ緩和した。

やってみてわかったこと:判定AIのバイアスを前提にした多層防御

判定用LLMの「甘く通したがる」というバイアスは、プロンプトの工夫だけで解消するのは困難である。しかし、「モデルには列挙という作業だけを任せ、数値化と判定はコード側で行う」「通したがる側が『落とす』と言った時だけ強い証拠として採用する」というアーキテクチャを組むことで、実用に向けた検査基盤の足がかりを構築できた。

実環境における通しテストでは、明らかな一般論(0%)を第1層の低コスト枠(Gemini Flash)で足切りし、判定の難しい境界域だけを高価な推論モデル(Opus)へ進めることで、呼び出し回数を抑えられた。

試した環境は Python 3.12、および構築した出典接地に関する回帰テストスイートである。

更新履歴

  • 2026-09-19: 初稿。出典接地における被覆率測定と実データに基づく判定閾値の調整を記録。

訂正履歴

  • なし。