開発者のためのプロンプトエンジニアリング:実際に機能するパターン

Dev.to / 2026/3/24

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical Usage

要点

  • この記事は、開発者にとって効果的なプロンプトエンジニアリングには、プロンプトの暗黙の契約(インプリシット・コントラクト)を明示化することが必要だと主張している。プロンプトを「関数シグネチャ」として捉え、タスク、入力の文脈、出力形式、制約を定義する。
  • あいまいな助言に頼るのではなく、どのプロンプトが機能するかを測定できる、テスト可能な評価用ハーネスを組み込むことで、プロダクション志向のプロンプト品質向上のアプローチを提示する。
  • 経験豊富なエンジニアが使う具体的なプロンプトパターンを12個示し、それぞれに実行可能なコード例、失敗パターン、コスト/品質のトレードオフを付けて解説する。
  • 構造化された出力(例:JSON形式)や明確な制約を用いて一貫性と信頼性を重視し、ばらつきを減らし、開発者のワークフローにおける下流(後続)での利用性を高める。
  • 全体として、この記事はプロンプトエンジニアリングを、測定可能な結果を伴う体系的なエンジニアリング手法として位置付け、実コードベースへの統合やテストパイプラインへの組み込みに適しているとする。

開発者のためのプロンプトエンジニアリング:本当に機能するパターン

「具体的に書け」といった曖昧な助言はスキップしましょう。動くコード、テストハーネス、測定可能な結果を含む12の具体的なプロンプトパターンを紹介します。

プロンプトエンジニアリングのコンテンツの多くは、主に2種類に分類されます。マーケティング用の薄いノリ(「AIに欲しいものを言うだけでいい!」)か、生産コードに落とし込めない学術的な理論です。この記事はどちらでもありません。

この記事では、経験豊富なエンジニアが実際に使う12のパターンを扱います。以下を含みます:

  • 今すぐ実行できる実コード例
  • どのプロンプトが機能するかを測定するテストハーネス
  • 避けるべき失敗パターン
  • 各アプローチにおけるコスト/品質のトレードオフ

最後には、単なる「ヒントの一覧」ではなく、プロンプトを構築・評価するための体系的な方法が身についているはずです。

プロンプトに対する開発者のメンタルモデル

プロンプトを関数のシグネチャだと考えてください:

// 悪い例:契約が不明確
const output = await claude("help me with my code");

// 良い例:明示的な契約
const output = await claude({
  task: "Review this function for security vulnerabilities",
  input: {code: functionBody, language: "javascript"},
  format: "JSON array of {severity, description, line, fix}",
  constraints: ["Only flag real issues, not style preferences"]
});

すべてのプロンプトには、暗黙的または明示的に:タスク入力コンテキスト出力形式制約があります。この4つをすべて明示すると、一貫性が大幅に向上します。

セットアップ:テスト可能なプロンプトフレームワーク

パターンに入る前に、ここで以降ずっと使う評価用ハーネスを紹介します:

// prompt-eval.js
import Anthropic from '@anthropic-ai/sdk';

const client = new Anthropic();

/**
 * 複数のテストケースに対してプロンプトを実行し、品質を測定する
 */
