6月30日、Toward Data Scienceが「Prompt Engineering Fails Quietly — Prompt Regression Is Why」と題した記事を公開した。この記事では、プロンプトへの小さな変更が既存の動作をサイレントに破壊する「プロンプト回帰」問題と、それを検出するテストスイートの構築方法について詳しく紹介されている。
「全体精度が上がったのに、本番は壊れていた」
RAG(Retrieval-Augmented Generation)のクエリ分類システムにPDF・ポリシー文書のルーティング機能を追加した。スポットテストでは問題なし。そのままリリースした。3週間後、「否定形のクエリ("Which products are not covered under warranty?"のような文)が誤分類される」というサポート報告が上がってきた。
分類ロジックもルーティングコードも変えていない。変わったのはシステムプロンプトだけだった。
これがこの記事の出発点だ。著者はここで、プロンプトを静的な設定ファイルとして扱っていたことに気づく。実際には違う。プロンプトは確率的なAPIであり、命令を1行追加するたびに、そのプロンプトが扱う全クエリタイプのAPI契約が変わる。
ソフトウェアエンジニアリングにはこの問題への解が存在する。リグレッションテストスイートだ。変更をシップする前にテストを走らせ、以前パスしていたものが落ちていたらシップしない。大半のチームはプロンプトにこれを持っていない。
テストスイートの構造
公開されているコードは、4つのプロンプトバージョンを40件の「ゴールデンクエリ」で検証する。ゴールデンクエリとは、システムが正しく処理できなければならないと事前に合意した代表的な入力サンプルのことで、いわばプロンプトの仕様書を兼ねる。外部依存なし、純粋なPython、実行時間は2秒以内だ。
クエリは6つのカテゴリに分散している:
| カテゴリ | 件数 | 対象とする失敗モード |
|---|---|---|
| simple_intent | 10 | 過剰推論ノイズ |
| comparison | 8 | 比較アンカーの欠落 |
| aggregation | 6 | 数値スコープの崩壊 |
| negation | 6 | 命令競合 |
| multi_hop | 6 | CoTの恩恵を受けるケース |
| edge_ambiguous | 4 | 誤った確信度 |
各クエリには「検証シグネチャ」が付いており、LLMによる採点は一切使わない。QueryValidatorが走らせるのは4つの決定論的チェックだ:スキーマキーの存在確認、期待パターンの出現確認、インテントラベルの一致確認、禁止文字列("I cannot"、"As an AI"など)の不在確認。4つ全部パスか、全部落ちるかの二択。部分点なし、ジャッジモデルなし。
なお、表中の「CoT」はChain-of-Thought(連鎖的思考)の略で、「まずステップ1を考え、次にステップ2を考え……」という形式でLLMに推論過程を明示させるプロンプト技法を指す。後述するが、CoTの追加が意図せず他の命令と競合する原因になる。
LLM-as-a-judgeを完全に省いた理由は明快だ。リグレッションテストは品質の問題ではなく契約の問題だからだ。期待インテントと実際のインテントが一致するかどうかは二値判定であり、ジャッジモデルはノイズを追加するだけになる。40クエリ×プロンプトバージョン分のAPI呼び出しコストも無視できない。
「False Improvement(偽の改善)」というパターン
このスイートの核心はFalse Improvementの検出にある。全体精度が上がっているのに、特定のクリティカルカテゴリが崩壊しているケースだ。
実際のベンチマーク結果がこれだ:
| カテゴリ | v1 | v2 | v3 | v4 |
|---|---|---|---|---|
| simple_intent | 100.0% | 40.0% | 80.0% | 90.0% |
| negation | 100.0% | 66.7% | 50.0% | 33.3% |
| aggregation | 100.0% | 100.0% | 100.0% | 100.0% |
| multi_hop | 0.0% | 100.0% | 100.0% | 100.0% |
| comparison | 0.0% | 0.0% | 0.0% | 0.0% |
| edge_ambiguous | 25.0% | 100.0% | 100.0% | 100.0% |
| OVERALL | 57.5% | 60.0% | 67.5% | 67.5% |
全体スコアだけ見ればv4は最良のプロンプト(67.5%)だ。しかしテストスイートの判定は以下になる:
VERDICT: v1 → v4
⚠ FALSE IMPROVEMENT DETECTED
Overall score improved by 10.0% but critical categories
regressed: [negation]
Critical regressions:
• negation 100.0% → 33.3% ▼ 66.7%
Failure mode: instruction_conflict
STATUS: ✗ DO NOT PROMOTE TO PRODUCTION
v2、v3、v4の全候補でこの判定が出た。3つとも全体スコアはベースラインより高い。3つとも本番に出してはいけない。
なぜv3でnegationが壊れたか
各バージョンの変更には正当な理由があった。
- v2:マルチホップクエリの誤分類を直すためにCoT推論を追加。効果はあったが、全クエリにCoTが適用されるようになり、"be concise"と"explain your reasoning step by step"という矛盾した命令が共存した。
- v3:文書ルーティングを追加。問題の命令はこれだ:「Prioritize document routing before intent classification」。否定形クエリ("Which regions are excluded from the express shipping policy?")はポリシーキーワードを含むため、インテント分類が走る前にドキュメントタイプ解決に吸い込まれる。否定チェックが一度も実行されない。
- v4:v2とv3の両方の変更を統合。命令の総数はv1の約3倍に膨らみ、潜在的な競合が複合する。
v4で否定クエリが失敗するときの出力例がこれだ:インテントは"negation_check"であるべきところ"ambiguous"になり、確信度は0.39に落ち、rewritten_queryにはCoTノイズが混入する:"Step 1: Scan for document type signals... Step 2: Negation keyword detected: but document routing takes priority..."
決定論的シミュレータという設計判断
スイートはライブAPIコールではなく決定論的モックシミュレータを使う。同じ入力には常に同じ出力が返る。
これは制約ではなく設計上の意図だ。テスト結果がランごとに変動すると、真のプロンプト回帰とバックグラウンドノイズを切り分けられなくなる。リグレッションテストにおいて再現性は唯一の指標だ。LLMのジャッジはオープンエンドな品質評価には有効だが、それは別のツールとして定期的に回せばよい。
プロンプト評価の周辺エコシステムとしては、より大規模な評価フレームワークであるLangSmithやPromptFooも存在するが、本記事のアプローチはそれらへの依存を排し、2秒・ゼロコストで回せる最小構成のCI的テストとして位置づけられている点が特徴的だ。※編集部の考察
ゴールデンクエリセットの設計、CRITICAL_CATEGORIESの定義(どのカテゴリが実トラフィックの大半を占めるか)、各失敗関数の実装詳細などは元記事およびGitHubリポジトリに詳述されている。
詳細はPrompt Engineering Fails Quietly — Prompt Regression Is Whyを参照していただきたい。