PRODUCT NOTE / 検証

大量の文章と問題文をどうテストするか

クイズと試験問題が 5,855 件、書籍の原稿が 4 冊。どちらも「ビルドが通る」ことと「中身が正しい」ことが別で、しかも全部を読み直すことはできません。


このノートは、その状態で何を機械に任せ、何を人が読み、事実をどう確かめているかの現時点での解です。うまくいかなかった決め方も残しています。

対象問題・教材文 5,855 件(クイズ 2,797 問 / 試験問題・教科書の節・用語 1,619 件 / 音声つき教材文 1,439 文)と、書籍 4 冊ぶんの原稿
道具形式テスト(アプリごと)/ 問題の生成スクリプト / 送り読みのレビュー画面 3 本 / 原稿の検査 4 本(禁止語・日本語校正・実物の露出・数値の正本)/ 音読
この解の要点機械に合否を出させない。機械は形式を落とし、残りを怪しい順に並べるところまで。合否は人が持ち、事実は出典が持つ
このノートの範囲判断だけ。社内の事例は役割語に置き換えてあり、固有名・実テーブル名・課題番号は載せていない
最終更新2026-09-12
関連Dotpile(クイズ) ・ Certly(試験対策) ・ 社内 AI エージェントの設計
TL;DR

ひとことで

テストは形式までしか見られない。ID の重複も、誤答が 3 つあることも、正解が誤答に混ざっていないことも機械で分かるが、「その答えは本当に正しいか」「解説はそれを説明できているか」は人が読むしかない。

そして 2,000 問を机の前で読み切ることはない。だから機械の仕事を「合否を出すこと」から「読む順を作ること」へ変えた。

事実の側は出典・数値の正本・実行履歴との照合で持ち、人はその上を送り読みする。

Problem

全部は読み切れない、という前提から始める

問題を 100 問から 2,797 問へ、原稿を 1 章から 4 冊へ増やすと、検証の壊れ方が変わる。少ないうちは「全部読む」で成立していたものが、ある時点から誰も通しで読まなくなる。

読まれないまま増えるので、間違いは足された順に溜まっていく。

この状態で起きることが 3 つある。

Layers

4 つの層に分け、層ごとに「見られないもの」を書く

いちばん効いたのは道具ではなく、層を分けて、各層が見られないものを明記したことだった。書いていないと、次に読む人(と半年後の自分)が「検査が通っているから大丈夫」と読む。

層何を見るか道具この層では見られないもの
1. 機械で落とす形式・スキーマ・ID の重複・誤答の数・正解が誤答に混ざっていないか・数値の一致・日本語の質アプリごとの形式テスト、原稿の検査 4 本書いてあることが事実かどうか
2. 機械で並べる怪しさ。重み付きの札を立てて、読む順を作るレビューデータの生成(札は 16 種)合否。機械は札を立てるだけで、判定はしない
3. 人が読む日本語として読めるか、その誤答の理由に納得できるか送り読みのレビュー画面 3 本 / 原稿は目と耳全部は読めない。だから 2 で順番を作る
4. 出典が持つ数字と事実の出どころ、いつ時点のものか問題ごとの出典 URL、原稿の出典一覧、数値の正本出典そのものの誤り。取得日で腐りを見るしかない

3 層目の「耳」は原稿にだけ使う。目で読むと脳が補完するが、耳では補完が効かない。

読点のない長い文、同じ語尾の連続、主語の迷子が一発で引っかかる。章の Markdown から読み上げ用のテキストを起こして音声にし、倍速で流す。

コードブロックも表も URL もそのまま読ませてはいけない(5 分で聞くのをやめることになる)ので、音で意味を持つものだけ残す処理を挟んでいる。

Decision Log

主要な意思決定ログ

分担 テストが弾いているものは、レビューの札に入れない
DECISION

機械の検査と人の送り読みで、見る対象を重ねない。ID の重複・正解の数・誤答の一行の欠け・出典 0 件・章 ID の綴り違いは全部ビルドが落とす。

レビュー画面に出すのは「日本語として読めるか」「その誤答の理由に納得できるか」だけにした。

