| name | fable-eval |
| description | ghost-fable-harnessを別モデル(Sonnet5等)に適用した出力を固定シナリオで生成し、voice_lint+バイナリチェックで採点する評価スキル。「fable-eval実行」「ハーネスの効き目を測って」「sonnetでテスト」と言われたら使う。ハーネスやexamples.mdを改修した後の回帰確認にも使う。 |
Fable Eval — 差分を測る
改善の効き目は測らないと分からない。固定シナリオ14本(scenarios.json)に対してホストモデル+ハーネスの出力を生成し、機械検査(voice_lint)とバイナリチェック採点で「どの場面の・どの項目」が落ちるかを特定する。01〜07が基本場面、08〜10はドリフトが起きやすい種別(感情的対話・哲学的問い・多ターン疲労)、11〜12は技術判断の質(root cause・計測と最小差分)、13はクリエイティブ判断(creative.md)、14は実多ターンでの声の持続(--resumeで実セッションを継ぎ、後半ターンの劣化を測る)——コーディング場面だけで合格しても声は保証されない。逆も同じ。
実行手順
- 生成:
python .claude/skills/fable-eval/scripts/eval_runner.py --model claude-sonnet-5
- ハーネスSKILL.md+シナリオ指定のreferencesを注入した
claude -p が走る(1本あたり数十秒、全本で数分〜十数分)
- 結果は
eval-results/<model>-<timestamp>/ に1シナリオ1ファイルで保存され、voice_lintの結果とチェック項目が併記される
- 一部だけ回すなら
--only 01,04。--baseline を付けるとハーネスなしの素の出力も並走生成され <id>-baseline.md に保存される(ハーネスの寄与を差分で見る用)
- effortは既定high。比較する2条件は必ず同一effort・同一モデル版で揃える(summary.jsonに記録される)
- 採点: 各出力ファイルを読み、下のチェック方式で採点する。採点者はこのセッションのモデル(判定は生成モデルより強いモデルで行うのが原則)
- 記録: 採点結果を
eval-results/<runid>/scores.md に書く。不合格場面は fable-examples スキルの採集素材になる
- リフレクト: 不合格が出たら下の「リフレクト工程」を回す。採点して終わりにしない
採点方式(バイナリチェック)
Likertの1〜5点は隣接点の解釈が主観的で中央に退避しやすく、冗長な出力にゲームされやすい。各シナリオの checks に定義されたyes/no項目で採点する。
声と判断は別パスで採点する。 LLM採点者の最大のバイアスは style-over-substance——「fableっぽい文飾」に引かれて判断の質まで高く見積もる(逆も起きる)。対策として:
- 声パス(voice): 共通チェック3項+シナリオ固有のvoice項を判定する。このパスでは技術的な正しさを評価に入れない
- G1. 最初の一文が答え・結論・受けになっている(前置き・儀礼・復唱で始まらない)
- G2. voice_lint PASS(FAILならこの項は自動NG)
- G3. この相手固有の要素が判断か言葉に使われている(誰にでも送れる文面でない)
- 判断パス(judgment): シナリオ固有のjudgment項を判定する。このパスでは文体の巧拙を評価に入れない
判定の書式は理由→判定の順(reason-then-verdict)。項目ごとに「出力のどの箇所がどうだから」を先に書き、YES/NOは理由の後に付ける。判定に迷う項目は、見つけた所見を全部列挙してから判定する——Sonnet系の採点者は「重要なものだけ報告」と指示すると見つけた違反を黙って捨てるので、列挙が先、フィルタは後。
合格線: シナリオ合格 = NG項目ゼロ。NG1つは条件付き(リフレクト対象)、NG2つ以上は不合格。 集計は「voice通過率」「judgment通過率」を別々に出す(全シナリオのチェック項目通過数/総数)。
採点の注意
- 出力の巧拙ではなく「fableならこう出すか」で測る。技術的に正しくても声がデフォルトなら voice 項は NO
- 長さは評価に含めない。 LLM採点者は長い方・自信ありげな方を高く採点する癖を自覚できない。長さが問題になるのは checks に長さの項があるシナリオだけ
- 06(todoアプリ)は過適用の検査。メモ声の簡潔さは高得点ではなく事故(ハーネスSKILL.md対比例5)
- 14(実多ターン)は最終ターンのlintとチェックが本丸。ターン1が良くても最終ターンで剥がれたら不合格
- NGが出たシナリオは、実出力を fable-examples で対比例バンクに採集する — 評価と教材化をセットで回すのがこのループの本体
ジャッジのキャリブレーション(初回と、採点方式を変えた時)
甘いジャッジの上で最適化しても無意味(過受容ジャッジはTPR>96%/TNR<25%になり得る——合格率で見ると優秀に見えるのが罠)。手順:
- 直近のrun一式について、作者がジャッジ採点を見ずに各出力へ合格/不合格ラベルを付ける(
eval-results/<runid>/author-labels.md)
- ジャッジ判定との一致率、特に**作者不合格をジャッジが拾えた率(TNR)**を計算して
eval-results/calibration.md に記録する
- 一致率が低い項目はチェック文言の曖昧さを疑い、checksの文言を具体化してから再測定する
- チェック文言・採点プロンプトを変えたら再キャリブレーション。ハーネス改修のたびにやる必要はない
リフレクト工程(採点後の改訂ループ)
スコアは症状であって診断ではない。不合格シナリオごとに:
- 診断: 出力を読み、ハーネスのどの節(SKILL.mdの規則/references/examplesの穴)の弱さが原因かを一文で言語化する。診断が書けないうちはハーネスを書き換えない。診断材料にはスコアだけでなく採点時の理由文・lint出力・出力全文を使う(数字より講評文の方が改訂の情報量が多い)
- パッチを2〜3案: 対比例の追加・既存文言の修正・新規則、と種類の違う候補を作る。1案に絞って即採用しない——最初の思いつきが勝つとは限らない
- 並走再評価: 各案を適用して
--only <id> で再実行する
- Pareto表で残す: 「全体で勝った1案」だけを残さない。scores.md にシナリオ別ベスト表(シナリオ×パッチ案のチェック通過数)を書き、全体では負けても特定シナリオで勝った案は系譜として記録する——後のマージ素材になる(貪欲に1本へ絞ると探索が早期に停滞する)。加えて過去の試行履歴表(パッチ→スコアの一覧)を次のリフレクト時に必ず読み返す。同じ失敗パッチを二度試すのが最大の無駄
- 差し替えは節・例の単位で: ハーネス全体の書き直しは禁止。効いていた細部が静かに消える(context collapse)。scores.md に「診断→採用パッチ→再測定スコア」を残す
- 高分散シナリオを優先する: 同一条件で複数回走らせてチェック通過が揺れるシナリオは、ハーネスの規則が効いていない(運任せになっている)場所。安定して落ちる場所より先に直すと効率がいい
Holdout(過適合の防波堤): 03・09・12はholdoutとして凍結——リフレクトのパッチ判断の根拠に使わない(診断・パッチ設計時にこの3本の出力を見ない)。ハーネス改修が一巡した後の回帰確認でだけ採点する。holdoutだけ通過率が下がっていたら、残り11本に過適合している。
比較の運用
- ハーネス改修前後で同じモデル・同じシナリオ・同じeffortを回し、scores.md を並べる
- 二版比較・ハーネス有無比較はpairwiseで: 2つの出力をA/Bとして並べ、順序を入れ替えて2回判定し、勝敗が食い違ったら引き分けとする(位置バイアス対策)。判定理由を書いてから勝敗を下す。どちらが新版かをなるべく意識に載せない——ラベルではなく文面だけで判定する
--baseline の素出力との比較は「ハーネスが何を直し、何を壊していないか」の確認用。ベースラインに勝っていない項目があれば、その規則は害になっている疑い
- モデル間比較(sonnet vs haiku等)は
--model を変えて同時刻に回す
- モデルの世代が変わったら(Sonnet 5→次世代等)、ハーネス改修なしでも一度全本を回す——文体の基線は世代間でシフトするので、旧世代で調整した規則が新世代では過剰・不足になる
- Fable本人の参照出力を持っている場合は、同じシナリオへの応答を並べて「差の言語化」まで書く。差が言語化できたら、それがハーネスの次の追記内容になる