プロンプトエンジニアリング完全ガイド【エンジニア向け】|Zero-shot・CoT・ReAct・構造化出力まで実装コード付きで解説

プロンプトエンジニアリング完全ガイド【エンジニア向け】|Zero-shot・CoT・ReAct・構造化出力まで実装コード付きで解説 AI開発

「ChatGPTに聞いたら的外れな回答が返ってきた」「同じ質問でも毎回品質がバラバラ」——そんな経験はないでしょうか。プロンプトエンジニアリングは、LLM(大規模言語モデル)から安定して高品質な出力を引き出すための技術体系です。「どう聞くか」を設計するだけで、同じモデルでも精度が劇的に変わります。

この記事はエンジニア向けに、プロンプトの理論から実装まで一貫して解説します。Zero-shot・Few-shot・Chain-of-Thought(CoT)・ReAct・Self-consistency・システムプロンプト設計・構造化出力・RAGプロンプト・バージョン管理まで、TypeScriptの実装コードつきで実務に直結する内容を網羅しています。Claude APIOpenAI APIの両方に対応した汎用的な設計パターンも紹介します。

プロンプトエンジニアリングの需要は急速に高まっており、エンジニア職の採用要件にも「プロンプト設計経験」が含まれるケースが増えています。「なんとなく使えている」から「設計できる」レベルに引き上げるための体系的な知識が、この記事で身に付きます。

この記事でわかること

  • プロンプトエンジニアリングの基礎概念と各技術の使い分け
  • Zero-shot・Few-shot・CoT・ReAct・Self-consistencyの実装方法
  • システムプロンプトの設計原則と実践パターン
  • 構造化出力(JSON/Zod)で安定したレスポンスを取得する方法
  • 日本語プロンプトで精度を上げるテクニック
  • RAGシステムでのプロンプト設計
  • プロンプトのバージョン管理・A/Bテスト・評価の自動化
スポンサーリンク

プロンプトエンジニアリングとは何か

プロンプトエンジニアリングとは、LLMへの入力(プロンプト)を意図的に設計して、望む出力を安定して得るための技術です。モデルのファインチューニングや再学習を行わずに、入力の工夫だけでパフォーマンスを向上させられる点が特徴です。

アプローチ 説明 コスト 効果の即時性
プロンプトエンジニアリング 入力の設計を工夫する 低(API費用のみ) 即時
ファインチューニング 学習データで追加学習 高(GPU・データ費用) 準備に数日〜
RAG 外部知識をコンテキストに注入 中(ベクトルDB等が必要) 設計次第
エージェント設計 ツール・ループで自律行動 中〜高 設計次第

まずプロンプトエンジニアリングで限界まで品質を上げてから、必要に応じてファインチューニングやRAGを組み合わせるのが現実的なアプローチです。

プロンプトの基本構造

良いプロンプトは次の要素で構成されます。すべてが必須ではありませんが、複雑なタスクほど明示すると精度が上がります。

要素 説明
Role(役割) AIにどの専門家として振る舞うかを指定 「あなたはシニアTypeScriptエンジニアです」
Context(文脈) タスクの背景情報 「レビュー対象はECサイトのAPI実装です」
Task(指示) 具体的にやってほしいこと 「型安全性の問題を指摘してください」
Format(出力形式) どんな形式で返してほしいか 「JSON形式で、問題点と修正案を返してください」
Constraints(制約) 守るべきルールや禁止事項 「200文字以内にまとめてください」

Zero-shot・Few-shotプロンプティング

Zero-shot:例なしで指示する

Zero-shotは、事例を一切与えずに直接タスクを指示する最もシンプルな手法です。最新のLLM(GPT-4o・Claude 3.5 Sonnet等)は非常に高い汎化能力を持つため、単純なタスクではZero-shotで十分なケースが多いです。

zero-shot.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function classifySentiment(text: string): Promise<string> {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 50,
    messages: [
      {
        role: "user",
        content: `以下のテキストの感情を「positive」「negative」「neutral」のいずれかで分類してください。\n\nテキスト: ${text}\n\n分類結果:`,
      },
    ],
  });
  return (response.content[0] as { text: string }).text.trim();
}

const result = await classifySentiment("このAPIは使いやすくて最高です!");
console.log(result); // "positive"

Few-shot:例を示して精度を上げる

