LLM評価基盤(evals)を1日で立ち上げる:棚卸しから警告モード運用まで
LLM評価基盤(evals)を1日で立ち上げる:棚卸しから警告モード運用まで
朝いちばんに評価基盤のチェックリストを投げた。 18項目あるうちの頭3つはこれだった。
- デプロイ前に必ず回る評価ハーネスを作る
- 最低100件以上のゴールデンデータセットを用意する
- キャリブレーション済みのLLM-as-a-judgeを実装する
書いた時点では、自分のリポジトリに何が足りていないのか分かっていなかった。 だから実装より先に、今の状況をデューデリジェンスして、その上で計画書を設計してほしいと頼んだ。
18項目の棚卸しで見えた空白
結果は、◎が3、△が10、✕が5だった。
✕の5つは全部が計測系に固まっていた。 ゴールデンセット、judge、精度メトリクス、コスト曲線、A/B。
逆に、守りの3層は厚かった。
CLAUDE.md のルール群が防止を担い、検証スクリプト群が決定的な検証を担い、/learn と自分の目視が人間ゲートになっている。
壊れたものが本番に出ないようにする仕掛けは何重にもある。
出てきた記事が良いか悪いかを数字で言える仕掛けだけが丸ごと無かった。
エンジンをどこに置くか
計画書が Chrome に表示されて、判断待ちの状態になった。
読んでいるうちに、構成で引っかかった。
エンジンの置き場所が apps/web/scripts/evals/ になっている。
これは mdx-playground の中だけで機能するものとして計画に入っているのか、と聞いた。 自分は全リポジトリで使うつもりでいる。 それならリポジトリを1個新規で切ったほうがよくないか。
返ってきたのは「今の計画は mdx-playground 内蔵の設計です」という答えだった。 デューデリジェンスの対象範囲自体が、このリポジトリだけになっていた。
そこで「エンジン中央、データ各リポ」の横断構成に組み替えさせた。 判定ロジックと CLI は専用リポジトリに置き、ゴールデンセットと実行記録は各リポジトリが持つ。 ゴールデンセットはリポジトリの中身そのものなので、中央に集めると顧客情報の境界が壊れる。
3択ボタンで決めた5件
もともと4件だった判断項目は、置き場所の⑤が増えて5件になった。 計画書HTMLに埋め込まれた3択ボタンで答えた。
①の最初の対象パイプラインには position-news を選び、④のゲートの強さは警告からブロックへ昇格させる案にした。
②のゴールデンセット規模で選んだのは「10件のスモークで即開始」だった。 同じ日の朝に自分で「最低100件以上」と書いておきながら、その日のうちに10件へ落としたことになる。 100件を揃えてから始めると、揃えるだけで数日溶ける。
③の judge の実行基盤は Codex CLI にした。 実行が遅ければ非同期でセッションを複数立ち上げて、適切なセッション数で回せばいい、とコメントに残した。
⑤の置き場所の回答には、進め方の条件を書いた。
まず小さく回して試すこと。
リポジトリを作ったら即コミットして push し、リモートも作ること。
.gitignore を整備すること。
何をやったか、どう判断したかを、計画ドキュメントに全部残しながら進めること。
エンジン初版とセッションの区切り
1セッション目で、エンジンリポ llm-evals の新規作成から GitHub private への push まで進んだ(7a94213)。
決定的チェックには frontmatter、外部リンク形式、禁止語の3本が入った。
実記事のスモークは error 0 / warn 0 で合格し、違反8種を仕込んだ合成不合格例では8件すべてを検出した。
「通ること」だけでなく「落とせること」まで見せてもらったのがよかった。
途中、決裁フォームのサーバーを立て直すときにポート8899が塞がっていた。 旧サーバーの node 子プロセスが残っていたので、ポート指定で止めさせた。
学習ゲートは4問全問正解で通過してコミットに進んだ。
ここでコンテキストが40%になった。 一旦セッションを切ったほうがいいと思うがどうか、と聞いてみた。 返ってきたのも、切ったほうがよいという答えだった。 エンジン初版を push し終えてスモークも合格した直後で、区切りとしてちょうどよかった。
このとき、状態を正確に言わせておいた。
llm-evals 側はコミットして push 済み、mdx-playground 側は意図的に未コミットのまま残してある。
計画書の実装ログに現状を追記させ、引き継ぎプロンプトをターミナルに出してもらって終わった。
数値照合とゴールデンセット10件
2セッション目は「実装の続きをお願いします」の一言から始まった。
エンジンの構造は、判定を純粋関数 run(text, cfg) に閉じ、副作用を bin/ へ集約する形になっている。
数値照合チェックでは、丸めの扱いを決めた。
記事側の表記精度で丸めて一致判定にする。
実測の -26.43 と、表記の -26.4% は一致扱いにした。
実記事のスモークで誤検知が1件出た。 「SNDK は売上 +372%」を、銘柄に紐づく数値主張として拾ってしまう。 これは売上の成長率であって、株価の変動率ではない。 数値と銘柄名のあいだに入ってよい文字を助詞と記号に限定して解消した。
ゴールデンセットは、合格5件と合成不合格5件の計10件にした。 調整用6件と holdout 4件に最初から分割し、holdout はキャリブレーションの最終測定まで開封しない。 不合格例の本文に「合成例」と自己申告させないことも決めた。 judge への正解リークになる。
呼び出し側には薄いラッパーを置いて、コマンド1本で回るようにした。
pnpm evals:check # 決定的チェック
pnpm evals:golden # ゴールデン10件を一括判定
pnpm evals:check-changed # 変更された記事だけ
副産物が1つあった。
position-news のスクリプト3本が、Windows で何もせず exit 0 していた。
エントリポイント判定が POSIX 依存の書き方になっていて、pathToFileURL への置換で直った(98bff975)。
評価ハーネスを作っていたら、評価される側のパイプラインが Windows で動いていなかったことが出てきた。
ユニットテストは212ファイルの19,486件がすべて pass した。
フェーズ4の重さ
フェーズ4は重たいのか、一気に行けるなら行ってほしい、重ければセッションを変えよう、と聞いた。
答えは軽い部分と重い部分に分かれていた。 道具の部分(週次ダイジェストとチェーン末尾の報告)は軽い。 記事系の2本(月次レビューと手法公開)は、判定履歴とキャリブレーションの実測値が無いと書けない。 そこで道具部分だけ一気にやらせて、記事は今日はやらないと決めた。
毎朝どこを見るか
この日いちばん効いたのは、報告の受け取り方の見直しだった。
計画書には、毎朝の完了報告の末尾に品質トレンドの1行が出る、と書かれていた。 それを読んで、自分がターミナルの完了報告をあまり読んでいないことに気づいた。
毎朝 /make-diary を打つのは自分の作業で、そこは変えなくていい。
ただ、末尾に1行出ても目に入らない。
Chrome にページが開かれれば「何だろう」と思って気づく。
だから報告面を report.html に一本化させた。
- 日次ログは最新日が上に来るように更新をかけていく
- 週次ダイジェストを自分から「見せて」と言うつもりはないので、チェーンにカウンターを持たせて7日ごとに自動でページへ出す
- 独立したスラッシュコマンドに分けたほうがいいと伝えて
/evals-dailyを新設させ、make-diary の末尾はそれへの委譲に変えた
ページは追記ではなく、記録からの全再生成にした。 記録のほうを正とすれば、どちらの端末で生成しても同じページになる。
日付境界のバグが1つ出た。 UTC で日を切ると、朝のチェーンの記録が前日の欄に入る。 実画面で見つかったので JST に直させた。
どちらの端末でチェーンが回るのか
「次にやること」の節に、手作業の端末は Mac だという前提で書かれた箇所があった。 実際には Windows と Mac の両方を手で使っている。 どちらの機でも毎朝のチェーンが回り得る。
前提を「両機とも手作業端末」に書き換えさせた(031ef860)。
残った必須作業は、Mac 側にエンジンを clone する1回だけになった。
半年後に辿れるか
何のことだったか、自分は忘れる。 忘れたときに、参照先として計画書へ辿り着けるようになっているか。 最後に確かめたのはこれだった。
参照経路は3層になった。
- CLAUDE.md のルール所在マップに計画書への行を追加した。セッション開始時に必ず読まれる場所にあたる(
53febe36) - evals に触れる入口(
/evals-daily、make-diary、position-news、/deploy、各 README)すべてから計画書へリンクを張らせた report.htmlのヘッダー自体に計画書のパスを出す
これで自分が忘れても、次のセッションが検索なしで辿れる。
学び
- 「100件を揃えてから始める」は、始めない理由になる。10件で回して、指摘が出るたびに足すほうが早く動く
- 置き場所の問いは計画書の段階で出しておいてよかった。実装が進んでから聞いていたら、動かす手間はもっと大きくなっていた
- 報告は出すだけでは届かない。自分が普段どこを見ているかを申告して、そこへ寄せてもらう
- 測る道具を入れると、測られる側の穴が出てくる。Windows で exit 0 していたスクリプトがそれだった