export async function evalPrompt(promptFn, testCases, {
  model = 'claude-haiku-4-5',
  judge = null, 
  verbose = false,
} = {}) {
  const results = [];

  for (const testCase of testCases) {
    const start = Date.now();

    const prompt = promptFn(testCase.input);
    const response = await client.messages.create({
      model,
      max_tokens: 1024,
      messages: [{role: 'user', content: prompt}],
    });

    const output = response.content[0].text;
    const latency = Date.now() - start;
    const tokens = response.usage.input_tokens + response.usage.output_tokens;

    // ジャッジ関数が渡されている場合は、出力をスコアリングする
    let score = null;
    if (judge) {
      score = await judge(testCase.input, output, testCase.expected);
    }

返却形式: {"translated": "翻訳されたHTML"}results.push({
      input: testCase.input,
      output,
      expected: testCase.expected,
      latency,
      tokens,
      score,
      passed: score !== null ? score >= 0.7 : null,
    });

    if (verbose) {
      console.log(`
入力: ${JSON.stringify(testCase.input).slice(0, 100)}`);
      console.log(`出力: ${output.slice(0, 200)}`);
      console.log(`レイテンシ: ${latency}ms | トークン: ${tokens} | スコア: ${score}`);
    }
  }

  const avgScore = results.filter(r => r.score !== null)
    .reduce((sum, r) => sum + r.score, 0) / results.filter(r => r.score !== null).length;

  const avgTokens = results.reduce((sum, r) => sum + r.tokens, 0) / results.length;
  const avgLatency = results.reduce((sum, r) => sum + r.latency, 0) / results.length;

  return {
    results,
    summary: {
      passRate: results.filter(r => r.passed).length / results.length,
      avgScore,
      avgTokens,
      avgLatency,
      totalCost: estimateCost(results.reduce((sum, r) => sum + r.tokens, 0)),
    },
  };
}function estimateCost(totalTokens, model = 'claude-haiku-4-5') {
  const rates = {
    'claude-haiku-4-5': 0.25 / 1_000_000,
    'claude-sonnet-4-5': 3 / 1_000_000,
    'claude-opus-4-5': 15 / 1_000_000,
  };
  return ((totalTokens * (rates[model] || 0.25 / 1_000_000)) * 100).toFixed(4);
}

パターン 1: JSONスキーマによる構造化出力

最も信頼性が高く、一貫した解釈可能な(パース可能な)出力を得る方法です。

// pattern-structured-output.js

function classifyBugPrompt(bugReport) {
  return `以下のバグ報告を分類し、この通りのJSONオブジェクトを返してください。

バグ報告:
"""
${bugReport}
"""

この正確なスキーマのJSONオブジェクトのみを返してください(markdownも説明もなし):
{
  "severity": "critical" | "high" | "medium" | "low",
  "category": "crash" | "performance" | "data-loss" | "ui" | "security" | "other",
  "affectedUsers": "all" | "some" | "few",
  "reproducible": true | false,
  "priority": 1-5,
  "summary": "1文の説明"
}`;
}

// テスト
const testCases = [
  {
    input: "『Export』ボタンをクリックすると、1000個を超えるアイテムがあるユーザーですぐにアプリがクラッシュする",
    expected: { severity: 'critical', reproducible: true },
  },
  {
    input: "一部のSamsung端末で、ボタンの色がわずかにおかしく見える",
    expected: { severity: 'low', category: 'ui' },
  },
];

// 判定:必要なフィールドが期待される値と一致するか確認する
async function structuredJudge(input, output, expected) {
  try {
    const parsed = JSON.parse(output);
    const matches = Object.entries(expected)
      .filter(([k, v]) => parsed[k] === v).length;
    return matches / Object.keys(expected).length;
  } catch {
    return 0; // 無効なJSON = 自動的な失敗
  }
}

const { summary } = await evalPrompt(classifyBugPrompt, testCases, {
  judge: structuredJudge,
  verbose: true,
});
console.log('Structured output score:', summary);

