プロンプトから「不要な一文」を削除したら、3日後に出力が悪化していた件:プロンプト版管理ガイドが重要な理由
- 稼働中のプロンプトから「不要な一文」を削除。デプロイ後も何もアラートは出ず、3日後にカスタマーサポートのチケットでようやく発覚しました。
- 採点済みの1018件のプロンプトで最も点数が低かったのは「入力が想定外だった場合の対処が書かれているか」で、平均はわずか31.5点。そして、この項目こそ、削除時に最初に「無駄な文言」として消されやすい部分です。
- 対策は4つの関門。そのうちのひとつが、デプロイ前にこうした変更を直接差し止めます。ツールを買わなくても、git と採点スクリプトで十分です。
「無駄に見える数行」を削除 → デプロイ後、アラートはゼロ
ある開発者が、稼働中のプロンプトを修正していました。数行に「あまり意味がない」ように見えたので削除し、ついでにトークンもいくつか節約。デプロイ前の軽い気持ちでの整理は、誰でも一度はやったことがあり、深く考えもしないものです。デプロイ後、エラーなし、アラートなし、モニタリングもすべて正常。3日後、カスタマーサポートのチケットが来ました。ユーザーが受け取った応答は曖昧で的外れ、時には自信満々に間違っていました。
これは Reddit の r/PromptEngineering 版に投稿された、開発者による振り返り記事です。この出来事をきっかけに彼が構築した、プロンプトのバージョン管理フローについて書かれています。もしユーザー向けにプロンプトを運用しているなら(API を使ったアプリでも、毎日自動実行しているパイプラインでも)、これは遅かれ早かれ踏むであろう落とし穴です。しかも、踏んだと気づかないタイプの穴です。
3日間、悪化したプロンプトが本番トラフィックを処理し、ひっそりと質の低い回答を配信し続けていました。その間のシグナルはゼロです。
プロンプトの劣化はアラートも出さないし、目視でも見抜けない
同じ「壊し方」をしても、コードとプロンプトでは大きく違います。それは、壊れた後に誰かが教えてくれるかどうかです。
| コードを壊した場合 | プロンプトを壊した場合 | |
|---|---|---|
| その場で何が起きるか | 例外が発生、コンパイルエラー、実行すると即クラッシュ | それらしいテキストを普通に返す |
| プログラムが受け取るもの | 失敗 | 正常な結果 |
| 誰が教えてくれるか | テスト、コンパイラ、監視アラート | 誰もいない |
| いつ気づくか | その場で | 3日後、ユーザーの苦情で |
根本的な違いはこれです。プロンプトは自然言語なので、どれだけ不完全に書き換えても、モデルはそれなりにテキストを返してきます。エラーも失敗のシグナルも出しません。だから突然壊れるわけではなく、「ほんの少しずつ」悪くなっていき、その蓄積が「ユーザーが気づくレベル」に達した時、初めて顕在化します。
では、デプロイ前に出力をいくつか目視確認すればいいのでは? 肉眼で捉えられるのは、明白なエラーだけです。エラーメッセージが返る、フォーマットが完全に崩れる、的外れな答えが一目瞭然、といった類い。品質の劣化は捉えられません。理由は2つ。出力は依然として「もっともらしい回答」であり、ただ悪くなっているだけだからです。そして、あなた自身が期待を持って見ているので、「このプロンプトは何を言うべきか」を知っていて、読んだ内容をすべてその方向に当てはめてしまうからです。本当の劣化は、多数の実ユーザーが多様な質問をして初めて表面化します。そして、その時の形こそがサポートチケットなのです。
1018件のプロンプト採点 → 最も低いのは「想定外の入力への対応」
これは一人のミスではありません。1018件のプロンプトを採点したところ、同じ問題がそこら中に見つかりました。満点は100点で、4つの項目に分かれています。この4項目自体が、「プロンプトがどこで劣化しうるか」のチェックリストです。
| 項目 | 求められること | 典型的な失敗例 |
|---|---|---|
| 明確性 Clarity | このタスクの解釈が一意に決まり、モデルが推測する必要がない | 動詞が曖昧:「処理しておいて」「これを改善して」 |
| 具体性 Specificity | 出力への要求が測定可能であること。形容詞だけにせず、モデルに「何が完了か」を推測させない | 「簡潔な要約を書いて」ではなく、「平易な言葉で3文の要約を書いて」 |
| 構造 Structure | 指示が論理的な順序で並んでいる:役割が先、背景が二番目、タスクが三番目、形式が最後 | 形式の指示がタスクの後ろに埋もれている、役割が書かれていない、制約が散在している |
| ロバスト性 Robustness | 起こりうる問題のある状況それぞれに、どう対処するかが明確に書かれている | プロンプトはユーザーがきれいな入力をしてくれる前提。実際のユーザーは何でも入力する |
4項目で最下位なのは最後の項目、ロバスト性英語では Robustness。この項目が問うのは、「プロンプトが『想定外の入力にどう対処すべきか』を指示しているかどうか」です。ユーザーが要件を明確にしない場合に何を返すか、入力が空の場合、フォーマットが完全に間違っている場合。これらのケースを一つ一つ指定して対応を明記する必要があり、「適切に対処してください」のような曖昧な一文はカウントされません。、つまりそのプロンプトが「想定外の入力にどう対処するか」を指示しているかどうかです。1018件の平均点はわずか31.5点でした。
想定外への対応指示は、普段は発動せず、テストもできない
なぜこの項目が一番悪いのでしょうか? それは、この項目に書かれている指示が、通常の運用では一度も出番がないからです。
この項目が扱うのは、それぞれの異常系で何を返すか、です。公開されている例では「ユーザーが何をしたいか明確でない場合」が明記されていますが、他にも入力が空、フォーマットが完全に崩れている、などがあります。しかし、自分で試す時に入力するのは、きちんとした内容ばかり。この3つのケースには一度も遭遇しません。
だからこそ、プロンプトを「ダイエット」する時、最初に狙われるのがこの部分です。トークンを消費するのに、効果を発揮しているのを見たことがない。削除すれば、プロンプトはすぐに短くなり、テストも一発で通ります。この2つは同じ根を持っています。最も弱く、最も削除されやすい。その理由は同じです。
車のシートベルトとエアバッグのようなものです。普段運転している時は使うことがなく、ある種の「無駄な重さとコスト」に見えます。しかし、事故の瞬間はこれに全てを依存します。省いた当日は何も感じません。代償が請求されるのは、事故が起きた日なのです。
削除した1文の指示で、スコアが14点下落
このワークフローを導入後、ある「整理」タイプの編集をその場で阻止しました。変更は無害に見えたのに、スコアは14点下落しました。下落の原因は、ある保険的な指示を削除したことでした。
原文では、その指示を load-bearing(直訳:耐荷重の)と表現しています。このプロンプトにおいては、次の意味です。それは単なる「余分な補足説明」に見えて、実は構造を支える重要な一文でした。「ユーザーが不完全な入力をした場合、何を返すか」を定義していたのです。それを取り除くと、代わりに機能するものが何もありません。
削除後、正常な入力は依然として完璧に動作しました。テストは全てパス、ログもクリーン。ただ、不完全な入力だけが曖昧な回答を受け取るようになり、それが後にサポートチケットとなったユーザーたちです。
10秒で見つけられるのに、サポートチケットをもう1件食べるよりはましだ。
Ten seconds to see it, instead of another support ticket.
明確にしておきますが、プロンプトを削ること自体は悪いことではありません。当サイトで解説した Anthropic 公式のプロンプトガイドも、削り方を教えています。問題は、削る前に「何かがあなたを受け止めてくれる仕組み」があるかどうかです。
コードの assert は使えない。だから採点に切り替えた
投稿への最初の反応は、次のような疑義でした。
古のソフトウェア工学のプラクティスを再パッケージしただけに聞こえる。何が新しいの?
コードを変えて結果が変わったら、それって回帰テストでしょ? それも古いよね。AI のせいで古いプラクティスを全部捨てたのに、今度は一つずつ再発見してる :D
r/PromptEngineering 読者のコメント
この疑義は半分正しい。バージョン管理は確かに新しいものではありません。プロンプトのために本当に新しく作られたのは、たった一つの層です。
コードのテストでは assert output == expected と書けば、機械が直接「結果がこれと等しいか」を判断できます。このチェックが成立するのは、答えが一意に定まるからです。
モデルが生成するテキストには一意な答えがありません。同じプロンプトでも実行するたびに言い回しが異なります。この assertassert は日本語では「アサーション」、自動テストにおける「結果がこれと等しくなければならない」という検査文のことです。例えるなら、採点のようなものです。選択問題は正解と照合できますが、作文問題はそれができず、採点基準で評価するしかありません。はプロンプトではそのまま機能しません。
そこで、プロンプトに固定の「試験問題セット」を用意します。同じ入力セットを常に与え、実行結果をチェックするのです。ただし、このチェックは採点に置き換えられました。生成された答えを採点基準に当てはめ、0から100のスコアを出します。これを自動実行し、新しいバージョンが本番に置き換わる前に実施します。
しかも、スコアは項目ごとに出るので、どの部分が壊れたかを正確に把握できます。「想定外の入力への対応」という項目が61から17に落ちた、と。項目単位で出るので、総合点だけではありません。修正も的確にできます。3行目の指示を直せば、この項目は17から61に戻り、他は触らずに済みます。
一言で言えば、これは答えが一意でないものを対象にした回帰テストです。デプロイ前に旧版と比較し、退行していたらブロックします。
4ステップのワークフロー:バージョン、diff、採点、エンドポイント
以下の4ステップの骨格は元記事からのものです。各ステップの「実装例」は、当サイトがこの骨格に基づいて整理した具体的な操作イメージであり、元記事の文言そのものではありません。
事故が起きるまで、これらのプロンプトは Google ドキュメントに置かれていました:Version A、Version A-final、Version A-final-2。あのパターン、心当たりがあるでしょう。うまく機能しているうちは問題ない。機能しなくなるまで。根本原因が分かった以上、ワークフロー全体を変える必要があります。この4つのステップはツールに依存しません。まずは特定の製品に依存しない形で説明します。
1 · すべてのプロンプトにバージョン番号
バージョン番号はv4、v5のようなものです。2つのバージョン間で diff(行ごとの差分、変更行が漏れなく表示される)を取得できます。これは、コードを main ブランチにマージする前に行ごとのレビューと同じです。「final_v2」のような命名ではこの機能は得られません。
- 1プロンプト=1ファイル。複数を1つのドキュメントにまとめないこと。まとめると個別比較ができない。
- ファイルにはプロンプト本文に加えて、バージョン番号、今回の変更点、前回のテスト結果を記録する。
- 変更は git(または diff が取れる場所)に入れる。唯一の基準は「v5 は v4 と比べて具体的にどの行が変わったのか」に常に答えられること。
2 · 1つのバージョンを本番版としてマーク
「現在本番で動いているのはどのバージョンか」に一意の答えを持たせ、それ以外はすべてドラフトとします。明示的に切り替えない限り本番には反映されません。例外はありません。「明らかに問題ない」小さな変更にも例外はありません。
- 「現在の本番版 = v4」と明確に記録する場所を設ける。フィールド1つで十分。記憶やチャット履歴に頼らない。
- ドラフトは何度でも変更可能。このマークを触らない限り、本番には影響しません。
3 · 本番版を切り替える前に、新旧両方のスコアを比較する
事前に合格ラインを設定し、新版が下回ったら赤字で本番不可にします。このステップが、あの削除事故を防げたはずのステップです。実ユーザーに届く前に。
- 試験問題セットを用意する:実際のユーザーが入力した質問を問題にします。その中に、不完全なもの(空、情報不足、形式が崩れている)を数件入れておきます。これは、最も削除されやすい「想定外への対応力」を探るための専用問題です。問題数は元記事では指定されていないので、ご自身のシナリオに合わせて決めてください。
- 同じ問題セットを、新旧両バージョンで実行し、それぞれスコアを出します。
- 2つのことを比較する:合計点が設定したラインを下回っていないか。各項目でどれだけ下がったか。合計点は「壊れた」ことしか教えませんが、項目別は「どこが壊れたか」を教えます。
- 下回ったら停止する。注意喚起して続行、ではありません。このステップで止めなければ、最初の3ステップは無意味です。
4 · 本番版はスラッグエンドポイント経由で提供する
本番で動くバージョンをコードにハードコードしない、ということです。新しいバージョンを本番版としてマークすれば、アプリケーションはそれを自動的に取得します。再デプロイは不要です。戻したい場合は、古いバージョンを再マークするだけ。深夜に git 履歴を探りながらサポートチケットを眺める必要はありません。
- コードに残すのは名前(例:
support-agent)だけ。プロンプト本文は書きません。 - アプリはその名前を使って現在の本番版を取得します。バージョンの切り替えは「現在の本番版 = ?」のマークを変更するだけです。
- このステップで得られるものはロールバック速度です。問題発生時、古いバージョンを再マークすれば即戻せます。アプリの再デプロイは不要です。
- 現在の本番版 v4 のコピーを作成し、変更を加えて v5 として保存します。
- diff を取得し、自分で何行変更したかを確認します。
- 同じ問題セットを v4 と v5 に与え、それぞれスコアを出します。
- 比較:合計点がラインを下回ったらその場で停止。項目別も確認し、どの項目が下がったかを見ます。あの14点の下落はこのステップで発見されました。10秒です。
- 合格したら、「現在の本番版」を v4 から v5 に変更します。
- アプリが次回プロンプトを取得する際、自動的に v5 を取得します。再デプロイは不要です。
- 万一、本番で問題が発生したら、マークを v4 に戻すだけ。数秒でロールバックできます。
合格ラインはプロンプトごとに個別設定。共通基準は設けない
では、このラインはどこに引くのでしょうか? 2つを重ねて使います:
1つのハードル:本番に上げるにはX点以上が必須。このXはプロンプトごとに個別設定され、全体共通の数字ではありません。生成、抽出、分類といったタスクはそもそも基準となるレベルが異なるため、同じ数字で測ると必ず誤差が出ます。
その上に、相対的な下落幅チェックを重ねます。これは「合格圏内だが退行している(リグレッション)」ケースを捉えるためのものです。絶対点が足りているかではなく、前回版と比べてどれだけ下がったかを見ます。前述の14点下落は、このレイヤーで検出されました。
もう1つの心構えとして、赤字表示は「見える化」のためであり、教条ではありません。時には5点下がっても有益な場合があります。新版がよりシンプルで、実行コストが安いなら。合格ラインはガードレールであり、最終判断は人が行います。真の利益はここにあります。何かを本番に上げる前に、自分が何を変えたかを知っていることです。
では、スコアが下がったのに、新版の出力が明らかに良いと感じる場合はどうでしょう? リライト版の診断はこうです:おそらく、あなたの試験問題セットが実際の利用状況を反映していない。実際のユーザーが尋ねる内容が、テスト入力に含まれていないのでしょう。これはリライト側の判断であり、元記事の投稿者はこの問いに直接答えていません。
ツールを買わない場合:git と採点スクリプト
このワークフローは特定の製品に依存しません。git と採点スクリプト、そして Makefile があれば、大部分をカバーできます。具体的には、各プロンプトをファイルとして git に入れます。ファイルにはバージョン番号と前回のテスト結果も一緒に記録します。小さなスクリプトを書き、同じ入力セットで2つのバージョンを実行し、差分を出力します。実行結果も一緒に保存しておけば、どのバージョンから品質が劣化し始めたのかが分かります。
実行タイミングについて:本番への切り替え前には必ず実行します。有害な変更をブロックするのはこのタイミングです。変更が頻繁な場合は、変更のたびに採点すれば、後戻り作業を省けます。また、本番版自体への定期的なスポットチェックも有効です。劣化は必ずしもあなたの変更とは無関係ではなく、モデルがバージョンアップした、ユーザーの質問内容が変わった、といった場合もあります。
実行頻度はスモークテストレベルで、期末テストのようにまとめてやってはいけません。重要な変更があるたびに実行します。大きなリリース前にまとめて実行するのではなく。これが決めるのは、問題を発見するまでの時間です:数時間か、それとも冒頭のように3日か。
明確性 Clarity このタスクの解釈が一意に決まり、モデルが推測する必要がない
失敗例:動詞が曖昧、「処理しておいて」「これを改善して」
具体性 Specificity 出力への要求が測定可能であること。形容詞だけにしない
失敗例:「簡潔な要約を書いて」ではなく「平易な言葉で3文の要約を書いて」
構造 Structure 役割が先、背景が二番目、タスクが三番目、形式が最後
失敗例:形式の指示がタスクの後ろに埋もれる、役割が書かれていない
ロバスト性 Robustness 起こりうる問題状況に対処法が明記されている
失敗例:ユーザーがきれいな入力をしてくれる前提
Before(18/100) Help the customer with their issue and be professional. After(79/100) You are a support specialist. Read the message and return: summary (1 sentence), customer_intent (only what's stated), urgency (urgent / normal / low). If no clear request: intent = 'unclear', urgency = 'normal'.
- uses: FranciscoFerreiraff/prompteval-action@v1
with:
api_key: ${{ secrets.PROMPTEVAL_API_KEY }}
prompt_file: prompts/support-agent.md
baseline_slug: support-agent
min_score: 75
fail_on_conflict: true
プロンプト劣化はエラーを出さない。サポートチケットで気づく
開発者が1文の削除ミスで3日間苦しみ、その対策を4つの関門に分解。1枚の図で解説します
↓ 1ページで完結・動く図あり
「無駄な一文」を削除 → 3日後にサポートチケットで判明
ある開発者が、ユーザー向けに稼働中のプロンプトを修正し、「無駄に見える数行」を削除。ついでにトークン(モデルが処理するテキストの単位)も節約。Reddit の r/PromptEngineering に投稿された開発者の振り返りです。実際のユーザー向けにプロンプトを運用しているなら、この方法は役立ちます。
デプロイ後、エラーなし、アラートなし、モニタリングも全て正常。3日後にサポートチケット発生。応答が曖昧で的外れ、時には自信満々に間違えていました。
コードの破損は叫ぶが、プロンプトの破損は無言
同じ「壊し方」でも、違うのは壊れた後に誰かが教えてくれるかどうか。コードは例外・コンパイルエラー・クラッシュ。プロンプトはそれらしいテキストを返し、プログラムは正常な結果を受け取ります。ログもクリーン、テストも通過。
目視も通用しません。依然として「もっともらしい答え」であり、ただ悪くなっているだけ。自分が期待を持って見ているので、どう見ても「問題ない」と映ります。劣化が表面化するのは、実ユーザーが多様な質問をした時。その形がサポートチケットです。
1018件のプロンプト採点、最も低いのは「想定外の入力への対応」
これは一人のミスではありません。1018件を採点した結果、4つの次元で最も悪かったのはロバスト性(プロンプトが「想定外の入力にどう対処するか」を指示しているか。例:ユーザーの要求が不明確、入力が空、形式が崩れている)。平均点はわずか31.5/100。
この2つは同根です。ロバスト性はエッジケースを扱うため、正常な入力ではテストされず、テストも正常経路を通ります。削除しても、その場では検出できません。データは投稿者自身の採点プラットフォームによるもので、第三者による検証はありません。
回帰テストとは違う:答えが一意でない → 採点でしか検証できない
コメント欄の最初の疑問:これって回帰テストじゃないの? コードのテストは「結果はこれと等しくなければならない」(assert)と書けますが、前提は答えが一意であること。モデルが生成するテキストには一意な答えがなく、同じプロンプトでも実行ごとに言い回しが異なります。このチェックはそのままでは機能しません。
スコアは次元別に出るため、どの部分が壊れたかが分かります。ワークフローは4ステップ:
- バージョン管理。行単位の変更記録を残す
- 本番版を1つ指定。他はドラフト
- 切替前に新旧両方を採点比較し、ラインを下回ったら本番不可。あの編集事故を防げたのはこのステップ
- 本番版は名前で取得。ハードコードしないので、ロールバックは旧版を再マークするだけ
合格ラインはプロンプトごとに個別設定し、共通の数値にはしません。さらに前回版からの下落幅チェックを重ねます。ツールを買わなくても、git と採点スクリプトで十分。本文にそのまま使える GitHub Action 設定があります。
- × ユーザーが不明確…
- × 入力が空…