promptfooを使ったLLM評価基盤設計 ― 評価サイクルを回す3つの工夫

-- Views

August 25, 26

スライド概要

社内勉強会で発表した資料を、社外公開向けにリライトしたものです。

promptfooを使って、複数のLLM呼び出しを含む処理の評価基盤を設計した話です。

シェア

またはPlayer版

埋め込む »CMSなどでJSが使えない場合

ダウンロード

関連スライド

各ページのテキスト
1.

promptfoo を使った LLM評価基盤設計 評価サイクルを回し切るために試した、3つの工夫 2026/05 @KJR020

2.

目次 promptfoo とは パフォーマンス改善の経緯 課題 1 ─ 複数の LLM 呼び出しを含む処理を、どう評価するか? 課題 2 ─ 評価を変えるたびに、生成からやり直しが生じる 課題 3 ─ 全候補をまともに評価すると、サイクルが回らない まとめ 2 / 18

3.

promptfoo とは LLM の比較・評価を体系的に回せる CLI 評価一式を、設定ファイルで宣言的に管理できる テストケース・プロンプト・モデル・評価観点が対象。Git や CI に乗せられる モデル × プロンプト × テストケースの全組み合わせを、1コマンドで一括比較できる OpenAI・Anthropic などの API からローカルモデルまで、同じ設定で比較できる 出力の検証を assertion として宣言し、自動評価できる ルール検証と model-graded(llm-rubric)に対応し、score・latency・cost も記録される Custom Provider で拡張でき、自前の処理をそのまま評価対象にできる GitHub スター 24k超の OSS。2026年には OpenAI への合流も発表されて注目されている(はず) 3 / 18

4.

設定を宣言すれば、実行から検証まで行える 4 / 18 providers・prompts・tests を宣言すると、promptfoo eval の1コマンドで全組み合わせ が実行される # promptfooconfig.yaml providers: - google:gemini-2.5-pro - google:gemini-2.5-flash prompts: - file://prompts/answer.txt tests: - vars: question: " assert: - type: llm-rubric value: " # 比較したいモデルを並べる # 比較したいプロンプト 経費精算の上限は?" # 出力を LLM に採点させる 根拠となる文書箇所を明示している"

5.

promptfoo で LLM 処理のパフォーマンス改善に取り組んだ 品質を維持しながら、回答のレイテンシを下げることが目的だった 多段の LLM 呼び出しは待ち時間が積み上がりやすく、レイテンシの削減が課題だった LLM as a Judge で理想回答と比較し、観点別にスコアリングさせた Judge LLM as a Judge Dataset テストケース 高評価だった想定問答 Generate promptfoo 回答生成 + Latency測定 理想回答と比較し採点 モデル / プロンプト / パラメータを変えて反復 Analyze 集約・分析 5 / 18

6.

実プロジェクトの処理フロー 6 / 18 複数の LLM 呼び出しが連鎖する、多段の処理だった 問い合わせを論点に分解する 論点ごとに必要な原文を参照し、根拠付きの回答案を組み立てる 個別の回答案を統合し、最終的な回答を生成する Input 問い合わせ Step 1 分解 Step 2 個別処理 参照情報 原文 Step 3 統合 Output 根拠付き回答

7.

promptfoo を入れたが… いざ実検討に入ると課題が見つかり、すぐには評価サイクルを回すことができなかった ぶつかった課題 1 複数の LLM 呼び出しを含む処理を、どう評価するか? 2 標準の assert では、生成と Judge が直列で回しにくい 3 全候補をまともに評価すると、サイクルが回らない 試行錯誤しながら対処した、3つの課題と対応を紹介する 7 / 18

8.

課題 1 ─ 複数の LLM 呼び出しを含む処理を、どう評価するか? 処理が多段になると、E2E 評価だけでは変更の影響を切り分けにくい Input 問い合わせ Step 1 分解 Step 2 個別処理 前段の出力 参照情報 前段の出力が、後続 Step の入力として連鎖する E2E でまとめて評価すると、変数が多く、どの変更が効いたのか比較しづらい そこで、Step 単体の評価と E2E 評価を使い分けた Step 単体 … 変更の影響を切り分ける E2E … 最終品質を確認する Step 3 統合 Output 根拠付き回答 8 / 18

9.

課題 1 の対応 ─ Step 単体の変化を観測する 9 / 18 どの変更が効いたのかを特定するため、条件を揃えて Step 単体への影響を切り分けた Fixed 前段の出力 Target 評価対象の Step ここだけを変数にする Score 出力を採点 Fixed 参照情報 前段処理の出力と参照情報をテストフィクスチャとして固定し、対象の Step だけを変更した 同じ入力条件で、対象 Step の変更差分を比較できるようになった ※ 前段処理は変更していないため、今回の評価対象外とした

10.

課題 1 の対応 ─ パイプライン全体の出力品質を確認する 10 / 18 最終出力の品質とパフォーマンスを担保するため、各ステップを組み合わせた最終出力を確 認した promptfoo eval callApi Custom Provider Step1 → Step2 → Step3 多段処理全体を1回の呼び出しとして 扱う Output 根拠付き回答 ステップ単体の評価だけでは、各ステップを組み合わせたE2E の挙動はわからない promptfoo でパイプライン全体を検証するため、Custom Provider を定義し、処理全体を1回にまとめた