WHY / 仮説
両方が同じものを見ると、一覧が機械で分かることで埋まり、人にしか分からないものが沈む。機械で見つかる不備はもう全部ビルドが落としている、という状態を先に作ってから、残りを読む場所を用意した。
RESULT
3 アプリとも同じ分担になった。札の定義のコメントに「テストで既に弾いているものは入れない」と書いてあり、札を足すときの判断がそこで済む。
下限 問題数の厳密一致をテストに書かない
DECISION
問題データのテストは下限だけを見る(1 ジャンル 100 問以上、ジャンル × 難易度で 10 問以上)。「ちょうど N 問」は書かない。
WHY / 仮説
前に作った英語アプリは「ちょうど 393 問」と書いていて、問題を足すたびにテストが落ち、やがてテストごと放置された。壊れてほしいのは件数が変わったときではなく、遊べなくなる性質が壊れたときだった。
RESULT

572 問足したときも、テストは 1 行も直さずに通った。

かわりに「同じ問いが二度出てこない」「誤答すべてに一行がある」「正解が誤答の中に混ざっていない」「ID が 3 桁ゼロ埋め」など、壊れると遊べなくなる性質を全問について見ている(24 項目)。

閾値 怪しさの札は、出荷物に当てて件数を数えてから採る
DECISION
札は思いつきで足さない。実データに当ててみて、当たりが多すぎるものは落とす。閾値も実データの分布に合わせる。
WHY / 仮説

全体の 1 割を超えて当たる札は目印にならない。「解説が 20 字未満」は 1,512 問中 375 問に点いた。

別の教材では「別名に日本語」が 314 件中 209 件、「非公式の出典」が 496 件中 441 件に点いた(MDN や RFC を引くのが正しい場面だった)。

半分に札が付いた一覧は誰も見ない。

RESULT

3 つ落として、残した札に重みを付けた。英訳の欠けは重み 0 にしてある —— 496 件中 476 件が未訳なので、重みを持たせると怪しい順がそれだけで埋まって、読むべきものが沈む。

数えたい未訳は言語の絞り込みで見る。

指紋 直したら「要再確認」へ戻す。ID ではなく文言の指紋で見る
DECISION
OK / NG の判定は、その項目の文言のハッシュと一緒に保存する。文言が変わったら、その 1 件だけ「要再確認」に戻す。
WHY / 仮説

ID は中身を直しても変わらないので、ID だけ見ていると古い OK が残り続ける。逆に出荷物まるごとのハッシュで見ると、1 問直しただけで 356 問が要再確認に落ちる。

指紋は項目ごとに持つ必要がある。

RESULT

音声つきの教材では指紋を 2 本に分けた。音声のファイル名は本文のハッシュなので引き直しても名前が変わらない。

本文の指紋とは別に、音声の中身の指紋を持たせて、文面の直しと音声の引き直しがそれぞれ独立に「要再確認」を立てるようにした。

出荷物 レビューは原稿ではなく、端末に実際に出る文字列を読む
DECISION
レビュー画面に流すデータは、原稿の YAML ではなく整形を通したあとの出荷物から作る。
WHY / 仮説

強調記号の開き・閉じは前後の文字種で決まる。日本語は助詞がそのまま続くので、この規則に実際に当たる。

素朴な正規表現で畳むと、アプリでは記号のまま出る箇所がレビュー画面でだけ太字になり、壊れているものを壊れていないと報告してしまう。

RESULT
レビュー画面の自前の描画と、ビルド側の本物のパーサの判定を、出荷 1,619 件で全件突き合わせる検査を別に置いた。あわせて「記号が 1 つも残らないのに段落まるごとが太字になる」壊れ方が出荷物に 8 件見つかった。
生成 正解がデータで決まるものは、手で書かない
DECISION
都道府県の形・世界地図・国旗・統計の順位は、素材テーブルから機械で組む。生成した問題には「生成物である」という印を持たせ、手書きと区別する。
WHY / 仮説
正解が地図データそのものなら、手で書けば取り違えるだけで、機械で作れば必ず正しい。手で直しても次の実行で消えるので、直す場所を素材の側に固定する意味もある。
RESULT

