maplibre-running-evals について
Promptfoo でスキルを評価する手順を、テスト設計や予算確認から結果の診断、記録まで案内するスキルです。スキル未注入の基準実行と注入後の実行を比較します。
主な機能
- 01API キーとプロバイダー制限の確認
- 02基準テストとスキル注入テストの設計
- 03評価の逐次実行
- 04プロバイダーと判定モデルのエラー診断
- 05CSV と JSON での結果記録
- 06結果に基づく検証ステータス設定
活用シーン
- 01スキルがモデルの振る舞いの課題を解消するか測定
- 02スキルや評価基準の編集後に再テスト
- 03停止やレート制限が発生した評価の調査
- 04スキル評価の結果を記録
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ maplibre/ maplibre-agent-skills --skill maplibre-running-evalsインストール後、Claude Code で/maplibre-running-evalsを入力して読み込まれたか確認します。
詳細
Promptfoo 評価の実行
ここで実行する評価では、プロバイダー API を介して 2 つの役割を使用します。generator はテスト対象のモデル、judge は各 llm-rubric アサーションを評価するモデルです。evals/README.md では、何をテストするか、プロンプトとルーブリックの作成方法を説明しています。ここでは、評価を実行する手順を説明します。フェーズは順番に進めてください。すでにコンテンツがあるからといって、フェーズ 5 から始めないでください。
1. 実行の準備と予算の確保
何よりも先に予算を確認してください。これらのプロバイダーは無料枠で使用するため、完了できない実行からは何も得られません。
- ジェネレーターキーとジャッジキーが設定されていることを確認します。確認するのは設定の有無だけで、値は確認しないでください:
echo "generator: ${GROQ_API_KEY:+set}"およびecho "judge: ${GOOGLE_API_KEY:+set}"。どちらかが設定されていない場合は、そこで中止して報告してください。実行していない結果を推測してはいけません。 - 両方の役割には固定のモデルを使います: ジェネレーターは
evals/prompts/lib/providers.yaml、ジャッジはpackage.jsonのeval:gradedスクリプトで指定されています。デフォルトでは固定の組み合わせを使用してください。必須条件は、異なるモデルであることです。ジェネレーター自身をジャッジにしてはいけません。また、報告する結果には両方のモデル名を記載してください。 - 記録に残す実行はすべて
npm run eval:gradedで行ってください。--grader、--delay、-jを設定するのはこのコマンドだけです。これらを手入力したり、過去の結果ドキュメントから値をコピーしたりしないでください。そこに記載された値は、その実行の内容を示すものであり、今回の実行に使う値ではありません。--providersも渡さないでください。固定されたジェネレーターがデフォルトのものに置き換わるため、実行が固定構成ではなくなり、結果に誤ったラベルが付いてしまいます。詳細は evals/README.md の「Writing eval prompts」 を参照してください。--providers echoはプローブ用のケースです。ジェネレーターの回答の代わりにレンダリング済みプロンプトを返すため、ジェネレーターに送られる内容をそのまま確認できます。ただし、各llm-rubricは依然としてジャッジを呼び出すため、プローブにもジャッジの呼び出し回数が必要です。 - 実行を開始する前に、プロバイダーの制限を確認してください: 固定モデルのプランに関するレート制限のドキュメントを読み、その後、レスポンスヘッダーを表示してエンドポイントに最小限のリクエストを1回送信します。OpenAI互換のエンドポイントでは、
x-ratelimit-*ヘッダーに、1分あたりのトークン枠(上限、残量、リセット時刻)と1日あたりのリクエスト数が含まれます。1日あたりのトークン上限には専用のヘッダーがないため、429が返されたときに初めて判明します。待機時間はretry-afterに示され、どの制限に達したかはレスポンス本文に記載されます。ヘッダー名や対象となる内容はプロバイダーによって異なり、変更されることもあるため、プロバイダーの公式ページで確認してください。
# Example against the current generator's OpenAI-compatible endpoint; the URL and model id come from providers.yaml.
curl -s -X POST https://api.groq.com/openai/v1/chat/completions \
-H "Authorization: Bearer $GROQ_API_KEY" -H "Content-Type: application/json" \
--data '{"model":"openai/gpt-oss-120b","messages":[{"role":"user","content":"hi"}],"max_tokens":1}' \
-D - | grep -iE 'ratelimit|retry-after|"error"'
- 開始前に計算してください。 トークン数はおおよそバイト数 / 4 なので、
wc -c skills/<skill>/SKILL.mdでテストごとのプロンプト注入コストが分かります。スキルを使う実行のコストは、おおよそテスト数 ×(その値 + ジェネレーターに設定されたmax_tokens)に、ルーブリックごとのジャッジ呼び出し1回分を加えたものです。利用可能な当日分の予算と照らし合わせてください。ベースラインの実行では注入分が不要です。合計が残りの予算に収まらない場合は、全体を一度に実行しないでください。 - コマンドごとに1つの構成だけを使い、順番に実行してください。 固定値は
-j 1です。ターミナル間でもチームメンバー間でも、採点付き評価を同時に2つ実行してはいけません。制限はプロセスごとではなくアカウントごとに適用されるため、どちらかの役割で429が発生すると両方の実行が失敗します。連続して実行する場合は、1分間の上限枠が十分に回復する時間を空けてください:<run one> && sleep 60 && <run two>。各構成ではlib/skill-prompt.mjsとlib/providers.yamlがすでに設定されているため、手作業で組み立てる必要はありません。自分で Promptfoo コマンドを書く場合も、--configは必ず1つだけ指定してください。複数指定すると、Promptfoo はそれらのdefaultTestブロックを結合し、最後のskillFileが適用されます。 --delayはリクエストの間隔を調整するだけで、予算を増やすものではありません。 同時実行を行わず、適切な間隔を空けているにもかかわらずレート制限エラーが続く場合は、容量を使い切っています。そこで中止し、上記の直接リクエストで確認したリセット時刻を待ってください。繰り返し実行してはいけません。- 残りの1分枠または当日分の予算が実行に足りない場合は、遅延を長くするのではなく、1回の実行あたりのテスト数を減らしてください。
--filter-pattern '<regex against the test description>'を使うと、指定したテストだけを実行できます(正規表現を引用符で囲んでください。npm runがシェルに渡します)。また、構成全体を実行する前に、各実行タイプからテストを1つ選んでプローブする場合にも使えます。--filter-failing <the run's .json output>を使うと、失敗またはエラーになったテストだけを再実行できます。フェーズ8でそのファイルが書き出されます。
2. コンテンツを書く前にテストを書く
evals/prompts/TEMPLATE.yamlをevals/prompts/<skill>.yamlにコピーし、スキルにコンテンツがまったくない状態で、明示的、暗黙的、アンチパターン、ネガティブの4つのテストを書きます。evals/README.md, "Writing eval prompts" に方法が、このスキルに手順が記載されています。
3. スキルを適用せずにベースラインを実行する
npm run eval:graded -- --config evals/prompts/<skill>.yaml --var injectSkill=false
- 明示的、暗黙的、またはアンチパターンのテストがベースラインで合格した場合、そのテストには判別能力がありません。 テストを作り直してください。証拠として残してはならず、実行結果をグリーンにするためにテストを削除してもいけません。ネガティブテストはベースラインで合格してもかまいません。それが目的です。
- 判定列ではなく、ベースラインの回答をすべて読んでください。 判定者が見逃したハルシネーションは、テスト設計の失敗であり、ギャップの証拠ではありません。
- 1回の実行は1つのサンプルです。
temperature: 0でも生成は非決定的です。したがって、主張の根拠となるのは、実際に読んだ FAIL であり、判定だけではありません。
4. ベースラインで失敗した項目だけを書く
- モデルがすでに正しく処理できるコンテンツは、メリットではなくコストです。CONTRIBUTING.md, "Cutting content the model already gets right" を参照してください。
- 主張は、開いていた設定ファイルではなく、その主張を担当するスキルに記載します。テストの配置によってコンテンツの配置が決まり、そうしなければ1つの主張が3か所に分散し、内容がずれていきます。
5. スキルを適用して実行する
npm run eval:graded -- --config evals/prompts/<skill>.yaml — スキルの適用はデフォルトです。ギャップが実証されるのは、ベースラインで FAIL、スキル適用後に PASS となる場合です。 両方で合格する、両方で失敗する、またはスキル適用後の実行がない場合、主張の根拠はまだありません。
6. 再試行する前に診断する
Promptfoo は実行が終了してからテーブルを表示し、--output ファイルを書き込みます。また、進行状況バーの表示には stderr の TTY が必要です。そのため、エージェントの実行環境では、正常な実行も停止した実行も同じように見えます。経過時間から進行状況を判断してはいけません。まず、どちらなのかを確認してください。
pgrep -fl promptfoo # take the node pid, not the sh wrapper — the wrapper holds no sockets
lsof -nP -p <node-pid> -i # ESTABLISHED = still waiting on a provider
ls -l ~/.promptfoo/promptfoo.db* # finished rows land in the local db; the -wal file's mtime stops advancing on a stall
- 接続が開いていることがシグナルであり、CPU 使用率ではありません — プロバイダーからの応答待ちの実行は、0% のままです。接続が切れており、かつ mtime が更新されていない場合に限り、停止してください。
- フェーズ 1 の直接呼び出しを使って、どの制限に達したのか確認してください。 1 分待った場合も 1 日待った場合も、キューのタイムアウト、または再試行中のリクエストに対するレート制限エラーとして、同じように報告されます。1 分あたりの上限を超えるサイズのリクエストは別です。プロバイダーがそのリクエストを即座に拒否し、Promptfoo は本文を含むエラー行を記録します。
- 「There were some errors」と同時に
0 passed / 0 failedと表示される場合、これは結果ではなくデータなしです。 また、空の行は FAIL ではありません。呼び出しがモデルに到達していないためです。どちらもデータなしとして記録してください。証拠がないまま複数回再試行すると、予算を消費するだけです。 - 停止した実行では
--outputファイルは書き込まれず、以前のファイルがそのまま残ります。 そのため、読み取る前に mtime と行数を確認してください。完了した実行の行はローカルの db にあります。npx promptfoo export eval latest -o <path>.jsonを実行すると、実行自体が出力する JSON と同じ形式で書き出され、プロバイダーは呼び出しません。 - CSV だけでなく、JSON 出力も確認してください。 CSV では各セルが判定、回答、最上位の採点理由に平坦化されます。JSON には各アサーション固有の結果と理由、および ERROR 行の詳細なエラーが保持されています。
testCase.descriptionで対応付けたうえで、results.results[].response.outputとresults.results[].gradingResult.componentResults[].reasonを確認してください。理由だけでは判定を判断できません。また、エラーメッセージになっている理由や空の理由は、判定ではなくジャッジの失敗です。記録済みの CSV で、ジャッジの理由を編集してはいけません。 - すべての FAIL の末尾を確認して途切れていないか調べ、すべての
Must NOTPASS の末尾を確認して早期終了していないか調べてください。 回答が途中で切れていると、ルーブリックが求める部分が失われることがあります。また、禁止事項を簡単に満たしてしまうこともあります。
7. 再テストが必要な場合と、不要な場合
必要な場合:
- 主張を追加、修正、または削除した場合 — スキルを使って、その主張を対象とするテストを実行し、新しい主張は記述する前にベースラインを取得してください。
- ルーブリックまたはプロンプトを書き換えた場合 — そのテストのベースラインは無効になるため、設定全体ではなく
--filter-patternを指定して、そのテストを再実行してください。逆の場合も同様です。スキルの文言を変更したら、同じコミットで、そのスキルのルーブリックから削除したフレーズを grep してください。古いルーブリックは採点を甘くし、PASS になるためです。 - 固定されているジェネレーターまたはジャッジを変更した場合 — すべての設定を再実行してください。
週次 CI のドリフトチェック(eval.yml)は、独自のスケジュールでデフォルトブランチに対してコレクション全体を再実行します。この実行をコントリビューターが手動で繰り返す必要はありません。
不要な場合:
- 意味を保ったままの文言変更、書式変更、リンクの修正、誤字の修正。
- 主張を別のスキルに移動する場合 — コーパスは変わりません。テストを主張の移動先となるスキルの設定に移してください。
- ジェネレーターが PASS するために必要としていなかった内容を削除する場合 — ベースラインの PASS が根拠になります。削除理由として結果ドキュメントに記録してください。
status: processのスキル — eval の対象外です。説明対象のファイルと照らし合わせて読み、レビューしてください。
8. 結果を簡潔に記録する
- 実行ごとに CSV と JSON の 2 つのファイルに出力します:
--output evals/results/local/<skill>-<baseline|with-skill>.csv evals/results/local/<skill>-<baseline|with-skill>.json— このフラグには複数のパスを指定でき、ディレクトリは gitignore に登録されています。CSV は記録、JSON は診断(フェーズ 6)および--filter-failingの入力です。scripts/run-evals.jsは CI でも同じ 2 つのファイルを書き出します。引用する実行結果のみを昇格させます:その CSV をevals/results/latest/<skill>-<baseline|with-skill>_<YYYY-MM-DD>.csvとしてコミットします。 evals/results/<skill>.mdは 1 つの表にします — テストごとに 1 行を設け、種別、ベースラインの結果、スキル適用後の結果、およびセル内に簡潔にまとめた失敗モードを記載します。先頭に日付、生成モデル、評価モデルを記したRun:行を置き、最後に太字で合格/不合格の集計を記載します。判定に人による確認が必要だった場合を除き、ほかには何も追加しないでください。その場合は、どの判定か、未加工の出力がどこにあるかを記載します。status:は実行結果に従います: 固定した実行が合格した場合のみverified、それ以外はprovisionalにします。
関連スキル
- maplibre-skill-authoring — セッションの調査結果をスキルのドラフトとしてまとめます。このスキルでは評価ステップを実行します。
参考資料
- evals/README.md — プロンプト、評価基準、ベースライン調査の手法、プロバイダーの設定
package.jsonのeval:gradedスクリプト — 評価者、遅延、同時実行数に関する唯一の信頼できる情報源- 現在の生成モデルのレート制限ページ — ティアごとの制限とレスポンスヘッダー
- Promptfoo 0.122 —
package.jsonでは^0.122.0に固定されています。上記に記載されたすべてのフラグと動作は、このバージョンの CLI ヘルプとソースコードで確認済みです。固定バージョンが変わったら、再確認してください。
このスキルはスナップショットです。 一次資料と内容が矛盾する場合は、上記の参考資料、MapLibre の最新ドキュメント、または MapLibre の実際の動作を優先してください。それに従ったうえで、出典と使用している MapLibre のバージョンを明記して不一致を報告してください:インストール済みのコピーを編集してもほかの人の役には立たず、次回の更新で上書きされます。