Few-shotは、期待する入出力のペアをいくつか例示してからタスクを依頼する手法です。出力形式・語調・判断基準をモデルに「学習」させることができます。特定のフォーマットや独自の判断基準がある場合に効果的です。

few-shot.ts
import OpenAI from "openai";

const client = new OpenAI();

async function extractKeywords(text: string): Promise<string[]> {
  const response = await client.chat.completions.create({
    model: "gpt-4o",
    messages: [
      {
        role: "system",
        content: "テキストから技術キーワードを抽出します。カンマ区切りで返してください。",
      },
      // Few-shot examples
      {
        role: "user",
        content: "TypeScriptでReactのカスタムフックを実装しました。",
      },
      {
        role: "assistant",
        content: "TypeScript, React, カスタムフック",
      },
      {
        role: "user",
        content: "DockerコンテナでPostgreSQLをローカル環境に立てました。",
      },
      {
        role: "assistant",
        content: "Docker, コンテナ, PostgreSQL, ローカル環境",
      },
      // 実際のリクエスト
      {
        role: "user",
        content: text,
      },
    ],
  });

  const raw = response.choices[0].message.content ?? "";
  return raw.split(",").map((kw) => kw.trim());
}

const keywords = await extractKeywords(
  "Next.jsとPrismaを使ってREST APIを構築しています。"
);
console.log(keywords); // ["Next.js", "Prisma", "REST API"]
Few-shotの例示数の目安
3〜5例が最もコストパフォーマンスが高い。1〜2例でも効果あり。10例以上になるとトークン消費が増えるため、その場合はファインチューニングを検討する。

Chain-of-Thought(CoT):推論ステップを明示させる

CoT(Chain-of-Thought)は、最終答えを出す前に推論プロセスを書き出させる手法です。「考えてから答える」ことでモデルの論理的推論能力が大幅に向上します。数学・コードデバッグ・複雑な判断など、複数ステップの推論が必要なタスクで特に効果があります。

CoTの注意点
出力が長くなるためトークン消費が増えます。シンプルな分類タスクには不要です。処理速度より精度が重要な場面で使いましょう。

Zero-shot CoT:「ステップバイステップで」と一言添えるだけ

zero-shot-cot.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function debugCode(code: string, error: string): Promise<string> {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    messages: [
      {
        role: "user",
        content: `以下のTypeScriptコードでエラーが発生しています。\nステップバイステップで原因を分析し、修正方法を説明してください。\n\nコード:\n${code}\n\nエラー:\n${error}`,
      },
    ],
  });
  return (response.content[0] as { text: string }).text;
}

const code = `
async function fetchUser(id: string) {
  const res = await fetch('/api/users/' + id);
  return res.json();
}
const user = fetchUser("123");
console.log(user.name);
`;

const analysis = await debugCode(code, "Cannot read properties of undefined (reading 'name')");
console.log(analysis);

Few-shot CoT:推論例を見せてパターンを学習させる

few-shot-cot.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const COT_SYSTEM = `あなたはコードレビューの専門家です。
問題を発見したら、必ず次の手順で回答してください:
1. 問題の特定(何が問題か)
2. 影響範囲(どんなバグやリスクが生じるか)
3. 修正案(具体的なコード)

例:
問題: setInterval の戻り値を clearInterval で解除していない
影響: コンポーネントのアンマウント後もタイマーが動き続け、メモリリーク発生
修正案:
  const timerId = setInterval(fn, 1000);
  return () => clearInterval(timerId);`;

async function reviewCode(code: string): Promise<string> {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    system: COT_SYSTEM,
    messages: [{ role: "user", content: `以下のコードをレビューしてください:\n\n${code}` }],
  });
  return (response.content[0] as { text: string }).text;
}

ReAct:思考と行動を交互に繰り返すパターン

ReAct(Reason + Act)は、Thought(思考)→ Action(行動)→ Observation(観察)のサイクルを繰り返すエージェント設計パターンです。ツール(Web検索・API呼び出し・コード実行等)と組み合わせることで、複雑なタスクを自律的に解決できます。

react-agent.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// ツール定義
const tools: Anthropic.Tool[] = [
  {
    name: "search_docs",
    description: "ドキュメントを検索して関連情報を返します",
    input_schema: {
      type: "object",
      properties: {
        query: { type: "string", description: "検索クエリ" },
      },
      required: ["query"],
    },
  },
  {
    name: "run_code",
    description: "TypeScriptコードを実行して結果を返します",
    input_schema: {
      type: "object",
      properties: {
        code: { type: "string", description: "実行するコード" },
      },
      required: ["code"],
    },
  },
];