構造化出力の重要なルール:

  1. return ONLY JSON」と言う—markdownのフェンスブロックは使わない
  2. スキーマを言葉だけでなく、インラインで正確に定義する
  3. 列挙型にはユニオン型を使う("a" | "b" | "c"
  4. try/catch内でJSON.parseを入れる—必ず。指示があっても、モデルが説明を付けてしまうことがたまにある

パターン 2: CoT(Chain-of-Thought:思考連鎖)

回答する前に、モデルに考えさせる。複数ステップの問題における精度が大幅に向上します。

返却形式: {"translated": "翻訳されたHTML"}
// pattern-cot.js

// ❌ CoTなし: モデルが答えに飛びつくことがあり、しばしば誤り
function withoutCoT(problem) {
  return `このプログラミング問題に答えてください: ${problem}`;
}

// ✅ CoTあり: モデルが段階的に考えを進める
function withCoT(problem) {
  return `このプログラミング問題に答えてください。

最終回答を出す前に、段階的に考えてください:

1. この質問が本当に求めていることを特定する
2. 関連するエッジケースや制約を列挙する
3. アプローチを考える
4. 推論に誤りがないか確認する
5. そして最終回答を提示する

質問: ${problem}

推論を始めてください:`;
}

// さらに強力: few-shot CoT
function fewShotCoT(problem) {
  return `プログラミング問題を段階的に解いてください。

例1:
Q: Setを使って、n個の整数からなる配列に重複があるかを見つけるときの計算量は?
Thinking:
- 配列を1回だけ走査する: O(n)
- 各要素ごとに、Setの参照/挿入を行う: 平均 O(1)
- 合計: O(n)時間、O(n)空間
Answer: 計算量はO(n)時間、O(n)空間。

例2:
Q: 再帰関数が各階層で2回自分自身を呼び出し、深さがn段ある場合、計算量は?
Thinking:
- レベル0: 1回呼び出し
- レベル1: 2回呼び出し
- レベル2: 4回呼び出し
- レベルn: 2^n回呼び出し
- 合計: 2^0 + 2^1 + ... + 2^n = 2^(n+1) - 1
Answer: 計算量はO(2^n)時間。

では次を解いてください:
Q: ${problem}
Thinking:`;
}

CoTを使うべきとき:

  • 数学または論理の問題
  • 複雑なコードのデバッグ
  • 複数の制約を伴う要件の分析
  • 答えが複数ステップに依存するあらゆるタスク

CoTを使わないべきとき:

  • 単純な分類タスク(トークンを増やすだけで、得るものがない)
  • JSON出力が必要なとき(CoTと構造化出力は競合する)
  • レイテンシが重要なクリティカルパス

Pattern 3: ペルソナと制約のためのシステムプロンプト

システムプロンプトは、役割を設定するためだけのものではありません。制約、スタイルガイド、そしてドメイン知識をエンコードする場です。

// pattern-system-prompts.js

// ❌ 弱いシステムプロンプト
const weakSystem = "あなたは親切なコーディングアシスタントです。";

// ✅ 強いシステムプロンプト
const strongSystem = `あなたはフィンテック企業のシニアソフトウェアエンジニアです。

専門性:
- Node.js、TypeScript、PostgreSQL、Redis
- 金融システム: 複式簿記、取引処理、PCIコンプライアンス
- セキュリティ重視のマインドセット

コミュニケーションスタイル:
- 率直で技術的 — 不要な取り繕いはしない
- 文章の説明よりコード例を使う
- 金融規制に関して不確かな点がある場合は、明確にそう言う

制約:
- 生のカード番号の保存を勧めない(トークン化を使用する)
- 支払い関連のコードについては、必ずPCI DSSへの影響に言及する
- ユーザーが特にJavaScriptを要求しない限り、デフォルトはTypeScriptとする
- コールバックや生のPromiseよりもasync/awaitを優先する
- すべてのコード例にエラーハンドリングを含める

出力フォーマット:
- コードの場合: スニペットではなく、完全な関数を提示する
- アーキテクチャの場合: 構造のためにASCII図を使う
- レビューの場合: 問題点を重大度(CRITICAL/HIGH/MEDIUM/LOW)付きで列挙する`;

// 再利用可能なプロンプトビルダーを作る
class PromptBuilder {
  constructor(systemPrompt) {
    this.systemPrompt = systemPrompt;
    this.fewShots = [];
  }

  addExample(userMessage, assistantResponse) {
    this.fewShots.push({ user: userMessage, assistant: assistantResponse });
    return this;
  }

  build(userMessage) {
    return {
      system: this.systemPrompt,
      messages: [
        ...this.fewShots.flatMap(({ user, assistant }) => [
          { role: 'user', content: user },
          { role: 'assistant', content: assistant },
        ]),
        { role: 'user', content: userMessage },
      ],
    };
  }

返却形式: {"translated": "翻訳されたHTML"}async call(userMessage, model = 'claude-sonnet-4-5') {
    const { system, messages } = this.build(userMessage);
    return client.messages.create({
      model,
      max_tokens: 2048,
      system,
      messages,
    });
  }
}

// 使用方法
const codeReviewer = new PromptBuilder(strongSystem)
  .addExample(
    'この関数をレビューしてください:
function transfer(from, to, amount) {
  db.query(`UPDATE accounts SET balance = balance - ${amount} WHERE id = ${from}`);
}',
    '重大: SQLインジェクションの脆弱性。金額とfromがクエリにそのまま補間されています。パラメータ化クエリを使用してください:
```

ts
await db.query("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [amount, from]);


```
また欠けています: トランザクション(非アトミック)、負の金額チェック、残高のバリデーション。'
  );

const review = await codeReviewer.call(userCode);

パターン 4: フューショット学習

求める内容の例を提示してから、その後にモデルに依頼します。最も活用されていない手法のひとつです。

// pattern-few-shot.js

// 雑多なテキストから構造化データを抽出する
function extractApiParams(docs) {
  return `ドキュメントからAPIパラメータをJSON配列として抽出してください。

---
例 1:
入力: "エンドポイントは userId(必須、string、ユーザーID)と limit(任意、integer、デフォルトは20、最大100)を受け付けます。"
出力: [
  {"name": "userId", "type": "string", "required": true, "description": "ユーザーID"},
  {"name": "limit", "type": "integer", "required": false, "default": 20, "max": 100}
]

---
例 2:
入力: "Authorizationヘッダに apiKey を渡してください。page(1始まりの整数)と per_page(10-50、デフォルト25)を含めてください"
出力: [
  {"name": "apiKey", "type": "string", "required": true, "location": "header", "header": "Authorization"},
  {"name": "page", "type": "integer", "required": false, "min": 1},
  {"name": "per_page", "type": "integer", "required": false, "default": 25, "min": 10, "max": 50}
]

---
次に以下から抽出:
入力: "${docs}"
出力:`;
}

// コード変換のタスクのために
function modernizeCode(legacyCode) {
  return `従来のJavaScriptをモダンなES2024+の構文に変換してください。

---
例 1:
入力:
\`\`\`js
var users = [];
for (var i = 0; i < data.length; i++) {
  if (data[i].active) {
    users.push(data[i].name);
  }
}
\`\`\`
出力:
\`\`\`js
const users = data.filter(u => u.active).map(u => u.name);
\`\`\`

---
例 2:
入力:
\`\`\`js
function fetchUser(id, callback) {
  db.find(id, function(err, user) {
    if (err) { callback(err); return; }
    callback(null, user);
  });
}
\`\`\`
出力:
\`\`\`js
async function fetchUser(id) {
  return await db.find(id);
}
\`\`\`

---
次に変換:
\`\`\`js
${legacyCode}
\`\`\`
出力:`;
}

経験則: 2〜3個の例を使ってください。5個を超えることはめったに結果が改善せず、トークンコストが増えます。ハッピーケースだけでなく、エッジケースをカバーする例を選びましょう。

パターン 5: 専門性インジェクションによるロールベースのプロンプティング

モデルにであり、何を知っているかを伝えてください。やるべきことだけを伝えるのではありません:

// pattern-role.js

// 汎用的(弱い)
const generic = "このアーキテクチャ図をレビューしてください。";

// ロール固有(強い)
const roleSpecific = `あなたは分散システムのアーキテクトで、経験は12年です。
あなたの専門は、1日あたり1,000万+件のリクエストを処理する高可用性システムです。
大規模な障害対応に携わったことがあり、障害モードについて強い見解を持っています。

以下のアーキテクチャをレビューし、次を特定してください:
1. 単一障害点(SPOF)
2. スケーリングのボトルネック
3. 不足している冗長性
4. 現在の負荷の10倍になったときに問題を引き起こす設計判断

率直に述べてください。このチームは来週、プロダクションへ投入します。

アーキテクチャ:
${architectureDescription}`;

返却形式: {"translated": "翻訳されたHTML"}// セキュリティレビューのための敵対的な役割
const securityReview = `あなたはウェブAPIセキュリティを専門とするペネトレーションテスターです。
あなたは攻撃者のように考えます――あなたの仕事は脆弱性を見つけることであり、良いコードを褒めることではありません。
過去のクライアントは、SQLインジェクションや認証のバイパスを見つけるためにあなたに5万ドルを支払いました。

最大限の懐疑心で、このAPIエンドポイントのコードをレビューしてください:
${apiCode}

見つかった各脆弱性について、攻撃者がそれをどのように悪用するかを正確に説明してください。`;

パターン6:制約ベースのプロンプト

否定的な制約は、肯定的な指示と同じくらい重要になることがよくあります:

// pattern-constraints.js

function writeCommitMessage(diff) {
  return `この差分(diff)のgitコミットメッセージを書いてください。

要件:
- 最初の行:50文字以内、命令形("Added"ではなく"Add")
- 2行目:空行
- 本文(必要な場合):72文字で折り返し、WHATではなくWHYを説明
- 従来のconventional commits形式を使用:feat/fix/refactor/docs/test/chore

してはいけないこと(DO NOT):
- "This commit..."で始める
- 過去形を使う
- 件名にファイル名を含める
- 本文の段落を3つ以上書く
- "update"、"change"、"fix things"のような曖昧な語を使う

差分:
${diff}

コミットメッセージ:`;
}

// テス卜用の制約は満たされている
async function testConstraints() {
  const violations = [];

  const response = await client.messages.create({
    model: 'claude-haiku-4-5',
    max_tokens: 256,
    messages: [{ role: 'user', content: writeCommitMessage(sampleDiff) }],
  });

  const message = response.content[0].text.trim();
  const lines = message.split('
');
  const subject = lines[0];

  if (subject.length > 50) violations.push(`件名が長すぎます: ${subject.length} 文字`);
  if (/^(Added|Updated|Changed|Fixed)/i.test(subject)) violations.push('過去形');
  if (/^This commit/i.test(subject)) violations.push('「This commit」で始まっている');
  if (lines[1] && lines[1].trim() !== '') violations.push('件名の直後の空行がない');

  return { message, violations };
}

パターン7:反復的な洗練(自己批評)

返す前にモデル自身に自分の仕事を確認させてください:

// pattern-self-critique.js

返却形式: {"translated": "翻訳されたHTML"}async function generateWithReview(task) {
  // Step 1: 初期のレスポンスを生成する
  const draft = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 2048,
    messages: [
      { role: 'user', content: task },
    ],
  });

  const draftText = draft.content[0].text;

  // Step 2: 下書きを批評する
  const critique = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 1024,
    messages: [
      { role: 'user', content: task },
      { role: 'assistant', content: draftText },
      {
        role: 'user',
        content: `上記のあなたの回答をレビューしてください。以下を確認してください。
1. 論理的な誤り、または不正確な主張
2. 欠けているエッジケース
3. 分かりにくい説明
4. セキュリティ上の脆弱性(コードがある場合)

具体的な問題点を列挙してください。問題がない場合は、「No issues found.」と言ってください。`,
      },
    ],
  });

  const critiqueText = critique.content[0].text;

  // 問題がない場合は下書きを返す
  if (critiqueText.toLowerCase().includes('no issues found')) {
    return draftText;
  }

  // Step 3: 批評に基づいて修正する
  const revised = await client.messages.create({
    model: 'claude-sonnet-4-5',
    max_tokens: 2048,
    messages: [
      { role: 'user', content: task },
      { role: 'assistant', content: draftText },
      { role: 'user', content: `あなたの回答をレビューしてください: ${critiqueText}

Please revise.` },
    ],
  });

  return revised.content[0].text;
}

