このレッスンのロックを解除するには、まず「レッスン1:プロンプトエンジニアリングの基礎」を完了してください。前のレッスンに戻る

はじめに

大規模言語モデルはデフォルトでは柔軟です—あらゆるフォーマット、長さ、スタイルで回答できます。Webアプリ内での実プロダクション利用では、その柔軟性が問題になります。予測可能でパース可能な出力が必要です。このレッスンでは、LLMの出力を制御する4つのコアなテクニックを扱います:システムプロンプトによる永続的な振る舞いの設定、few-shot例による実例を通じた出力パターンの学習、構造を保証するフォーマット強制、そしてモデルが想定外の挙動をしたときのためのエッジケース対応です。

システムプロンプトとユーザープロンプト

システムプロンプトは、ユーザーメッセージより高い優先度を持つ独立したメッセージとして送信されます。会話全体を通じてモデルの役割、制約、トーンを定義します。ユーザープロンプトは実際のタスクや質問です。システムプロンプトは安定したルールに集中させ、ユーザープロンプトは変動するタスクに集中させてください。

typescript
const response = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: "You are a JSON API. Respond ONLY with valid JSON. Never include explanations, markdown fences, or extra text. If you cannot comply, return {\"error\": \"reason\"}." },
{ role: "user", content: "List 3 colors" }
],
temperature: 0
});

`temperature: 0` に注意してください—構造化された出力が必要な場合は、ランダム性をゼロに固定してください。temperatureを上げると、強いシステムプロンプトを使っていてもフォーマットのブレが生じます。

Few-Shot Examples

Few-shotプロンプティングとは、希望する入力から出力へのマッピングを説明する代わりに、その正確な例を2〜4個示すことです。モデルはパターンマッチングによって学習します。例はユーザープロンプト内に置くか、ユーザー/アシスタントの交互メッセージペアとして配置します。

typescript
const messages = [
{ role: "system", content: "Extract the product name and price from review text." },
{ role: "user", content: "Review: The Sony WH-1000XM5 headphones cost $399." },
{ role: "assistant", content: '{"product": "Sony WH-1000XM5", "price": 399}' },
{ role: "user", content: "Review: Got the Instant Pot Duo for $99, works great." },
{ role: "assistant", content: '{"product": "Instant Pot Duo", "price": 99}' },
{ role: "user", content: "Review: My new Logitech MX Master 3S was a steal at 79 dollars." },
];

3番目のユーザーメッセージにはアシスタントの返信がありません—モデルがそれを埋めます。2つ一貫した例を見たため、ほぼ常に同じJSONの形状に従います。

フローチャート:システムプロンプト+few-shot例がLLMに入力され、Webアプリが消費する構造化されたJSON出力を生成する様子

フォーマット強制の戦略

モデルが頑固でフォーマット指示を無視する場合は、以下の対策を順に重ねてください。第一に、「『Here is your JSON:』と書かないこと」のような明示的な否定例でシステムプロンプトを強化します。第二に、構造化出力モード(JSONモードまたはtool/function calling)をサポートするモデルに切り替えます。APIが有効なJSONを保証するためです。第三に、コードに後処理ステップを追加し、スキーマで出力をバリデートし、失敗時にリトライします。

typescript
import { z } from "zod";
import { ZodError } from "zod";
const ColorList = z.object({
colors: z.array(z.string()).length(3)
});
async function getColors() {
const raw = await callLLM("List 3 colors");
try {
const parsed = ColorList.parse(JSON.parse(raw));
return parsed;
} catch (e) {
if (e instanceof ZodError) {
return { colors: [] as string[] };
}
throw e;
}
}

Zodスキーマは契約として機能します—LLMが文字列の代わりに数値を返したり、3つではなく4つの色を返した場合、parseがエラーを投げ、フォールバックが発動します。プロダクションコードでは生のLLM出力を決して信用しないでください。

エッジケースの対応

頻繁に発生する3つのエッジケースがあります。第一に、モデルがデータの代わりに謝罪を返し拒否する場合—拒否フレーズを検知し、より明確なプロンプトでリトライすることで対応します。第二に、モデルがスキーマに存在しないフィールドを幻覚(ハルシネート)する場合—JSON.parseだけでなく、スキーマでバリデートすることで対応します。第三に、トークン上限に達して出力が途切れる場合—明示的にmax_tokensを設定し、長いリクエストを分割するか、簡潔に答えるようモデルに依頼することで対応します。

まとめ

LLMの出力を制御するのは1つのトリックではなく、防御の積み重ねです。明確なシステムプロンプトから始め、フォーマットが曖昧な場合はfew-shot例を追加し、スキーマとバリデーションで構造を強制し、拒否・ハルシネーション・途切れには常にフォールバックを用意してください。次のレッスンでは、モデルが自由形式のテキスト生成ではなくツールを選択できるようにするfunction callingを扱います。

レッスンの練習

レッスンのチェックポイント

学んだ内容を復習し、回答へのフィードバックを受けましょう。

0/1
回答済み · 0/1