// ツール実行のモック(実際はここに実装を入れる)
function executeTool(name: string, input: Record<string, unknown>): string {
  if (name === "search_docs") {
    return `「${String(input.query)}」の検索結果: Array.prototype.map() は各要素に関数を適用した新しい配列を返します。`;
  }
  if (name === "run_code") {
    return `実行結果: [2, 4, 6]`;
  }
  return "ツールが見つかりません";
}

async function reactAgent(task: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [
    { role: "user", content: task },
  ];

  // ReActループ(最大10ステップ)
  for (let step = 0; step < 10; step++) {
    const response = await client.messages.create({
      model: "claude-opus-4-6",
      max_tokens: 1024,
      tools,
      messages,
    });

    // ツール呼び出しがない場合は最終回答
    if (response.stop_reason === "end_turn") {
      const textBlock = response.content.find((b) => b.type === "text");
      return textBlock ? (textBlock as { type: "text"; text: string }).text : "";
    }

    // アシスタントのメッセージを追加
    messages.push({ role: "assistant", content: response.content });

    // ツール呼び出しを処理
    const toolResults: Anthropic.ToolResultBlockParam[] = response.content
      .filter((b): b is Anthropic.ToolUseBlock => b.type === "tool_use")
      .map((toolUse) => ({
        type: "tool_result" as const,
        tool_use_id: toolUse.id,
        content: executeTool(toolUse.name, toolUse.input as Record<string, unknown>),
      }));

    messages.push({ role: "user", content: toolResults });
  }

  return "最大ステップ数に達しました";
}

const result = await reactAgent(
  "[1, 2, 3]に対してmapで2倍にする方法を調べて、コードを実行して確認してください"
);
console.log(result);
ReActの実装ポイント
最大ステップ数を設定して無限ループを防ぐこと。各ステップの思考・行動・観察をログに残すと、デバッグが格段に楽になります。

Self-consistency:複数回実行して多数決を取る

Self-consistencyは、同じ質問を高温度(temperature)で複数回実行し、最も多く得られた回答を採用する手法です。単純なCoTより精度が高い反面、API呼び出しが増えるためコストが上がります。精度が最重要で、コストが許容できる場面で使います。

self-consistency.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

async function selfConsistency(
  question: string,
  numSamples: number = 5
): Promise<string> {
  // 同じプロンプトを複数回実行する(モデルは確率的なので毎回異なる推論をする)
  const promises = Array.from({ length: numSamples }, () =>
    client.messages.create({
      model: "claude-opus-4-6",
      max_tokens: 256,
      messages: [
        {
          role: "user",
          content: `${question}\n\nステップバイステップで考えて、最後に「答え: X」の形式で回答してください。`,
        },
      ],
    })
  );

  const responses = await Promise.all(promises);

  // 各レスポンスから「答え: X」を抽出
  const answers = responses.map((r) => {
    const text = (r.content[0] as { text: string }).text;
    const match = text.match(/答え[::]\s*(.+)/);
    return match ? match[1].trim() : text.trim();
  });

  // 多数決:最も多く出現した答えを返す
  const counts = new Map<string, number>();
  for (const answer of answers) {
    counts.set(answer, (counts.get(answer) ?? 0) + 1);
  }

  return [...counts.entries()].sort((a, b) => b[1] - a[1])[0][0];
}

const answer = await selfConsistency(
  "TypeScriptで型ガードとして機能する関数の正しい戻り値の型は何ですか?",
  5
);
console.log("最多回答:", answer);

システムプロンプトの設計原則

システムプロンプトはLLMの「性格・役割・ルール」を定義する最も重要なプロンプトです。適切に設計することで、アプリケーション全体の出力品質が安定します。

良いシステムプロンプトの7原則

  1. 役割を明確に定義する:「あなたは○○の専門家です」と明示
  2. 出力形式を指定する:「JSON形式で返してください」「箇条書きで3点」
  3. 禁止事項を明記する:「推測で回答しない」「範囲外の質問はお断りする」
  4. 具体例を含める:期待する入出力のサンプルを1〜2個示す
  5. 優先順位を伝える:「正確性 > 簡潔さ」など
  6. コンテキストの限界を意識する:長すぎるシステムプロンプトは推論に使えるトークンを圧迫する
  7. バージョン管理する:変更履歴を残し、A/Bテストで改善する