注: このパターンは、必要となるトークン数が3倍です。高リスクな出力(本番環境のコード、重要なドキュメント)で使用し、すべての呼び出しで使うのは避けてください。

パターン 8: テンプレート変数とプロンプト・ファクトリ

本番システムでは、プロンプトをコードのように管理してください。

// prompt-factory.js

class PromptTemplate {
  constructor(template) {
    this.template = template;
    this.variables = this.extractVariables(template);
  }

返却形式: {"translated": "翻訳されたHTML"}extractVariables(template) {
    const matches = template.matchAll(/\{\{(\w+)\}\}/g);
    return new Set([...matches].map(m => m[1]));
  }

  render(variables) {
    // 必要な変数がすべて提供されていることを検証する
    for (const varName of this.variables) {
      if (!(varName in variables)) {
        throw new Error(`必須の変数がありません: ${varName}`);
      }
    }

    return this.template.replace(/\{\{(\w+)\}\}/g, (_, name) => variables[name]);
  }
}

// プロンプトをテンプレートとして定義する
const PROMPTS = {
  codeReview: new PromptTemplate(`
次の {{language}} コードを {{context}} システムとしてレビューしてください。

コード:
\`\`\`{{language}}
{{code}}
\`\`\`

注目点: {{focusAreas}}

JSON形式で出力: {"issues": [...], "score": 1-10}
`),

  bugReport: new PromptTemplate(`
あなたは {{component}} における {{severity}} のバグを調査しています。

エラー: {{errorMessage}}
スタックトレース:
{{stackTrace}}

ユーザーコンテキスト: {{userContext}}

根本原因を特定し、修正案を提案してください。
`),
};

