応答品質の回帰テストハーネスを作る
目標
評価ハーネスを最初から作って、品質とレイテンシをいっしょに測り、ベースラインに対する回帰を自動で判定するCIゲートまで完成させます。
なぜ重要なのか
評価ハーネスなしに、モデルやプロンプトを変えるのは、テストなしにリファクタリングするのと同じです。いくつか尋ねてみて、「よくなった気がする」で決めたあと、2週間たって、特定の種類で品質が落ちたという問い合わせを受け、元に戻すかを判断する根拠がない、という状況が繰り返されます。LLMの出力は決定的ではないので、完璧な評価は不可能ですが、回帰を捉えられる程度には、十分に測れます。このラボで特に重要なのが、ステップ5です。品質だけを測るのでは半分です。品質が2パーセントポイントよくなったのに、レイテンシが2倍になったなら、それは改善ではありません。そして、ステップ7のCIゲートが、これらすべてを実際に機能させます。人が覚えていて動かす評価は、結局動かなくなります。
ステップ
/opt/fixtures/llms/evalset.jsonl(40件、各行にid、prompt、expected、grader)を読んで、/root/ev/loaded.txtに、cases=40 graders=<쉼표로 이은 종류>(プレースホルダーはカンマでつないだ種類です)を書いてください。/root/ev/run.pyで、すべてのケースを8170のサーバーで実行して、/root/ev/results.jsonlを作成してください。各行にid、output、latency_msがある必要があり、40行である必要があります。graderがexactのケースを、完全一致で採点してください。/root/ev/exact.txtに、cases=<n> passed=<n> score=<0~1 소수>(プレースホルダーは0から1の小数です)を書いてください。graderがcontainsのケースを、キーワードを含む比率で採点してください。/root/ev/contains.txtに同じ形式で書き、scoreは0以上1以下である必要があります。- レイテンシのSLOを800msとして、違反の件数を数えてください。
/root/ev/latency.txtに、slo_ms=800 violations=<n> p99_ms=<수>(プレースホルダーは数値です)を書いてください。 - 全体のスコアを、
/root/ev/baseline.jsonに、{"score":<수>,"p99_ms":<수>,"cases":40}(プレースホルダーは数値です)として保存してください。そのあと、/root/ev/compare.pyで、新しい実行結果をベースラインと比較して、/root/ev/regression.txtに、baseline=<수> current=<수> delta=<부호 있는 수> verdict=<PASS|FAIL>(プレースホルダーは数値と、符号付きの数値です)を書いてください。しきい値は-0.03です。 /root/ev/gate.shは、回帰なら終了コード1、そうでなければ0で終了してください。両方の場合を試験して、/root/ev/gate.outに、pass_exit=0 fail_exit=1を書いてください。
参考
- 127.0.0.1:8170のバックエンドは、このPodにあらかじめ立ち上がってはいません。
POST /generateで、{"prompt":..., "max_tokens":n}を受け取って、{"text":..., "usage":{...}}を返せばよいので、前のトークンストリーミングのラボで作ったサーバーをそのまま使うか、同じ契約の最小のサーバーを自分で立ち上げてから始めてください。回帰を作ってみるには、品質を外から変えられるつまみを1つ置いておくと便利です。 - 評価セットは、実際のトラフィックの分布を反映する必要があります。うまくいくケースだけを集めると、回帰を捉えられません。
- しきい値は、ノイズより大きい必要があります。3パーセントポイントが出発点で、評価セットが小さければ、さらに大きくする必要があります。
- ユーザーの問い合わせで見つかった失敗ケースを、評価セットに追加するループを作れば、同じ失敗が2回起きません。
- よくあるミス1: 品質だけを測って、レイテンシとコストを抜かしてしまうことです。
- よくあるミス2: ハーネスを作っておいて、CIに入れないことです。人が覚えていて動かす評価は、結局動かなくなります。
評価セットを読み込む
/opt/fixtures/llms/evalset.jsonl(40件、各行にid、prompt、expected、grader)を読んで、/root/ev/loaded.txtに、cases=40 graders=<쉼표로 이은 종류>(プレースホルダーはカンマでつないだ種類です)を書いてください。
JSONLの1行が、1つのケースです。ケースごとに、採点方式が異なることがあります。
ランナーですべてのケースを実行する
/root/ev/run.pyで、すべてのケースを8170のサーバーで実行して、/root/ev/results.jsonlを作成してください。各行にid、output、latency_msがある必要があり、40行である必要があります。
結果を、元のデータといっしょに残しておかないと、あとで何が間違ったかを見られません。
完全一致で採点する
graderがexactのケースを、完全一致で採点してください。/root/ev/exact.txtに、cases=<n> passed=<n> score=<0~1 소수>(プレースホルダーは0から1の小数です)を書いてください。
構造化された出力に合う方式です。先に、空白と大文字小文字を正規化してください。
部分点で採点する
graderがcontainsのケースを、キーワードを含む比率で採点してください。/root/ev/contains.txtに同じ形式で書き、scoreは0以上1以下である必要があります。
自由記述は、キーワードを含む比率で点数を出します。0と1の間の値が出る必要があります。
レイテンシSLOの違反を数える
レイテンシのSLOを800msとして、違反の件数を数えてください。/root/ev/latency.txtに、slo_ms=800 violations=<n> p99_ms=<수>(プレースホルダーは数値です)を書いてください。
品質だけを測るのでは半分です。遅くなったことも、回帰です。
ベースラインを保存して回帰を判定する
全体のスコアを、/root/ev/baseline.jsonに、{"score":<수>,"p99_ms":<수>,"cases":40}(プレースホルダーは数値です)として保存してください。そのあと、/root/ev/compare.pyで、新しい実行結果をベースラインと比較して、/root/ev/regression.txtに、baseline=<수> current=<수> delta=<부호 있는 수> verdict=<PASS|FAIL>(プレースホルダーは数値と、符号付きの数値です)を書いてください。しきい値は-0.03です。
比較する対象がなければ、判定できません。しきい値は、ノイズより大きい必要があります。
CIゲートのスクリプトを作る
/root/ev/gate.shは、回帰なら終了コード1、そうでなければ0で終了してください。両方の場合を試験して、/root/ev/gate.outに、pass_exit=0 fail_exit=1を書いてください。
人が覚えていなくてもよいようにすることが、目標です。終了コードで、通過したかどうかを知らせてください。