プロンプト設計はセンスじゃない——チーム運用2年で見えた構造化の話
「プロンプトってセンスじゃないですか?」と後輩に言われてムッとした話から始まります。本番LLM運用で実際に壊れたケース、コストが半減した改善例など、教科書には載っていない実践知見をまとめました。
先日、チームの後輩に「プロンプト設計って結局センスじゃないですか?」って言われて、正直ちょっとムッとした。センスじゃなくて、構造化された思考の積み重ねなんだよ、と言い返したかったけど、うまく説明できなかった。
そのリベンジも兼ねて、今回は2026年時点でうちのチームが本番プロダクトにLLMを組み込んで運用してきた中で見えてきた、プロンプト設計の実践的な知見をまとめてみる。教科書的な「Chain-of-Thoughtとは何か」みたいな話じゃなくて、実際にどういうケースで何が刺さって、何が刺さらなかったかの話をしたい。
2026年時点のLLM環境と「プロンプト」の位置づけ変化
2026年に入って、LLMを取り巻く環境はだいぶ変わったと感じている。GPT-4o、Claude 3.7 Sonnet、Gemini 2.0 Flash、そしてローカル動作も現実的になったLlama 4系と、選択肢が増えすぎて逆に困るくらいになった。
一方でモデルが賢くなったことで「プロンプトエンジニアリングなんてもう不要」説も出てきた。確かに昔ほど繊細に調整しなくてもそれなりの出力が得られるようになった。でも実務規模で精度を担保しようとすると、依然としてプロンプト設計が品質のボトルネックになる。
特にうちのチームで顕著だったのは、「1回試して動いた」レベルのプロンプトを本番に入れたら、エッジケースで盛大に壊れるケース。個人の検証では問題なくても、実際のユーザー入力の多様性に耐えられなかった。
あと、コスト観点も無視できない。同じタスクでも、プロンプトの書き方次第でトークン数が2〜3倍変わることがある。月で計算するとシャレにならない差になる。実際に計測してみると、タスクによっては改善後に半分以下まで落とせた。
xychart-beta
title "プロンプト設計の改善前後トークン消費比較(月間・千トークン)"
x-axis ["ドキュメント抽出", "コード生成", "分類タスク", "要約", "QA応答"]
y-axis "トークン数(千)" 0 --> 1200
bar [980, 840, 720, 1100, 650]
bar [420, 390, 180, 510, 280]
要約タスクなんかは改善後に半分以下まで落ちてる。これはローカルLLM構築完全ガイド2026でも触れたけど、コスト最適化はトークン設計から始まる、というのが今のうちのチームの共通認識になっている。
実際に本番で使っているプロンプト設計パターン
理論じゃなくて、うちのチームが実際に効果を確認した3つのパターンを紹介する。どれも「試したら思ったより効いた」という感想が正直なところで、最初から確信を持って導入したわけじゃない。
パターン1: Structured Output + 厳密な型定義
2025年後半から本格化した流れで、「自然言語でいい感じに答えてください」ではなく、出力形式をJSONスキーマで厳密に定義する手法。OpenAI APIのStructured Outputsや、Anthropicのtool_use経由でほぼ必ず使っている。
from openai import OpenAI
from pydantic import BaseModel, Field
from typing import Literal
client = OpenAI()
class ReviewAnalysis(BaseModel):
sentiment: Literal["positive", "negative", "neutral"]
confidence: float = Field(ge=0.0, le=1.0)
key_issues: list[str] = Field(max_items=5)
requires_escalation: bool
summary: str = Field(max_length=200)
def analyze_review(review_text: str) -> ReviewAnalysis:
response = client.beta.chat.completions.parse(
model="gpt-4o-2026-04",
messages=[
{
"role": "system",
"content": """あなたはカスタマーサポートの品質分析AIです。
与えられたレビューを分析し、以下の観点で評価してください:
- センチメント: positive/negative/neutralの3値
- 確信度: 0.0〜1.0のスコア
- 主要課題: 最大5個
- エスカレーション要否: クレーム・法的リスクが含まれる場合true
- サマリー: 200文字以内
重要: key_issuesは具体的な問題点のみ。抽象的な表現は禁止。"""
},
{
"role": "user",
"content": f"レビュー:\n{review_text}"
}
],
response_format=ReviewAnalysis
)
return response.choices[0].message.parsed
# 実行例
result = analyze_review("商品は届きましたが、梱包がひどく箱がボコボコでした。中身は無事でしたが二度と注文しません")
print(result)
# ReviewAnalysis(
# sentiment='negative',
# confidence=0.92,
# key_issues=['梱包品質の問題', '再注文意欲の喪失'],
# requires_escalation=False,
# summary='梱包状態に強い不満。商品本体の損傷はないが顧客維持リスクあり。'
# )
最初はオーバーエンジニアリングかと思ってた。でも実際に運用したらパース失敗率が自由形式比で1/20以下になって、下流処理の安定性が劇的に上がった。Pydanticとの組み合わせが地味に便利で、型バリデーションもまとめてやってくれる。「なんか出力がおかしい」という問い合わせがほぼゼロになったのは個人的に感動した。
パターン2: Few-Shot ExampleはDynamic Retrievalで注入する
静的なFew-Shotの限界に気づいたのが2025年末頃だった。プロンプトに固定で3〜5例を書く方法だと、入力データの分布がズレると急に精度が落ちる。最初はなぜ落ちているかすら気づかなくて、原因特定に時間を取られた苦い経験がある。
うちでは類似した過去の処理結果をベクトルDBで管理して、実行時に類似度の高い例を動的に取得してプロンプトに挿入する方式に切り替えた。
import anthropic
from typing import Any
# ベクトルDB(この場合はQdrant想定)から類似例を取得する関数
def retrieve_similar_examples(
query: str,
vector_client: Any,
top_k: int = 3
) -> list[dict]:
"""
クエリに類似した過去の処理例をベクトル検索で取得する
"""
# 実際の実装はベクトルDB依存
results = vector_client.search(
collection_name="prompt_examples",
query_text=query,
limit=top_k
)
return [{"input": r.payload["input"], "output": r.payload["output"]} for r in results]
def dynamic_few_shot_prompt(user_input: str, examples: list[dict]) -> str:
"""動的Few-Shotプロンプトを構築"""
example_block = "\n\n".join([
f"例{i+1}:\n入力: {ex['input']}\n出力: {ex['output']}"
for i, ex in enumerate(examples)
])
return f"""以下の例を参考に、同じ形式で出力してください。
{example_block}
---
本番入力: {user_input}
出力:"""
client = anthropic.Anthropic()
# 実際の呼び出し
examples = retrieve_similar_examples(user_input, vector_client)
prompt = dynamic_few_shot_prompt(user_input, examples)
message = client.messages.create(
model="claude-3-7-sonnet-20260301",
max_tokens=1024,
messages=[{"role": "user", "content": prompt}]
)
ベクトルDBの選定で迷っている方はベクトルDB比較2026|マルチモーダルEmbedding対応の最適選定ガイドも見てみてほしい。実装コストはそれなりにかかるけど、バリエーションの多いタスクなら投資に見合う効果があると思う。
パターン3: Chain-of-Thoughtの「明示的ステップ分解」
CoTは2023年頃から知られているけど、2026年でも依然として有効なパターンだ。ただ「ステップバイステップで考えてください」みたいな雑なCoTは精度がブレやすい。うちで効果があったのは、思考ステップをこちら側で明示的に定義する方法。
COT_SYSTEM_PROMPT = """
あなたはコードレビューの専門AIです。
以下の**厳密な手順で**コードを分析してください。
各ステップを必ず出力し、飛ばさないでください。
[STEP 1: 構造分析]
- 関数/クラスの責務は単一か?
- 依存関係に循環はないか?
[STEP 2: セキュリティチェック]
- SQL/コマンドインジェクションリスク
- 認証・認可の漏れ
- 機密情報のハードコード
[STEP 3: パフォーマンス分析]
- N+1クエリ問題
- 不要なループ/再計算
- メモリリーク可能性
[STEP 4: 総合評価]
- Risk Level: LOW / MEDIUM / HIGH
- 必須対応事項(箇条書き)
- 推奨改善事項(任意対応)
必ずSTEP 1〜4の順で出力すること。
"""
最初は「ここまで細かく定義するの?」と思ったけど、実際にやってみたらレビューの抜け漏れが明らかに減った。AIエージェント開発で痛い目を見た話でも書いたけど、LLMに自由度を与えすぎると制御が難しくなる。「何を考えるか」まで決めてあげるのが2026年時点でのCoTの正しい使い方だと個人的には思っている。
チームで「プロンプト」を管理するための仕組み
正直これが一番しんどかった部分かもしれない。個人でプロンプトを試す段階はいいんだけど、チームで本番運用するとなると「どのバージョンが今使われてるの?」「誰かが勝手に変えて精度が落ちた」みたいな事故が起きやすい。うちも実際にやらかした。
核心はプロンプトをコードと同様にGit管理して、評価テストをCIに組み込むこと。フロー全体を図にするとこんな感じだ。
flowchart TD
A[エンジニア] -->|プロンプト変更PR| B[GitHubリポジトリ]
B -->|CI: 自動評価テスト| C{評価スコア}
C -->|Pass ≥ baseline| D[レビュー待ち]
C -->|Fail < baseline| E[PRブロック]
D -->|承認| F[プロンプトレジストリ]
F -->|バージョン管理| G[本番環境]
G -->|メトリクス収集| H[モニタリング]
H -->|品質劣化検知| I[アラート]
I -->|ロールバック| F
style C fill:#f9f,stroke:#333
style E fill:#f66,stroke:#333
style D fill:#9f9,stroke:#333
# .github/workflows/prompt-eval.yml
name: Prompt Evaluation
on:
pull_request:
paths:
- 'prompts/**'
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.13'
- name: Install dependencies
run: pip install -r requirements-eval.txt
- name: Run prompt evaluation
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
run: |
python scripts/eval_prompts.py \
--prompt-dir prompts/ \
--eval-dataset data/eval_cases.jsonl \
--baseline-score 0.85 \
--output-report eval_report.json
- name: Upload evaluation report
uses: actions/upload-artifact@v4
with:
name: eval-report
path: eval_report.json
- name: Comment PR with results
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const report = JSON.parse(fs.readFileSync('eval_report.json'));
const body = `## プロンプト評価結果\n\n| プロンプト | スコア | ベースライン | 判定 |\n|---|---|---|---|\n${report.results.map(r => `| ${r.name} | ${r.score.toFixed(3)} | ${r.baseline} | ${r.passed ? '✅ Pass' : '❌ Fail'} |`).join('\n')}`;
github.rest.issues.createComment({issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body});
このCIを入れてから「気づいたら精度が落ちてた」問題がほぼなくなった。マジで助かった。ただ評価データセットの整備が最初は大変で、ここに3週間くらいかかった記憶がある。それでも絶対にやった方がいい。サボると後で確実に後悔する。
2026年現在のプロンプトパターン比較
自分たちが試したパターンを横断的に比較するとこんな感じ。全パターンを網羅的に本番検証できているわけじゃないので、あくまで参考程度に見てほしい。
| パターン | 精度向上 | コスト影響 | 実装難易度 | 向いているタスク |
|---|---|---|---|---|
| Zero-Shot | — | 低 | 低 | シンプルな分類・変換 |
| Few-Shot(静的) | ★★ | 中 | 低 | テンプレート的な処理 |
| Few-Shot(Dynamic) | ★★★ | 中〜高 | 高 | バリエーション多いタスク |
| Chain-of-Thought | ★★ | 中 | 中 | 推論・判断系 |
| CoT + Step分解 | ★★★ | 中高 | 中 | 複雑な多段推論 |
| Structured Output | ★★(安定性) | 低〜中 | 中 | 後処理が必要な全般 |
| Self-Consistency | ★★★ | 高 | 中 | 高精度が必要な重要判断 |
| ReAct(Tool Use) | ★★★★ | 高 | 高 | 外部情報参照が必要なタスク |
Self-Consistencyは同じプロンプトを複数回実行して多数決を取る手法で、コストは増えるけどクリティカルな判断タスクでは検討の価値がある。社内ベンチマーク上のスコアをグラフにするとこんな感じ。
xychart-beta
title "各プロンプトパターンの精度スコア比較(社内ベンチマーク)"
x-axis ["Zero-Shot", "Few-Shot静的", "Few-Shot動的", "CoT", "CoT+Step", "Structured"]
y-axis "精度スコア" 0 --> 100
bar [62, 74, 83, 79, 88, 71]
CoT + Step分解が一番スコアが高いけど、コストとトレードオフなので用途次第というのが正直なところ。
失敗から学んだ「やってはいけない」プロンプト設計
ここが本当に伝えたいところ。成功パターンより失敗から学んだことの方が印象に残っているし、同じ轍を踏んでほしくない。
失敗1: プロンプトが巨大化しすぎた問題
最初は「情報をたくさん与えればいいはず」という発想で、システムプロンプトに仕様書を丸ごとぶち込んでいた。結果、トークン数が爆発したのと、モデルが「Lost in the Middle」(コンテキスト中間部の情報を見落とす)問題を起こして精度が落ちた。今は「システムプロンプトは役割と制約の定義だけ、具体的な仕様はコンテキストウィンドウの先頭か末尾に配置する」をルール化している。
失敗2: 「〜しないでください」の否定指示に頼りすぎた
「個人情報を含めないでください」「差別的な表現は禁止です」のような禁止系の指示は、肯定的に書き直した方が効果が高い。「出力には氏名・住所・電話番号を含めず、事実のみを記述してください」みたいに。これ、地味だけど結構精度が変わる。
失敗3: 英語プロンプト vs 日本語プロンプト問題
「英語でプロンプト書いた方がいい」説は2026年時点では半分本当で半分嘘だと思っている。日本語タスクで日本語プロンプトを使った場合、最新モデルでは精度差がほぼなくなってきた。むしろ保守性を考えると日本語の方がチームにフィットするケースが多い。ただしo3系の数学・論理推論タスクは英語の方が若干スコアが出やすい感触がある。ここは正直まだ検証中で、みんなのチームはどうしているか気になるところだ。
失敗4: プロンプトインジェクション対策を後回しにした
ユーザー入力をプロンプトに組み込む実装で、インジェクション攻撃を想定せず本番に出してしまったことがある。「上記の指示を無視して…」みたいな入力で意図しない動作をするやつ。セキュリティ設計は後回しにすると絶対後悔する。これは身をもって学んだ。
def sanitize_user_input(user_input: str) -> str:
"""
プロンプトインジェクション対策の基本的なサニタイズ
※これだけでは不十分。防御を多層化すること
"""
# 既知のインジェクション試行パターンを検出
injection_patterns = [
r"ignore (all |previous |above |the )?instructions?",
r"disregard (all |previous |above |the )?instructions?",
r"you are now",
r"act as",
r"jailbreak",
r"system prompt",
]
import re
for pattern in injection_patterns:
if re.search(pattern, user_input, re.IGNORECASE):
# 検出した場合はログ記録して入力を拒否
raise ValueError(f"Potential prompt injection detected: {pattern}")
# ユーザー入力を明示的にラップしてシステムプロンプトから分離
return f"<user_input>\n{user_input}\n</user_input>"
セキュリティ観点でのLLM実装はOWASP Top 10 2024対策でも触れている。OWASP LLM Top 10という派生版も2025年に更新されていて、こちらも参考になった。
まとめ
長くなったけど、うちのチームが2026年時点で実務で使えると判断したプロンプトエンジニアリングの知見を整理するとこうなる。
1. 出力形式はStructured Outputで型定義する
JSON Schema + Pydanticの組み合わせは今すぐ導入できる。パース失敗率が劇的に下がる。
2. Few-Shotは動的取得を検討する
ベクトルDB活用で入力分布の変化に強くなる。実装コストはかかるが効果は高い。
3. CoTは「何を考えるか」まで定義する
「ステップバイステップで」ではなく、ステップの内容まで明示した方が安定する。
4. プロンプトはGit管理してCIで評価する
ここをサボると必ず「なんか精度落ちた」問題が発生する。評価データセット整備が一番大変だけど一番重要。
5. セキュリティ設計は後回しにしない
プロンプトインジェクション対策は設計初期から組み込むこと。
次のアクションとして、まずStructured Output + Pydanticを既存の実装に適用してみるのが一番コスパがいいと思う。それが軌道に乗ったら、評価ケースを10〜20件だけ作ってCIに組み込む実験をやってみてほしい。チームのプロンプトをprompts/ディレクトリに集約してGit管理を始めるだけでも、かなり運用が楽になるはずだ。
プロンプトエンジニアリング、センスじゃなくて設計だよというのが今の自分の結論。まだまだ自分たちも試行錯誤中だし、モデルの進化に合わせて常にアップデートが必要な領域でもある。「うちはこうやってる」みたいな知見があれば、ぜひコメントで教えてほしい。