前回、内容の検証は前回との比較で見るのが実用的だと書きました。その比較の土台になるのが評価セットです。「入力」と「期待する性質」の組を並べたもので、 の中核になります。
この回で言いたいことはひとつです。立派な評価基盤を作る前に、まず30件作ったほうが早い。件数が少なくても、変更前後で同じ入力を通すという目的は達成できます。
1. 何を「正解」として持つか
最初に決めるのが、正解データの形式です。ここで理想的な回答文そのものを書こうとすると、たいてい行き詰まります。表現は無数にありますし、書くのに時間がかかり、保守もできません。
実務的なのは、含まれているべき要素と含まれてはいけない要素を書く形式です。表現の揺れを許しつつ、落ちてはいけない情報だけを押さえられます。
EVAL_CASES = [ { "id": "fee-001", "note": "更新料の単純な問い合わせ", "doc": "契約期間は2026年4月1日から1年間。更新料は33,000円。", "question": "更新料はいくらですか", "must_include": ["33,000", "33000"], # いずれか1つ含めばよい "must_not_include": ["無料", "不明"], "max_chars": 200, }, { "id": "fee-002", "note": "文書に書かれていない事項。答えられないと言えるか", "doc": "契約期間は2026年4月1日から1年間。", "question": "解約手数料はいくらですか", "must_include": ["記載がありません", "わかりません", "不明"], "must_not_include": ["円"], # 金額を作り話しないこと "max_chars": 200, },]2件目が重要です。答えられないと言えるかを見ています。評価セットを作ると、つい「正しく答えられるか」ばかり並べたくなりますが、実務で損害につながるのは知らないことを答えてしまうケースのほうです。答えられなかった場合、利用者は不満を持ちますが判断は誤りません。一方、もっともらしい誤答はそのまま業務に流れます。この非対称が、評価セットの設計をそのまま左右します。
情報が無いときに無いと言えるかは、 対策の中核です。文書から意図的に情報を削った入力を作り、答えられないことを確認する。これは評価セットでしか検証しにくい性質です。
2. 判定は単純な関数で足りる
上の形式なら、判定は数行で書けます。凝った仕組みは要りません。
def judge(case, answer_text): """違反理由の一覧を返す。空なら合格。""" problems = []
if case["must_include"] and not any(k in answer_text for k in case["must_include"]): problems.append("必要な要素が含まれていない")
for ng in case["must_not_include"]: if ng in answer_text: problems.append(f"含めてはいけない表現: {ng}")
if len(answer_text) > case["max_chars"]: problems.append(f"長すぎる({len(answer_text)}文字)")
return problems
def run_eval(cases, ask_fn): results = [] for case in cases: text = ask_fn(case["doc"], case["question"]) results.append({"id": case["id"], "problems": judge(case, text)})
passed = sum(1 for r in results if not r["problems"]) print(f"通過 {passed}/{len(results)}") for r in results: if r["problems"]: print(f" NG {r['id']}: {', '.join(r['problems'])}") return results出力されるのは通過率と失敗の内訳です。この2つがあれば、プロンプトを変えたときに良くなったか悪くなったかを判断できます。評価基盤として必要な最小限は、実のところこれだけです。 を相手にしている以上、1件ごとの合否より全体の割合のほうが情報量が多いので、この粒度でちょうどよいとも言えます。
3. 何を集めるか ── 失敗例を優先する
件数を増やすとき、うまくいっている入力を増やしても情報は増えません。集めるべきは、実際に失敗した入力です。
| 集める対象 | 優先度 | 理由 |
|---|---|---|
| 実際に失敗した入力 | 最優先 | 再発を防げる。現実に起きた形である |
| 情報が不足している入力 | 高い | 答えないことを検証できる |
| 曖昧・多義的な質問 | 高い | 解釈の揺れが出やすい |
| 極端に長い / 短い入力 | 中 | 前処理の境界が壊れやすい |
| 典型的でうまくいく入力 | 低い | 通ることは分かっている |
「実際に失敗した入力」を集めるには、失敗を記録する仕組みが先に要ります。利用者が回答を修正した、やり直した、報告した ── こうした痕跡を残しておくと、評価セットの原資になります。EP.5 で書いた「不具合が出た入力をリグレッションに追加する」と同じ発想です。
机上で作った入力は、きれいすぎます。実際の利用者は、主語を省き、誤字を含み、複数の質問を1文に混ぜます。実データから採取した入力を混ぜないと、評価セットが現実と乖離します。
4. 分類して、傾向を見る
件数が増えてきたら、分類を入れます。全体の通過率だけを見ていると、「全体としては良くなったが、特定の種類だけ大きく悪化した」という変化を見逃します。
- 質問の種類 — 金額、日付、条件、手続きの手順
- 文書の性質 — 長文 / 短文、表を含む / 含まない
- 期待する振る舞い — 答える / 答えない / 確認を返す
- 難易度 — 直接書いてある / 複数箇所を突き合わせる必要がある
分類ごとの通過率を並べると、どこが弱いかが見えます。プロンプトの改善もモデルの選定も、この内訳がないと当てずっぽうになります。分類は最初から完璧である必要はなく、失敗を分類しようとして初めて必要な軸が分かるという順序で構いません。件数が少ないうちは各分類が数件ずつになりますが、それでも全体の平均だけを見るより早く異変に気づけます。
5. 評価セットの保守
評価セットは作って終わりではありません。放置すると現実と乖離し、通過率が高いのに利用者は不満、という状態になります。保守の作法を決めておきます。
- 1失敗が起きたら、その入力を追加する — これが最大の供給源
- 2仕様が変わったら、該当ケースを更新する — 期待値が古いまま残ると判断を誤る
- 3外すときは理由を書く — なぜ対象外にしたかが残らないと、後任が復活させる
- 4定期的に、全件が通る状態になっていないか確認する — 全部通るなら難易度が低すぎる
4番目は見落とされがちです。全件通る評価セットは、改善の余地を示せません。通過率が高止まりしたら、難しいケースを追加する時期だと考えてください。
6. 大きくする前に、使い始める
評価の話になると、網羅性の議論に時間を使いがちです。何件必要か、どう代表性を担保するか、統計的に有意か ── 重要な論点ではありますが、30件で回し始めたほうが学べることは多いというのが実感です。
30件でも、プロンプトを変えたときに何が壊れるかは分かります。そして壊れた事例が、次の30件を作るための材料になります。使いながら育てるのが、評価セットに関しては最も現実的な進め方です。逆に、網羅性の設計に数週間かけて一度も回さないまま止まってしまう、というのが最もよくある失敗の形です。手元の と同じで、小さく始めて回し続けたものだけが残ります。
評価セットは、正しさを測る物差しである前に、変更の影響を映す鏡である。鏡なら、小さくても役に立つ。
正解は回答文ではなく満たすべき性質で持つ。答えられないと言えるかを必ず入れる。集めるのは実際に失敗した入力を最優先。分類して内訳で見る。そして、大きくする前に使い始める。次回からは、これらを 上でどう回すかに移ります。
この記事の感想を教えてください
あなたの 1 クリックで、本当にこの記事は更新されます。「もっと詳しく」「続編希望」が一定数集まった記事は、 ふくふくが 実際に内容を拡充したり続編記事を公開 します。 送信したリアクションはお使いのブラウザに記録され、再カウントされません。