個人でWebサイトやアプリを作っています(屋号は sesebox です)。
いま、中学生向けの歴史クイズアプリを作っています(まだ開発中で、公開はしていません)。1つの単元ごとに「4択問題・年代のならべかえ・穴埋め・教科書の読み物」をそろえる形で、問題文はAI(Claude Code)に書かせています。
AIに問題を作らせると速いのですが、そのまま使うのは不安です。事実がまちがっていないか、答えが1つに決まるかを、だれかが確かめないといけません。
そこで、作ったAIとは別のAIに、答えを伏せた状態で解かせるという点検を入れました。約90単元・判定4,000件ほど回してみたら、思っていたのとはちがう種類のミスがたくさん見つかったので、その話を書きます。
やったこと:作る人と解く人を分ける
1単元を作る流れは、次の5段にしました。
| 段 | 担当 | やること |
|---|---|---|
| ① | 作り手 | 4択・ならべかえ・教科書・穴埋めを作る |
| ② | (スクリプト) | 問題ファイルから答えを消した版を作る |
| ③ | 解き手 | 答えを伏せた版だけを見て解き、問題点を書く |
| ④ | 確かめ手 | 教科書と穴埋めの事実を、出典で1つずつ確かめる |
| ⑤ | 直し手 | ③④の指摘を反映して完成版にする |
ポイントは③です。解き手には答えが入ったファイルを開かせません。人間のテストと同じで、答えを知っている人が解いても点検になりません。
各段は Claude Code のサブエージェント(親のAIから呼び出す、別の会話のAI)に任せています。サブエージェント同士は会話を共有しないので、作り手の考えが解き手にうつらないのも都合がよいところです。
答えを伏せるスクリプト
②は Python の短いスクリプトです。正解と誤答をひとまとめにしてシャッフルし、どれが正解かの情報を消します。ならべかえ問題は年を消して、出来事の順番を混ぜます。
import json, random, sys
from pathlib import Path
for uid in sys.argv[1:]:
random.seed(uid) # 同じ単元なら毎回同じ並びになるように
g = json.loads(Path(f"gen_{uid}.json").read_text(encoding="utf-8"))
qs = []
for i, q in enumerate(g["questions"]):
choices = [q["answer"]] + q["distractors"]
random.shuffle(choices)
qs.append({"id": f"g1-{i}", "stem": q["stem"], "choices": choices})
orders = []
for i, o in enumerate(g.get("orders", [])):
events = [e["text"] for e in o["events"]] # 年(year)は捨てる
random.shuffle(events)
orders.append({"id": f"o-{i}", "title": o["title"], "events": events})
Path(f"blind_{uid}.json").write_text(
json.dumps({"unitId": uid, "questions": qs, "orders": orders},
ensure_ascii=False, indent=1),
encoding="utf-8")
random.seed(uid) を入れているのは、やり直したときに選択肢の並びが毎回変わると、前回の結果と見比べにくいからです。
解き手には、1問ずつ「自分の答え」と「OK/要修正/削除」の判定、問題点、直し案、確かめた出典のURLを返してもらいます。
結果:解き手はほぼ全問正解だった
まず意外だったのがここです。
4択3,358問のうち、解き手の答えが正解と食いちがったのは2問だけでした。しかもその2問は、答えの書き方(かっこの全角・半角など)のちがいで、中身は合っていました。
つまり「解けなかった問題=まちがった問題」という見つけ方は、ほとんど役に立ちませんでした。AIは歴史の知識が十分にあるので、多少おかしな問題でも正解を当ててしまいます。
役に立ったのは、解いたあとに書いてもらった「問題点」のほうでした。
| 判定 | 件数 |
|---|---|
| OK | 3,157 |
| 要修正 | 753 |
| 削除 | 30 |
(4択とならべかえを合わせた件数。2件は判定の書き方が決まりの外だったので除いています)
約5件に1件に「直したほうがいい」という指摘がつきました。
多かった指摘①:答えが「別の問題」に書いてある
いちばん多くて、作り手のAIが自分では気づけなかったのがこれです。
たとえば室町時代の単元で、
- 「6代将軍はだれか」(答え:足利義教)という問題があるのに
- 別の問題の文が「6代将軍足利義教が行ったこととして…」で始まっていた
ということが起きていました。1問ずつ見ればどちらも正しい問題ですが、同じ回に出すと片方の答えがもう片方に書いてあります。
琉球の単元では、「15世紀に沖縄島をまとめた国は何か」(答え:琉球王国)の答えが、ほかの10問ほどの問題文にそのまま出ていました。テーマが1つにまとまった単元ほど起きやすいです。
作り手は1問ずつ書いていくので、「この語はもう別の問題文に出したか」までは見ていません。一方、解き手は単元の問題を全部ながめて解くので、「あ、これさっきの問題に書いてあった」と気づけます。人間がテストを解くときとまったく同じ気づき方です。
直し方は2つにしました。
- 問題文を書きかえて、答えの語を消す
- 書きかえると不自然になるものは、「同じ回に出さない組」として記録する
2つ目のために、完成版のファイルに conflicts(いっしょに出すと答えがばれる問題の組)という項目を作りました。アプリ側で出題を選ぶときに、この組がそろわないようにします。最終的に2,710組になりました。思っていたよりずっと多かったです。
多かった指摘②:見た目で答えが分かる
次に多かったのが、知識がなくても形で当たってしまう問題です。
- 正解だけが長い(「スペインかぜ」の由来を聞く問題で、正解が約40字・誤答が27〜33字)
- 誤答が極端(「すべての取り引きが米で行われた」「銭の使用が幕府によって禁止された」)
- 問題文と同じ字が入っている選択肢が1つだけ(「茶の産地を飲み分ける遊び」を聞いて、選択肢で「茶」の字があるのが「闘茶」だけ)
- 問題文に答えの一部がそのまま入っている(「寺や神社の門前に…できた町」→答え「門前町」)
テストを作ったことのある人なら「あるある」だと思いますが、AIが作ると、これが本当によく出ます。正しい文を丁寧に書こうとすると正解だけ長くなり、誤答は「明らかにちがうもの」を選びがちです。
多かった指摘③:事実の言い過ぎ
数は①②より少ないですが、いちばん大事なのがこれです。
- 龍安寺の石庭を「白い砂と15個の石だけ」と書いていた(実際は苔もある)
- 「守護代から戦国大名になった例」の答えを朝倉氏にしていた(辞典では、朝倉氏は守護の重臣で、守護代は別の家)
- 1単元に1つ、出典で裏づけが取れない言い切りが入っていることがある
解き手には「出典(コトバンクなどの辞典)を実際に開いて確かめる」ことを決まりにしていて、確かめられなかったものは「確かめられなかった」と書いてもらいます。この「分からなかったことを分からないと書かせる」のが、ミスを見つけるうえでかなり効きました。
やってみて分かったこと
- 「解けたかどうか」では点検にならない。AIは知識があるので、多少おかしな問題でも当ててしまう。役に立つのは「解きながら気づいた問題点」を書かせること
- 答えのもれは、問題を1問ずつ見ていても見つからない。単元の問題を全部見る人が要る。作る人とは別の担当にすると、ちょうどその役になる
- 指摘の中身は単元が変わってもだいたい同じなので、よく出る指摘は、次の単元の作り手への指示に先に入れるようにしました(「誤答は同じ種類で長さをそろえる」「答えの字を問題文に入れない」など)
- ただ、指摘の割合は作った順に 14.7% → 25.9% → 20.2% で、きれいには減りませんでした。途中から解き手にも「出典を1問ずつ開く」ことを強めたので、見つける側の目も厳しくなっていて、単純には比べられません。指示に入れた効果がどれくらいあったかは、まだ分かっていません
- 解き手の「問題点」も100%正しいわけではないので、直し手がもう一度出典を見て判断する段は残しています
AIに問題を作らせるときは「作る役」と「解く役」を分けるだけで、人間が1問ずつ見るよりかなり手前でミスを減らせました。同じようにAIでクイズや問題集を作っている人の参考になればうれしいです。
ほかに作っているものは sesebox にまとめています。