11.
[beta]
課題 1 の対応 ─ Custom Provider の実装
実装するのは id() と callApi() の2つ
(擬似コード)

// providers/e2e_chain.js
class E2EChainProvider {
id() { return 'e2e-chain'; }

}

async callApi(prompt, context) {
const step1Output = await step1(prompt);
const references = await loadReferences(step1Output);
const drafts = await step2(step1Output, references);
const answer = await step3(drafts);
return { output: answer };
}

//
//
//
//
//

前段処理
参照情報
個別処理
統合
評価対象

中で何を呼んでもよいため、複数の LLM 呼び出しを含む処理全体をそのまま実装できる
promptfoo からは「入力を渡すと出力が返る1つのモデル」に見えるため、既存の評価設定をそのまま使える

11 / 18

12.

課題 2 ─ 評価を変えるたびに、生成からやり直しが生じる 標準の assert では生成と Judge が直列になり、生成と評価が密結合になっていた Input テストケース Generate 回答生成 Assert: llm-rubric Judge 評価 直列 この密結合により、検証に次の不便が生じた Judge モデルや評価プロンプトを変えて、評価だけやり直すことができない 同じ回答に複数回 Judge を走らせて、スコアを平均化することができない 評価まで直列なので 1 run が重く、試行を回しにくい Result 実行結果 12 / 18

13.

課題 2 の対応 ─ 実行と評価を分ける 13 / 18 回答生成は promptfoo、評価(Judge)は自前スクリプトに分離した 実行 promptfoo eval n回試行 Logs 実行ログ JSON runごと 評価 Flatten Judge 1回答 = 1行 1レコードずつ 回答 CSV 採点 Merge 結合テーブル promptfoo で回答だけを生成し、回答csv(1回答 = 1レコード)に平坦化して保存した csv の1レコードずつに Judge を実行し、採点結果を結合して分析した 分離の効果 → Judge だけ再評価可能になり、データの再利用性が上がった Analyze Notebook 分析

14.

課題 3 ─ 全候補をまともに評価すると、サイクルが回らない Candidates モデル × プロンプト × パラメータ Generate 回答生成 × 試行回数 Judge 品質評価 LLM as a Judge 検証要素が多く、候補 × 試行 × 採点で評価のコストは掛け算で膨らんだ 候補は、モデル × プロンプト × パラメータの組み合わせで増える 試行は、スコアのばらつきを見るため1候補あたり何度も生成する 採点(品質評価・LLM as a Judge)でも LLM 呼び出しが発生し、最終確認は人手が担う Result 結果 14 / 18

15.

課題 3 の対応 ─ 手軽な評価から順に、段階的に足切りする 15 / 18 評価ゲートを設定し、段階的に足切り可能な構成にすることで ゲートをクリアした候補だけ を、高コストな次工程の評価へ送る運用ができた Candidates 多数の候補 速い候補だけ 1. Latency 速度で足切り 追加コストゼロ 有望な候補だけ Latency は生成ログから算出でき、追加コストがかからない 2. LLM as a Judge 品質を自動採点 変更前とのベンチ比較で速い候補だけを残す(分布も確認する)。課題 2 の対応が、ここで効いた LLM as a Judge が自動で品質を採点する これも足切りであって、最終判断ではない 人手チェックは、ドメインエキスパートによる最終品質担保 最もコストが高いため、絞った少数だけに行った 3. Expert ドメインエキスパート 人手で最終チェック

16.

課題 3 の対応 ─ LLM as a Judge の採点を確認する 自動採点は、基準回答・reason・スコア分布を確認してから足切りに使った 1 基準回答を固定する 2 reason(判断理由)を確認する 3 複数回採点し、分布を見る 理想回答には、実運用で高評価だった回答セットを使った 低スコアの候補は理由を読み、採点内容を点検した 同じ候補を複数回試行し、単一スコアで判断しない 16 / 18

17.

評価基盤が整い軽量モデルへの切替を根拠を持って判断できた モデルの進化で生まれた候補を、3つの工夫で品質と速度の両面から比較した 1 軽量モデルでも品質を維持できる候補が生まれた モデルの進化で、切替の可否を検証する対象になった 2 1ケース処理時間:275秒 → 53秒 3 「切り替えても大丈夫」と判断できた 222秒短縮、約81%削減(約8割) 評価結果を根拠に、品質を落とさないモデル変更を進められた 17 / 18

18.

まとめ ツールを導入するだけでは、LLM の品質と速度を継続的に改善できない 1 課題:複数の LLM 呼び出しを含む処理を、どう評価するか? 対応:Step 単体で変更の影響を切り分け、E2E で全体品質を確認する 2 課題:評価を変えるたびに、生成からやり直しが生じる 対応:回答生成と Judge 評価を分離し、生成結果を再利用する 3 課題:全候補をまともに評価すると、サイクルが回らない 対応:Latency → Judge → Expert の順に候補を絞る 改善サイクルを回す運用設計が本質だった LLM の改善も、計測 → 変更 → 再計測の反復で進める 複雑な処理では、Step単体で変更の影響を切り分け、E2Eで最終品質を確認する 18 / 18