// 使用例
const reviewPrompt = PROMPTS.codeReview.render({
  language: 'TypeScript',
  context: 'payment processing',
  code: userCode,
  focusAreas: 'security, error handling, transaction atomicity',
});

パターン 9: 並列プロンプト

包括的な分析のために、複数の専門化されたプロンプトを同時に実行する:

// pattern-parallel.js

async function comprehensiveReview(code) {
  const [securityReview, performanceReview, styleReview] = await Promise.all([
    client.messages.create({
      model: ' claude-haiku-4-5',
      max_tokens: 1024,
      system: ' あなたはセキュリティの専門家です。セキュリティ上の脆弱性のみ報告してください。',
      messages: [{ role: ' user', content: `セキュリティの観点でレビューしてください:
\`\`\`
${code}
\`\`\`` }],
    }),
    client.messages.create({
      model: ' claude-haiku-4-5',
      max_tokens: 1024,
      system: ' あなたはパフォーマンスエンジニアです。パフォーマンス上の問題のみ報告してください。',
      messages: [{ role: ' user', content: `パフォーマンスの観点でレビューしてください:


返却形式: {"translated": "翻訳されたHTML"}\`\`\`
${code}
\`\`\`` }],
    }),
    client.messages.create({
      model: 'claude-haiku-4-5',
      max_tokens: 1024,
      system: 'あなたは、保守性と可読性に重点を置くコードレビュアーです。',
      messages: [{ role: 'user', content: `コード品質のレビュー:
\`\`\`
${code}
\`\`\`` }],
    }),
  ]);

  return {
    security: securityReview.content[0].text,
    performance: performanceReview.content[0].text,
    style: styleReview.content[0].text,
  };
}