実践的なシステムプロンプトの例

system-prompt.ts
// コードレビューアシスタントのシステムプロンプト
const CODE_REVIEW_SYSTEM = `あなたは10年以上の経験を持つシニアTypeScriptエンジニアです。
コードレビューの専門家として振る舞ってください。

【レビュー方針】
- 型安全性(型の欠落・any の乱用・型アサーションの誤用)を最優先でチェックする
- パフォーマンス問題(無駄なレンダリング・N+1クエリ・メモリリーク)を指摘する
- セキュリティリスク(インジェクション・XSS・認証の欠落)を必ず確認する
- 読みやすさと保守性の観点でコメントする

【出力形式】
必ず以下のJSON形式で返してください:
{
  "severity": "critical" | "major" | "minor" | "info",
  "issues": [
    {
      "line": 行番号または範囲,
      "type": "type-safety" | "performance" | "security" | "readability",
      "description": "問題の説明",
      "suggestion": "修正案のコード"
    }
  ],
  "summary": "全体的な評価(2〜3文)"
}

【禁止事項】
- 問題がない場合でも必ずissuesを空配列で返す(省略しない)
- JSONフォーマット以外での回答は禁止
- 推測でissuesを追加しない`;

export { CODE_REVIEW_SYSTEM };

システムプロンプトのモジュール化

大規模なアプリケーションでは、システムプロンプトをモジュール化して再利用性を高めましょう。

prompt-modules.ts
// プロンプトの構成要素をモジュール化する
const PERSONA = (role: string, experience: string) =>
  `あなたは${experience}の${role}です。`;

const OUTPUT_FORMAT = (format: string) =>
  `必ず${format}形式で回答してください。`;

const CONSTRAINTS = (...rules: string[]) =>
  `【制約】\n${rules.map((r, i) => `${i + 1}. ${r}`).join("\n")}`;

// 組み合わせてシステムプロンプトを構築
function buildSystemPrompt(config: {
  role: string;
  experience: string;
  outputFormat: string;
  constraints: string[];
}): string {
  return [
    PERSONA(config.role, config.experience),
    OUTPUT_FORMAT(config.outputFormat),
    CONSTRAINTS(...config.constraints),
  ].join("\n\n");
}

const systemPrompt = buildSystemPrompt({
  role: "SQLクエリ最適化エンジニア",
  experience: "データベースチューニング10年以上",
  outputFormat: "JSON(original_query, optimized_query, explanation を含む)",
  constraints: [
    "実行計画の改善点を必ず説明する",
    "インデックスの提案がある場合はCREATE INDEX文も含める",
    "PostgreSQL 15+を前提とする",
  ],
});

console.log(systemPrompt);

構造化出力でLLMレスポンスを型安全に扱う

プロダクション環境では、LLMの出力を自由テキストのまま使うのは危険です。構造化出力(Structured Output)を活用して、型安全なオブジェクトとして受け取ることが重要です。

Zodを使った型安全なレスポンス検証

structured-output-zod.ts
import Anthropic from "@anthropic-ai/sdk";
import { z } from "zod";

const client = new Anthropic();

// 期待するレスポンスの型をZodスキーマで定義
const CodeReviewSchema = z.object({
  severity: z.enum(["critical", "major", "minor", "info"]),
  issues: z.array(
    z.object({
      line: z.union([z.number(), z.string()]),
      type: z.enum(["type-safety", "performance", "security", "readability"]),
      description: z.string(),
      suggestion: z.string(),
    })
  ),
  summary: z.string(),
});

type CodeReview = z.infer<typeof CodeReviewSchema>;

async function reviewCode(code: string): Promise<CodeReview> {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    system: `コードレビューの結果を以下のJSON形式のみで返してください。余分なテキストは含めないでください。
スキーマ:
{
  "severity": "critical" | "major" | "minor" | "info",
  "issues": [{ "line": number | string, "type": string, "description": string, "suggestion": string }],
  "summary": string
}`,
    messages: [{ role: "user", content: `以下のコードをレビューしてください:\n\n${code}` }],
  });

  const text = (response.content[0] as { text: string }).text;

  // JSON部分を抽出(モデルが余分なテキストを出力した場合に対応)
  const jsonMatch = text.match(/\{[\s\S]*\}/);
  if (!jsonMatch) throw new Error("JSON形式のレスポンスが見つかりません");

  const parsed = JSON.parse(jsonMatch[0]);
  return CodeReviewSchema.parse(parsed); // 型チェック(失敗時は例外)
}