乱数のシードを問題ごとに固定したので、2 回流しても差分が出ない。誤答は「同じ地方 → 同じ大陸 → 全体」の 3 段で近いものから引く(ありえない選択肢が混ざると消去法で解けてしまう)。

素材テーブル自身にも検査を置き、隣接が片方向だけ書かれている・順位と値の並びが食い違う・誤答を作れるだけの国が大陸に残っていないを弾く。

2 位や上位 3 件を問うのは、下位と十分離れているときだけにした(僅差だと統計の更新で正解が入れ替わり、問題が嘘になる)。

保留 判定を 2 値にしない
DECISION
判定は「未実施 / OK / 要確認 / 保留 / NG / 対象外」。迷ったら NG ではなく要確認に置く。
WHY / 仮説

2 値で持つと、分からないものが OK か NG のどちらかに紛れる。仕様が読めないものを憶測で NG にすると、直す側の工数が無駄になる。

判定する人に要るのは「NG を出す勇気」ではなく「保留する権利」だった。

RESULT
機械で正しさが保証できる生成ジャンルは、ボタン 1 つでまとめて「対象外」にできるようにした。読む対象が減ると、残りを読み切れる。
露出 禁止語の検査が 0 件でも落ちる。消せるのは名前で、残るのは構造
DECISION
原稿には、禁止語(名前)の検査とは別に「実物の露出」(構造)の検査を当てる。見るのは実ファイル名・行数・行番号・常駐設定・コメントの逐語引用など、置換では消えない側。
WHY / 仮説
1 冊は禁止語検査を 59 ファイル 0 件で通過したまま、出せなくなって欠番になった。実名は 1 つも残っていなかったが、構造が残っていた。匿名化は名前を消す作業であって、構造を消す作業ではない。
RESULT

件数の絶対値は可否と対応しない(教材コードを載せる本ほど多く鳴る)。使い方を 2 つに限った —— コードを載せないと決めた本では許可済みを除いて 0 に保つ、載せる本では鳴った箇所が教材用か実物かを人が 1 件ずつ判定する。

ここは機械には分からない。

Fact-check

事実をどう確かめるか

上の 8 つは、どれも事実かどうかは見ていない。「日本語として読める」「形式が正しい」と「書いてあることが本当」は別で、後者を機械で判定する方法は持っていない。持っているのは、後から追えるようにしておくための仕掛けだけ。

1. 出典を持たせる範囲を、先に決める

試験対策アプリでは出典 URL が無い問題は出荷しないことにして、ビルドが弾く。クイズアプリでは統計と実測値の 244 問にだけ付け、手書きジャンルには付けないと決めた。

小説や花の問題は常識で答えられるぶん、リンク切れの管理コストのほうが大きくなる。付けない範囲も決めておかないと、付いていない問題が「漏れ」なのか「そう決めた」のか分からなくなる。

きっかけは「埼玉県は 7 つの都県と接する」という解説を疑われたことだった。実際には正しかったが、そのとき「どこの数字か」を答えられなかった。

順位を問う問題は年で答えが入れ替わるので、出典が言えないものは検証も修正もできない。付けると決めた 244 問は、あわせて一次資料から機械で数字を抜き直し、手で書き写す工程を無くした。

官公庁の統計は商用利用できるが出典の記載が条件なので、この対応は有償化の下地にもなっている。

2. 出典は「いつ時点か」まで書く

書籍側では出典一覧を付録に置き、1 件ごとに次の欄を埋める。

欄意味
発表日出典側が公表した日
取得日実際に開いて数値を確認した日。ここが規律の本体
規模と手法n・対象・調査方法。これが書けない出典は使わない
限界出典自身が述べている適用範囲の限界

賞味期限の起点は発表日ではなく取得日になる。AI 関連の調査は 1 年で古びるので、取得日が古びた項目は改訂のたびに引き直す。

あわせて、採らなかった数字と、採らなかった理由を同じ付録に 1 節置いている。採った数字だけ並べると、選び方そのものが検証できない。

3. 同じ数字が 2 か所に出るなら、正本を 1 つ決める

書籍をまたいで出てくる実測値は、「値・意味・正本の本・掲載を許す本」の 4 列で 1 か所に持ち、機械で照合する(いま 21 件)。