パターン10: 動的な例(Retrieval-Augmented Prompting)

現在の入力に基づいて、few-shotの例を動的に選択します。

// pattern-dynamic-examples.js
import { cosineSimilarity } from './utils.js';

// 埋め込み(embedding)を持つ例を保存する
const exampleStore = [
  { input: '引用符で囲まれたフィールドを含むCSVを解析する', example: '...', embedding: null },
  { input: 'HTTPのレート制限を処理する', example: '...', embedding: null },
  { input: 'メールアドレスを検証する', example: '...', embedding: null },
  // ... その他の例
];

async function embedText(text) {
  // 任意の埋め込みモデルを使用する(例: OpenAI, Cohere, またはローカル)
  const response = await fetch('https://api.openai.com/v1/embeddings', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${process.env.OPENAI_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ model: 'text-embedding-3-small', input: text }),
  });
  const data = await response.json();
  return data.data[0].embedding;
}

async function findRelevantExamples(query, topK = 3) {
  const queryEmbedding = await embedText(query);

返却形式: {"translated": "翻訳されたHTML"}return exampleStore
    .map(ex => ({ ...ex, similarity: cosineSimilarity(queryEmbedding, ex.embedding) }))
    .sort((a, b) => b.similarity - a.similarity)
    .slice(0, topK);
}

