プロンプトエンジニアリング実践:業務で使えるプロンプトの書き方と検証方法
業務用プロンプトを5つの部品で組み立てる方法と、正解付きの評価セットで版を比べる検証手順を実例で解説。丁寧に書いた版が小型モデルでは負けた実測結果と、その読み解き方も載せています。
プロンプトエンジニアリングとは、LLMに渡す指示・前提情報・出力形式を設計し、実際の入力で結果を測りながら改善していく作業です。チャットで上手に質問するコツとして語られがちですが、業務システムに組み込むプロンプトは同じ文面が何百回、何千回と実行されるため、「書き方」と「検証の仕組み」を1セットで持つ必要があります。
ここでは、情シスの業務でよく使う要約と分類を題材に、プロンプトを組み立てる部品、曖昧な指示の直し方、正解付きのデータで版を比べる検証スクリプトまでを具体的に示します。検証結果の数値は、Ollama上の小型モデル qwen2.5:0.5b で実際に測ったものです。
この記事でわかること
- 業務用プロンプトを組み立てる5つの部品と、それぞれが抜けたときに起きること
- 「要約して」を障害履歴にそのまま載せられる指示に直す具体例
- 評価セットを作り、プロンプトの版を数値で比べるPythonスクリプトと実行結果
- 温度・モデル更新・入力形式の変化など、結果がぶれる原因と対処
- プロンプトをGitで管理し、変更のたびに回帰テストする運用
プロンプトエンジニアリングは「書き方」と「検証」の2つで1セット
LLMの出力は、同じ文面でも入力が変われば品質が変わり、モデルの版が変われば同じ入力でも変わります。そのため「この書き方が効く」という経験則は、自分の業務データで確かめるまでは仮説にすぎません。個人がチャットで使う場合は、結果を見て言い直せば済みます。一方、問い合わせの自動分類や議事録の定型要約のようにプロンプトをシステムに組み込む場合は、言い直す人がいないため、事前に測った精度が運用品質の上限になります。
実務での進め方は、プロンプトを書く、20〜30件の正解付きデータで測る、誤りの内訳を見て直す、の繰り返しです。以降の章もこの順に並べ、検証の例では説明のため12件に絞っています。
業務プロンプトを組み立てる5つの部品
業務で使うプロンプトは、次の5つの部品に分けて考えると抜けが見つかります。「あなたは優秀な〇〇です」という役割の一文より、ここに挙げた部品のほうが結果への影響は大きくなります。
| 部品 | 書く内容 | 抜けたときに起きること |
|---|---|---|
| 目的と読み手 | 誰が何に使う出力か(例:社内ポータルの障害履歴に載せる) | 一般論や冗長な前置きが増える |
| 入力データ | 処理対象を <mail> などのタグで囲んで渡す | 指示とデータの境界があいまいになり、本文中の命令文に引きずられる |
| 判断基準 | 分類の定義、迷ったときの優先順位 | 境界のケースで結果が揺れる |
| 出力形式 | 見出し、項目、JSONスキーマ、文字数 | 後段のプログラムで読み取れない |
| 例外時の扱い | 情報が足りないときは「記載なし」、該当なしは空配列 | もっともらしい内容で空欄を埋める(作り話) |
5つ目は特に効きます。LLMは空欄を「ありそうな内容」で埋めるように続きを生成するため、「書かれていなければ記載なしと書く」という出口を明示しない限り、報告にない発生時刻や原因をもっともらしく補ってしまいます。
曖昧な指示を具体的にする:障害報告の要約
利用者から届いた障害報告メールを、社内ポータルの障害履歴に転記する作業を例にします。最初によく書かれるのは「このメールを要約して」の一行です。これでは、要約の長さ、残すべき項目、書かれていない情報の扱いがすべてモデル任せになり、実行のたびに形が変わります。5つの部品に沿って書き直すと次のようになります。
あなたは情報システム部で障害対応の記録を作る担当です。
<report> タグ内の障害報告を、社内ポータルの障害履歴に載せる形に整理してください。
# 出力形式(この5行だけを出力する)
- 発生日時:
- 影響範囲:(部署・人数・システム名)
- 現象:(利用者から見えた症状を1文で)
- 原因:(確定していなければ「調査中」)
- 対応状況:
# 守ること
- 報告に書かれていない項目は「記載なし」と書き、内容を補わない
- 推測を含める場合は、その行の先頭に「推測:」と付ける
- 人名は部署名に置き換える
<report>
{本文}
</report>変更点はそれぞれ理由があります。出力形式を行単位で固定したのは、後で正規表現で項目を取り出せるようにするためです。「記載なし」と「推測:」の2つの出口は、空欄を補う動作と推測を区別させるためのものです。人名を部署名に置き換える指示は、障害履歴が全社に公開される前提から来ています。読み手と用途が決まると、必要な指示は自然に決まります。
書き方について、仕組みから効く理由が説明できるものを挙げておきます。
- 禁止だけでなく代わりの動作を書く:「推測しないで」だけだと、書くべき内容がない場面でモデルの行き場がなくなる。「記載なしと書く」のように置き換え先を示す
- 長い資料は前に、指示と質問は最後に置く:生成は末尾から続くため、直前にある指示のほうが資料の中に埋もれにくい。効果は自分の評価セットで確かめる
- 例(few-shot)は境界のケースを選ぶ:例に「障害」ばかり並べると出力もそちらに寄る。迷いやすい組み合わせ(「直りました」という報告など)を1件ずつ入れる
- 手順が多い作業は呼び出しを分ける:抽出、判定、文面作成を1つのプロンプトに詰めると、どこで間違えたか分からなくなる。分けておけば段階ごとに評価できる
- 出力はJSONスキーマで縛る:Ollamaの
formatやOpenAI互換APIの構造化出力を使えば、ラベルを列挙値に限定でき、パースの失敗がなくなる
出力をJSONスキーマで縛れるのは「形式」だけです。スキーマどおりのJSONが返っても、分類や抽出の中身が正しいとは限りません。形式の検証(パースできるか)と内容の検証(正解と一致するか)は別の仕組みとして用意してください。
検証の手順:評価セットで版を比べる
プロンプトの良し悪しは、正解付きのデータ(評価セット)で数えて決めます。ここでは、情シス宛てのメールを「障害」「依頼」「質問」「対応不要」の4つに振り分けるプロンプトを2つの版で比べます。
- 過去の問い合わせから20〜30件を選び、担当者が正解ラベルを付ける。迷いやすいもの(解決済みの報告、作業を伴う質問)を意図的に混ぜる
- 評価セットはプロンプトと同じリポジトリにJSONLやPythonのリストで置き、個人情報は置き換えておく
- 版ごとに全件を実行し、正解数と「どの正解がどのラベルに化けたか」を記録する
- 誤りの内訳を見て判断基準を直し、同じ評価セットでもう一度測る
v1は分類名を並べただけの一文、v2は5つの部品のうち判断基準と入力データの区切りを加えた版です。出力はどちらもJSONスキーマで4つのラベルに限定し、差がプロンプトの中身だけから出るようにしました。
import json
from collections import Counter
from ollama import Client
MODEL = "qwen2.5:0.5b"
LABELS = ["障害", "依頼", "質問", "対応不要"]
SCHEMA = {"type": "object",
"properties": {"label": {"type": "string", "enum": LABELS}},
"required": ["label"]}
# 例: 実際は過去の問い合わせから選び、正解は担当者が付ける(12件を抜粋)
CASES = [
("今朝9時ごろから経費精算システムにログインすると500エラーが出ます。営業部の数人が同じ状態です。", "障害"),
("来週月曜に入社する山田さんのPCとアカウントを準備してください。", "依頼"),
("Teamsの会議を録画したファイルはどこに保存されますか?", "質問"),
("先ほどのプリンターの件、再起動したら直りました。ありがとうございました。", "対応不要"),
# ……残り8件も同じ形式で並べる
]
PROMPTS = {
"v1": "次のメールを「障害」「依頼」「質問」「対応不要」のどれかに分類してください。\n\n{text}",
"v2": (
"あなたは情報システム部の受付担当です。<mail>タグ内のメールを、次の定義で1つに分類します。\n"
"- 障害: いま何かが使えない、遅い、壊れているという報告。直ったという連絡は含めない\n"
"- 依頼: 情シスに作業(準備、権限の変更、削除など)を求めている\n"
"- 質問: 手順やルールを尋ねているだけで、作業は求めていない\n"
"- 対応不要: お知らせ、自動通知、お礼、解決済みの報告\n"
"迷ったときは「情シスが作業をする必要があるか」で判断します。\n"
"<mail>\n{text}\n</mail>"
),
}
def classify(client, version, text):
resp = client.chat(
model=MODEL,
messages=[{"role": "user", "content": PROMPTS[version].format(text=text)}],
format=SCHEMA,
options={"temperature": 0, "num_predict": 32},
)
return json.loads(resp.message.content)["label"]
client = Client(timeout=120)
for version in PROMPTS:
hits, misses = 0, Counter()
for text, expected in CASES:
got = classify(client, version, text)
if got == expected:
hits += 1
else:
misses[f"{expected}→{got}"] += 1
print(f"{version}: {hits}/{len(CASES)} 正解 誤りの内訳: {dict(misses)}")検証結果:丁寧に書いた版が負けた
qwen2.5:0.5b で12件を分類した結果が次の表です(例:CPU上の5億パラメータのモデル、温度0で各版1回ずつ実行)。v3は、v2から判断の優先順位を書いた1行を外し、ラベルごとの例を1件ずつ会話の形で前置きした版です。
| 版 | 内容 | 正解数 | 誤りの内訳 |
|---|---|---|---|
| v1 | 分類名を並べた1文 | 6/12 | 質問→依頼 2、質問→障害 1、対応不要→障害 3 |
| v2 | 定義・判断の優先順位・タグでの区切りを追加 | 3/12 | 障害→依頼 3、質問→依頼 3、対応不要→依頼 3 |
| v3 | v2から優先順位の1行を削除し、例を4件追加 | 6/12 | 障害→依頼 2、質問→依頼 3、対応不要→依頼 1 |
SHOTS = [ # 評価セットとは別の文面を、ラベルごとに1件ずつ
("社内Wikiが開けません。ほかの人も同じです。", "障害"),
("会議室Bのモニターを1台追加で貸してください。", "依頼"),
("VPNのクライアントは自分で更新してもよいですか?", "質問"),
("【定期】今月のパッチ適用は完了しました。", "対応不要"),
]
messages = [{"role": "system", "content": SYSTEM_V3}] # 定義だけを書いたシステムプロンプト
for mail, label in SHOTS:
messages.append({"role": "user", "content": f"<mail>\n{mail}\n</mail>"})
messages.append({"role": "assistant",
"content": json.dumps({"label": label}, ensure_ascii=False)})
messages.append({"role": "user", "content": f"<mail>\n{text}\n</mail>"})定義を丁寧に書いたv2は、v1より正解が減りました。誤りの内訳を見ると9件すべてが「依頼」に寄っており、「迷ったときは情シスが作業をする必要があるかで判断」という1行が、小さなモデルには「作業」への強い引き寄せとして効いたと読めます。この1行を外して例を加えたv3は6件に戻り、対応不要は3件中2件を正しく分けましたが、「質問」はどの版でも1件も当たりませんでした。
ここから言えることは2つです。1つは、良かれと思って足した指示が逆効果になるのは珍しくなく、正解数だけでなく「どのラベルに寄ったか」を見れば原因の行を絞り込めることです。もう1つは、プロンプトでは越えられないモデルの能力の上限があることです。このモデルは、手順を尋ねる「質問」と作業を求める「依頼」を最後まで区別できませんでした。この場合の次の一手は、同じ評価セットのまま大きいモデルに替えて測ることで、文面の調整を続けることではありません。評価セットがあれば、この見切りを感覚ではなく数字で付けられます。
few-shotの例に評価セットと同じ文面を使うと、答えを教えた状態で採点することになり、正解数が実力以上に出ます。例は評価セットとは別に用意し、評価セットは改善の途中で中身を見て入れ替えないようにしてください。
結果がぶれる原因と対処
評価で良い数字が出ても、本番で結果が変わる原因はいくつかあります。原因ごとに打てる手を整理します。
| 原因 | 起きること | 対処 |
|---|---|---|
| 温度(temperature)などのサンプリング設定 | 同じ入力でも実行ごとに出力が変わる | 分類・抽出は温度0にする。温度0でも完全には一致しないAPIがあるため、評価は複数回実行して揺れも見る |
| モデルの更新 | 提供元がモデルを差し替え、同じプロンプトの精度が変わる | 日付付きのモデルID(例:gpt-5-mini-2025-08-07)やOllamaのタグを固定し、更新前に評価セットを流す |
| 入力形式の変化 | PDFの抽出方法や、メールの署名・引用の有無で精度が落ちる | 評価セットに実際の形式のデータを入れる。前処理(署名や引用の除去)もプロンプトと一緒に管理する |
| コンテキストの溢れ | 長い入力の一部が切り捨てられ、指示が効かなくなる | 入力トークン数を記録し、上限に近いものは分割するか要点を抽出してから渡す |
プロンプトをGitで管理し、回帰テストする
システムに組み込むプロンプトはコードと同じ扱いにします。文面をソースに直書きせず prompts/incident_summary.txt のようなファイルに分け、評価セットと評価スクリプトと同じリポジトリに置きます。変更はプルリクエストで出し、差分と一緒に評価結果(正解数と誤りの内訳)を貼る決まりにすると、レビューする側が「良くなったのか」を数字で判断できます。
本番の実行ログには、どの版のプロンプトで生成したかを必ず残します。ファイルのハッシュ値やGitのコミットIDを出力と一緒に記録しておけば、「先週から分類がおかしい」という問い合わせに対して、プロンプトの変更が原因かモデルの変更が原因かを切り分けられます。モデルやプロンプトを更新するときは、評価セットの正解数が基準(例:現行版の正解数以上)を下回ったらリリースしない、という回帰テストをCIに入れておくと安全です。
プロンプトにAPIキーや社内システムの接続情報を書くのは避けてください。プロンプトは実行ログや外部サービスの履歴に残りやすく、利用者からの質問で引き出されることもあります。「このデータは社外秘なので出力しない」といった指示も、それ自体は漏えい対策になりません。アクセス制御はアプリ側のコードで行います。
よくある質問
プロンプトエンジニアリングは終了しましたか?
「魔法の言い回しを探す」という意味では役割が小さくなりましたが、業務に組み込むための設計と検証の仕事はむしろ増えています。モデルが賢くなっても、目的、判断基準、出力形式、例外時の扱いは業務側でしか決められず、評価セットで精度を測る作業も残ります。
プロンプトエンジニアに向いている人は?
業務の判断基準を言葉にできる人です。「この問い合わせはなぜ依頼なのか」を他人に説明できる担当者は、分類の定義や境界の例を書けます。加えて、結果を正解と比べて数える地道な作業をいとわないこと、少しのPythonで評価を自動化できることが強みになります。
プロンプトエンジニアリングの年収は?
プロンプトの設計だけを担う求人は少なく、AIアプリの開発、業務改善、データ分析などの職種の中で求められるスキルとして扱われるのが一般的です。年収は職種と経験で決まるため、求人票では肩書きより、評価や運用まで担当範囲に含まれるかを見て比較してください。
プログラマーはAIに奪われる仕事ですか?
コードの雛形を書く作業はAIに任せやすくなっていますが、何を作るかの決定、生成されたコードの検証、既存システムとの整合、障害時の判断は人の仕事として残ります。プロンプトの評価セットを作る作業も、業務とコードの両方を理解している人にしかできません。
まとめ
業務用のプロンプトは、目的と読み手、区切った入力データ、判断基準、出力形式、例外時の扱いの5つで組み立て、特に「書かれていなければ記載なし」という出口を明示します。良し悪しは正解付きの評価セットで数えて決め、プロンプトと評価セットをGitで一緒に管理して、プロンプトやモデルを変えるたびに同じ評価を流してください。
次に読む
- LLMとは?仕組み・得意なこと・苦手なこと:プロンプトの効き方を左右する、次のトークン選びの仕組み
- OllamaのPythonライブラリとModelfileで社内向けLLMツールを作る:評価したプロンプトをModelfileに固定して配る方法
- 生成AIのセキュリティリスクと対策:区切りタグだけでは防げないプロンプトインジェクションへの備え