// 使用例
const review = await reviewCode(`
function getUser(id) {
  return fetch('/api/users/' + id).then(r => r.json());
}
`);

if (review.severity === "critical") {
  console.error("重大な問題があります:", review.issues);
} else {
  console.log("レビュー完了:", review.summary);
}

OpenAIのStructured Outputs機能(json_schema)

openai-structured-output.ts
import OpenAI from "openai";
import { zodResponseFormat } from "openai/helpers/zod";
import { z } from "zod";

const client = new OpenAI();

const ArticleSchema = z.object({
  title: z.string(),
  summary: z.string().max(200),
  keywords: z.array(z.string()).max(5),
  difficulty: z.enum(["beginner", "intermediate", "advanced"]),
});

type Article = z.infer<typeof ArticleSchema>;

async function generateArticleMeta(content: string): Promise<Article> {
  const response = await client.beta.chat.completions.parse({
    model: "gpt-4o-2024-08-06",
    messages: [
      {
        role: "system",
        content: "技術記事のメタデータを生成します。",
      },
      {
        role: "user",
        content: `以下の記事のメタデータを生成してください:\n\n${content}`,
      },
    ],
    response_format: zodResponseFormat(ArticleSchema, "article_meta"),
  });

  const parsed = response.choices[0].message.parsed;
  if (!parsed) throw new Error("パースに失敗しました");
  return parsed;
}

const meta = await generateArticleMeta(
  "TypeScriptのジェネリクスを使うと、型安全なコレクションが実装できます..."
);
console.log(meta.title, meta.difficulty); // 型が保証される

日本語プロンプトで精度を上げるテクニック

最新のLLMは日本語でも高い精度を持ちますが、いくつかのコツを知っておくとさらに良い結果が得られます。

テクニック 説明 効果
敬体/常体を統一する プロンプト全体で「ですます調」か「だ・である調」を統一 文体の乱れによる混乱を防ぐ
カタカナ技術用語を正確に使う 「ミドルウェア」ではなく「middleware」と英語表記も併記 技術的な誤解を防ぐ
読点・句点を適切に使う 長い指示は読点で区切り、文意を明確にする 指示の解釈ミスを防ぐ
「〜してください」より「〜せよ」 命令形の方が簡潔でトークン節約になる場面も コスト最適化
英語プロンプトと比較テスト 同じタスクを英語でも試してみる 精度差を確認して高い方を採用

日本語と英語の使い分け

bilingual-prompt.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

// 英語プロンプトで高精度を狙い、日本語で出力させるパターン
async function analyzeCode(code: string): Promise<string> {
  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 512,
    messages: [
      {
        role: "user",
        // 指示は英語、出力指定は日本語
        content: `Analyze the following TypeScript code for potential bugs and type safety issues.
Respond in Japanese with specific line references.

Code:
${code}`,
      },
    ],
  });
  return (response.content[0] as { text: string }).text;
}

RAGシステムでのプロンプト設計

RAG(Retrieval-Augmented Generation)は、ベクトル検索で取得したドキュメントをプロンプトに注入して回答精度を上げる手法です。プロンプトの設計がRAGシステムの品質に直結します。

RAGプロンプトのテンプレート

rag-prompt.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

interface Document {
  id: string;
  title: string;
  content: string;
  score: number; // 類似度スコア
}

function buildRagPrompt(query: string, documents: Document[]): string {
  // 関連度スコアでソートして上位のドキュメントを使う
  const topDocs = documents
    .sort((a, b) => b.score - a.score)
    .slice(0, 3);

  const context = topDocs
    .map((doc, i) => `[文書${i + 1}] ${doc.title}\n${doc.content}`)
    .join("\n\n---\n\n");

  return `以下の参考文書をもとに、質問に回答してください。

【重要なルール】
- 参考文書に記載されている情報のみを使って回答してください
- 文書にない情報を推測・補完しないでください
- 確信が持てない場合は「文書には記載がありません」と明記してください
- 回答の根拠となる文書番号を[文書X]の形式で明示してください

===参考文書===
${context}
=============

質問: ${query}`;
}

async function ragAnswer(
  query: string,
  documents: Document[]
): Promise<string> {
  const prompt = buildRagPrompt(query, documents);

  const response = await client.messages.create({
    model: "claude-opus-4-6",
    max_tokens: 1024,
    messages: [{ role: "user", content: prompt }],
  });

  return (response.content[0] as { text: string }).text;
}