async function buildDynamicPrompt(userQuery) {
  const relevantExamples = await findRelevantExamples(userQuery);

  const examplesSection = relevantExamples
    .map(ex => `Example (your request と似ています):
${ex.example}`)
    .join('

---

');

  return `${examplesSection}

---

Now help with:
${userQuery}`;
}

Pattern 11: 出力の検証とリトライ

本番環境では、LLM の生の出力を決して信頼しないでください。必ず検証し、失敗した場合は修正のヒントとともにリトライしてください。

// pattern-validation.js

async function callWithValidation(prompt, validator, maxRetries = 3) {
  let lastError = null;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    const messages = [{ role: 'user', content: prompt }];

    // リトライ時は、前回の失敗をコンテキストとして追加する
    if (lastError && attempt > 1) {
      const lastAttempt = await getPreviousAttempt(); // 履歴から
      messages.push({ role: 'assistant', content: lastAttempt });
      messages.push({
        role: 'user',
        content: `前回のレスポンスは検証に失敗しました: ${lastError}. 修正してください。`,
      });
    }

    const response = await client.messages.create({
      model: 'claude-haiku-4-5',
      max_tokens: 1024,
      messages,
    });

    const output = response.content[0].text;

返却形式: {"translated": "翻訳されたHTML"}try {
      const validated = validator(output);
      return { output: validated, attempts: attempt };
    } catch (error) {
      lastError = error.message;
      console.warn(`試行 ${attempt} が失敗しました: ${error.message}`);
    }
  }

  throw new Error(`    ${maxRetries} 回の試行の後に失敗しました。 最終エラー: ${lastError}`);
}

// 例: バリデータ
const validators = {
  json: (output) => {
    const match = output.match(/\{[\s\S]*\}|\[[\s\S]*\]/);
    if (!match) throw new Error('出力内にJSONが見つかりません');
    return JSON.parse(match[0]);
  },

  semver: (output) => {
    const match = output.trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
    if (!match) throw new Error(`有効なsemverではありません: ${output.trim()}`);
    return output.trim();
  },

  nonEmpty: (output) => {
    if (!output.trim()) throw new Error('空のレスポンスです');
    return output;
  },
};

// 利用方法
const result = await callWithValidation(
  "どのバージョンに上げるべきですか? 現在: 1.2.3. バグを修正しました。 semverの数値だけを返してください。",
  validators.semver,
);

パターン 12: モデル選択戦略

タスクごとに異なるモデルを使う — これがコスト最適化における最大のレバーです:

// pattern-model-selection.js

const MODEL_TIERS = {
  fast: 'claude-haiku-4-5',      // $0.25/100万トークン - シンプルなタスク
  balanced: 'claude-sonnet-4-5', // $3/100万トークン - ほとんどのタスク
  powerful: 'claude-opus-4-5',   // $15/100万トークン - 複雑な推論
};function selectModel(task) {
  // ファスト層:シンプルな分類、フォーマット、抽出
  if (
    task.type === 'classify' ||
    task.type === 'format' ||
    task.type === 'extract' ||
    (task.outputTokens || 0) < 200
  ) {
    return MODEL_TIERS.fast;
  }

  // パワフル層:複雑な推論、アーキテクチャ判断
  if (
    task.type === 'architect' ||
    task.type === 'debug-complex' ||
    task.requiresExpertise === true
  ) {
    return MODEL_TIERS.powerful;
  }

  // デフォルト:それ以外すべてに対してバランス型
  return MODEL_TIERS.balanced;
}