ブレるのは値より意味のほうが多い —— 同じ 250.9 時間でも、受益者で割った数字と業務カテゴリで割った数字を取り違える。

だから「意味」の列を必ず書かせている。検査は「許していない本に出ていないか」と「正本の側に記載があるか」の両方を見る。

4. 「確認しました」は、書けば通る

社内で動かしている AI エージェントでの話(固有名は伏せる)。問い合わせへの回答に「■確認したこと: ○○を直接確認」と書かせ、点検役にレビューさせていたが、実際にはそのデータを一度も参照していない回答が本番に出た。

点検役が見ていたのは開示の有無だけで、開示の正しさは一切見ていない。

対策はプロンプトに注意書きを足すことではなく、根拠欄に書かれた参照先と、そのターンの実行履歴を機械で突き合わせることにした。履歴に無いものが 1 つでもあれば無条件で差し戻す。この照合を作るときに要った条件が 3 つある。

照合を作ったあとに再発した
  • 掛かっていない経路が 2 本あった。関門は「有るか」ではなく「経路ごとに有るか」で数える。
  • 履歴が空のターンが対象外だった。「履歴に無いものを探す」設計なので、何も調べずに断定したいちばんひどい状態だけが検査をすり抜けた。0 件のときの分岐を、設計と同時に決める。
  • 0 件 ≠ 存在しない。別の保存先を 1 つ以上潰すか、「この経路では見つかりませんでした」と確度つきで書くか、どちらかを課す。

5. LLM に確かめさせるときの型

採点役を立てるときは、材料を実際に実行したコマンドとその出力・差分そのものに限り、実装者の説明や意図を材料にしない。判断がつかない場合は FAIL にする(UNKNOWN は PASS ではない)。

書いた本人に採点させない。ループエンジニアリング実践ガイドに置いてある型で、これはいまのところ人が 1 件ずつ回していて、問題バンクに対してバッチでは動かしていない。

まとまった量で回したのは多言語の翻訳レビューだけで、言語ごとに敵対的に洗い出させて「高(学習内容として誤り・文字化け)/中(事実の不整合・用語不統一)/低(文体・体裁)」で分類させた。

全角プラス記号や波ダッシュの混入、「3 音節以上は more / most」と書きながら例が 2 音節、母音の列挙が五十音順になっている、といったものが出た。

凡例を先に決めておかないと、指摘の山だけが残って直す順が決まらない。

Traps

気づきにくい壊れ方

検証の側が壊れる 3 つ
  • 検証する道具のほうが間違っている。レビュー画面の描画がアプリとずれていると、壊れているものを壊れていないと報告する。道具の出力も突き合わせる対象に入れる。
  • レビュー用の乱数が偏る。「正解を隠して自分で解く」モードのために線形合同法を素で書いたら、下位ビットが落ちて正解が先頭に来る問題が 200 問中 0 件になった。
    32bit に直しても偏りは残り(510 / 248 / 358 / 396、均等なら 378)、出力を混ぜて返す方式に替えてカイ二乗 1.85 に収まった。
    偏った状態では、解いても検証にならない。
  • スキーマは、実行していなければ無いのと同じ。ある古いアプリには対話データの JSON Schema が置いてあるが、それを読んで検証するコードがリポジトリのどこにも無い。実データは ID の範囲もレベルの値も、書いてある定義から外れている。

もう 1 つ、新しい検査は一度わざと失敗させてから信用する。壊した状態で落ちることを見ていない検査は、通っていることが何の証拠にもならない。検査を足したときは「これでも見つからないもの」を同じ場所に書いておく。

Roadmap / Open Questions

これから(いま開いている穴)

このノートを書くために測り直して、自分の穴が 4 つ見つかった。直す前に書いておく。

References

関連

社内で運用しているシステムの事例は、社名・製品名・人名・テーブル名・課題番号を伏せ、役割語に置き換えて書いています。件数はいずれも公開時点の実測値で、数え方はアプリのリポジトリ側のスクリプトが持ちます。

記述は運用上の判断の整理であり、特定の構成を推奨するものではありません。