// 使用例
const docs: Document[] = [
  {
    id: "1",
    title: "TypeScriptの型ガード",
    content: "型ガードはruntime で型を絞り込む関数です。is キーワードを使います。",
    score: 0.92,
  },
  {
    id: "2",
    title: "TypeScriptのGenerics",
    content: "Genericsを使うと型安全な汎用コンポーネントを作れます。",
    score: 0.75,
  },
];

const answer = await ragAnswer("型ガードとは何ですか?", docs);
console.log(answer);
RAGプロンプト設計のポイント
「文書に記載がない場合は答えない」という制約を必ず入れることで、幻覚(ハルシネーション)を防げます。文書番号の引用を求めることで根拠が明確になり、デバッグも容易になります。

プロンプトのバージョン管理と評価

プロンプトはソースコードと同様に管理すべき資産です。バージョン管理・A/Bテスト・自動評価を整備することで、継続的な品質改善が可能になります。

プロンプトを外部ファイルで管理する

prompts/code-review.v2.json
{
  "version": "2.0.0",
  "name": "code-review",
  "description": "TypeScriptコードレビュー用プロンプト",
  "system": "あなたはシニアTypeScriptエンジニアです...",
  "variables": ["code", "context"],
  "template": "以下のTypeScriptコードをレビューしてください。\n\nコンテキスト: {{context}}\n\nコード:\n{{code}}",
  "updated_at": "2025-03-01"
}
prompt-manager.ts
import * as fs from "fs";
import * as path from "path";

interface PromptConfig {
  version: string;
  name: string;
  description: string;
  system: string;
  variables: string[];
  template: string;
  updated_at: string;
}

class PromptManager {
  private promptsDir: string;

  constructor(promptsDir: string) {
    this.promptsDir = promptsDir;
  }

  load(name: string, version?: string): PromptConfig {
    const pattern = version ? `${name}.v${version}.json` : `${name}.json`;
    const filePath = path.join(this.promptsDir, pattern);
    return JSON.parse(fs.readFileSync(filePath, "utf-8")) as PromptConfig;
  }

  render(config: PromptConfig, variables: Record<string, string>): string {
    return config.variables.reduce(
      (template, varName) =>
        template.replace(
          new RegExp(`\\{\\{${varName}\\}\\}`, "g"),
          variables[varName] ?? ""
        ),
      config.template
    );
  }
}

// 使用例
const manager = new PromptManager("./prompts");
const promptConfig = manager.load("code-review", "2");
const userMessage = manager.render(promptConfig, {
  code: "const x: any = getValue();",
  context: "ECサイトの商品取得API",
});

console.log(promptConfig.system); // システムプロンプト
console.log(userMessage); // ユーザーメッセージ

プロンプトの自動評価

prompt-eval.ts
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

interface EvalCase {
  input: string;
  expected: string; // 期待する出力のキーワードや部分文字列
}

interface EvalResult {
  case: EvalCase;
  output: string;
  passed: boolean;
  score: number;
}

async function evaluatePrompt(
  systemPrompt: string,
  evalCases: EvalCase[]
): Promise<{ results: EvalResult[]; averageScore: number }> {
  const results: EvalResult[] = await Promise.all(
    evalCases.map(async (evalCase) => {
      const response = await client.messages.create({
        model: "claude-opus-4-6",
        max_tokens: 512,
        system: systemPrompt,
        messages: [{ role: "user", content: evalCase.input }],
      });

      const output = (response.content[0] as { text: string }).text;

      // 簡易スコアリング:期待キーワードが含まれているか
      const passed = output
        .toLowerCase()
        .includes(evalCase.expected.toLowerCase());
      const score = passed ? 1 : 0;

      return { case: evalCase, output, passed, score };
    })
  );

  const averageScore =
    results.reduce((sum, r) => sum + r.score, 0) / results.length;

  return { results, averageScore };
}

// 評価実行例
const evalCases: EvalCase[] = [
  {
    input: "anyを使っているコード: const x: any = 1;",
    expected: "any",
  },
  {
    input: "型が正しいコード: const x: number = 1;",
    expected: "問題",
  },
];

const { results, averageScore } = await evaluatePrompt(
  "TypeScriptコードの型安全性をレビューしてください。",
  evalCases
);