// インテリジェントなルーティングの例
async function routedCodeReview(code, language) {
  // 最初のパス:素早い分類(低コスト)
  const classification = await client.messages.create({
    model: MODEL_TIERS.fast,
    max_tokens: 100,
    messages: [{
      role: 'user',
      content: `このコードの複雑さを 1-5 で評価してください。数値だけを返してください。

${code.slice(0, 500)}`,
    }],
  });

  const complexity = parseInt(classification.content[0].text.trim());

  // 適切なモデルへルーティング
  const model = complexity >= 4 ? MODEL_TIERS.powerful : MODEL_TIERS.balanced;

  const review = await client.messages.create({
    model,
    max_tokens: 2048,
    messages: [{
      role: 'user',
      content: `この ${language} コードをレビューしてください:
\`\`\`
${code}
\`\`\``,
    }],
  });

  return {
    model,
    complexity,
    review: review.content[0].text,
  };
}

プロンプトのテストスイートを作成する

プロンプトはコードとして扱います。バージョン管理に入れ、テストを書き、CIで実行します:

// prompt-tests/review-prompt.test.js
import { describe, it, expect } from 'vitest';
import { evalPrompt } from '../prompt-eval.js';
import { classifyBugPrompt } from '../prompts/classify-bug.js';

describe('Bug classification prompt', () => {
  const testCases = [
    {
      input: '最新のデプロイ後、全ユーザーでアプリが起動時にクラッシュします',
      expected: { severity: 'critical', affectedUsers: 'all' },
    },
    {
      input: 'Firefox ではローディングスピナーがわずかに中央からずれています',
      expected: { severity: 'low', category: 'ui' },
    },
    {
      input: 'パスワードがログに平文で保存されています',
      expected: { severity: 'critical', category: 'security' },
    },
  ];

  it('重大度の分類で>90%の精度を達成する', async () => {
    const { summary } = await evalPrompt(classifyBugPrompt, testCases, {
      judge: jsonFieldJudge(['severity', 'category']),
    });
    expect(summary.avgScore).toBeGreaterThan(0.9);
  }, 30_000); // API 呼び出しのための 30 秒タイムアウト

  it('常に有効な JSON を返す', async () => {
    const { results } = await evalPrompt(classifyBugPrompt, testCases);
    for (const result of results) {
      expect(()=> JSON.parse(result.output)).not.toThrow();
    }
  }, 30_000);
});

プロンプトエンジニアリングのチェックリスト

本番環境に何らかのプロンプトを投入する前に、次を確認してください。

□ タスクは一意に定まっていますか? 複数のエンジニアが別の解釈をしてしまう可能性はありませんか?
□ 出力形式は明示的に指定されていますか?
□ すべての制約は DO/DO NOT として列挙されていますか?
□ 複雑なタスクには 2〜3 個の例がありますか?
□ 出力検証 + リトライのロジックがありますか?
□ 正しいモデルのティアを使用していますか(コストと品質のバランス)?
□ エッジケースに対してテストしましたか?
□ プロンプトはバージョン管理されていますか(ハードコードではありません)?
□ temperature は明示的に設定されていますか(構造化された出力には低め、クリエイティブには高め)?
□ コストの監視を用意していますか?

結論

プロンプトエンジニアリングはエンジニアリングです。ほかのどんなシステムにも適用するのと同じ厳密さで取り組みましょう:

  • 体系的にテストする、出力を見て良さそうかどうかだけで判断しない
  • プロンプトをバージョン管理する — プロンプトはコードです
  • 本番環境での挙動を監視する — テストでうまくいくプロンプトでも、実データでは失敗することがあります
  • コストを測定する — 3 倍良いプロンプトでも、コストが 10 倍かかるなら価値がありません
  • 直感ではなくデータに基づいて反復する

ここで扱うパターン――構造化された出力、思考過程(chain-of-thought)、少数ショット(few-shot)、自己批評、自動リトライによる検証――は、ほとんどすべての本番運用のユースケースをカバーしています。この12個をマスターすれば、確実に動作するAI機能を構築できるようになります。

テスト用ハーネスとすべての例は github.com/chengyixu/prompt-patterns で入手できます