console.log(`平均スコア: ${(averageScore * 100).toFixed(1)}%`);
results.forEach((r) => {
  console.log(`${r.passed ? "✓" : "✗"} ${r.case.input.slice(0, 30)}...`);
});

避けるべきプロンプトのアンチパターン

アンチパターン 問題点 改善策
「できる限り詳しく教えてください」 出力量が無制限になり、不要な情報が増える 「300字以内で3点に絞って」と具体的に指定
「わかったらOKと言ってください」の多用 本当に理解しているか確認できない サンプル出力を1件求めて検証する
プロンプトへのロールプレイ依頼(「悪役を演じて」等) モデルの安全フィルターを迂回しようとすると拒否される 正当なユースケースを明確に説明する
同じ文脈で大量のSystem Prompt変更 モデルが混乱し、指示を無視することがある 新しいconversationを開始する
結果の無検証 LLMは確信を持った嘘をつく(ハルシネーション) Zodバリデーション + 人間レビューを組み合わせる

まとめ

プロンプトエンジニアリングは「AIをうまく使う技術」の中核です。本記事で紹介した技法を状況に応じて組み合わせることが重要です。

技法 使いどころ コスト
Zero-shot シンプルな分類・生成タスク
Few-shot 特定のフォーマットや基準に合わせたいとき 低〜中
CoT 複雑な推論・デバッグ・判断タスク
ReAct ツール呼び出しを伴うエージェントタスク 中〜高
Self-consistency 精度最優先の重要な判断
構造化出力 プロダクション環境でのAPI連携 低(Zodで型保証)
RAGプロンプト 外部知識を参照する必要があるとき

まずZero-shotで試し、精度が足りなければFew-shot→CoT→Self-consistencyと段階的に強化するのがコストパフォーマンスの良いアプローチです。プロンプトはコードと同様にバージョン管理・テスト・継続的改善のサイクルを回すことで、プロダクションレベルの品質が実現できます。

次のステップ
プロンプトエンジニアリングをマスターしたら、Claude API × TypeScript入門OpenAI API × TypeScript入門も合わせて読むことで、実装の幅が一気に広がります。

よくある質問

QプロンプトエンジニアリングはどのLLMにも効果がありますか?

Aはい。GPT-4o・Claude・Gemini・Llamaなど主要なLLMすべてに効果があります。ただし、モデルによって得意な指示形式が異なるため、使用するモデルで検証することを推奨します。Claudeは詳細な指示と構造的なプロンプトに強く、GPT-4oは Few-shot の例示に素直に反応する傾向があります。

QChain-of-Thoughtは常に使った方が良いですか?

Aそうとは限りません。単純な分類・翻訳・抽出タスクではCoTを使うとかえってトークンが無駄になります。複雑な推論・数値計算・コードデバッグなど、「複数ステップの思考が必要なタスク」でのみ使うのが最適です。

Q構造化出力でもモデルが間違ったフォーマットを返すことがありますか?

Aあります。特に無料モデルや低コンテキスト長の環境では、JSONが崩れることがあります。OpenAIのStructured Outputs(json_schemaモード)ではほぼ100%正しい形式が返りますが、それ以外の場合はZodによるバリデーション + エラー時のリトライロジックを実装することを推奨します。

Qシステムプロンプトの最適な長さはどのくらいですか?

A目安は300〜1000トークン(日本語で約200〜700字)です。長すぎるとコンテキスト長を圧迫し、モデルが末尾の指示を忘れることがあります(Lost in the Middle問題)。最重要なルールは冒頭に配置し、詳細は後半に置く構成が効果的です。

QRAGとFew-shotはどう使い分けますか?

AFew-shotは「出力形式や判断基準を教える」のに使います。RAGは「知識を補充する」のに使います。例えば、社内ドキュメントを参照して回答させたい場合はRAG、特定のJSON形式で出力させたい場合はFew-shotが適しています。両方を組み合わせることも多く、Few-shot例 + RAGコンテキストの構成はよく使われます。

QプロンプトのA/Bテストはどうやってやりますか?

A評価用のテストケース(入力と期待出力)を20〜50件用意し、プロンプトAとBの両方で実行して正解率を比較します。本記事のprompt-eval.tsのサンプルをベースに、スコアリング関数を強化するとよいでしょう。LangSmith・PromptLayer・Braintrustなどのプロンプト管理ツールを使うと、実運用での管理が楽になります。