TL;DR
- LLM は「JSON を返さない」前提で組むのが、自動化の鉄則
- Direct Parse / Code Block / Regex Extraction の 3 段構えで救出
- 指示を守らせる努力と、守らなかった時の保険を両立させる
The Pain: "Here is the JSON"
LLM(GeminiやChatGPT)に return JSON only と指示しても、彼らは親切心から余計なことをします。
"Sure, here is the JSON you requested:\n
json\n{ ... }\n"
これをそのまま JSON.parse() に投げると、当然ながら SyntaxError で落ちます。
また、Markdownのコードブロック記法(```json)が含まれていたり、謎の接頭辞がついていることも日常茶飯事です。
The Solution: 3段構えのパース (Triple-Layer Parsing)
LLMの出力は「汚染されている」という前提に立ち、3段階のフィルタで救出を試みます。
- Direct Parse: そのままパース(奇跡的に綺麗な場合)。
- Code Block Extraction: マークダウン記法(```)の中身を取り出してパース。
- Regex Extraction: 文字列中の最初の
{から最後の}までを正規表現で無理やり切り出してパース。
これでもダメなら諦めますが、99%はこの網にかかります。
The Implementation: 実際のコード
scripts/generate_gemini.mjs で実際に稼働しているコードです。
function parseJsonFromGeminiText(text) {
if (!text) throw new Error("Empty response text");
// Strategy 1: 素直にパース
try {
return JSON.parse(text);
} catch {}
// Strategy 2: Code Block (```json ... ```) から抽出
const jsonCodeBlockMatch = text.match(/```(?:json)?\s*([\s\S]*?)\s*```/i);
if (jsonCodeBlockMatch) {
try {
return JSON.parse(jsonCodeBlockMatch[1].trim());
} catch (e) {
console.warn("Failed to parse from code block");
}
}
// Strategy 3: Brute Force ({ ... }) 抽出
// 最初の '{' と 最後の '}' を見つける
const jsonMatch = text.match(/\{[\s\S]*\}/);
if (jsonMatch) {
try {
return JSON.parse(jsonMatch[0]);
} catch (e) {
console.warn("Failed to parse from fallback extraction");
}
}
throw new Error("JSON parse failed: " + text.slice(0, 100) + "...");
}
プロンプト側での工夫
もちろん、まずプロンプトで釘を刺すことも重要です。
const prompt = `
...
【出力形式】
必ずJSON形式のみで出力してください。
Markdownのコードブロック(\`\`\`)や、"Here is..." などの説明文は一切含めないでください。
{ で始まり } で終わる有効なJSON文字列のみを出力してください。
...
`;
しかし、LLMは確率モデルである以上、指示を無視する可能性はゼロになりません。「指示を守らせる努力」と「守らなかった時の保険」の両方が必要です。
The Takeaway: 完璧な入力などない
Web APIの世界では「入力値検証(Validation)」は基本ですが、AIエンジニアリングの世界でもそれは同じです。むしろ、相手が人間(ユーザー)よりも「賢いが気まぐれな存在(LLM)」である分、より柔軟で粘り強いパースロジックが求められます。
次回は、これらスクリプトを定期実行するためのインフラ「GitHub Actions」について解説します。
パース戦略の比較表
| レイヤー | 手法 | 対象 | 成功率 |
|---|---|---|---|
| Layer 1 | Direct Parse | 完璧な JSON | 低 |
| Layer 2 | Code Block | json ... | 中 |
| Layer 3 | Regex | { ... } 抽出 | 高 |
2026-05 時点の最新仕様: Structured Output / response_schema
2026 年中頃の Gemini API は Structured Output(responseSchema) が GA となり、本記事の 3 段構え戦略は fallback パターン として位置付けが変わりました[公式値](Google AI for Developers - Structured Output 公式)。
現行ベストプラクティス(2026-05)
- 第一選択:
responseSchemaで JSON Schema を強制(成功率が大幅向上)[公式値] - 第二選択: 本記事の 3 段構え(Direct Parse / Code Block / Regex)を fallback として実装
- 第三選択:
response_mime_type: "application/json"を最低限指定[公式値]
OpenAI 側の同等機能は Responses API の text.format[公式値]、Anthropic 側は Tool Use の strict: true[公式値] で同様の構造化出力強制が可能です。詳細は Responses API 時代のツール呼び出し設計 を参照。
推奨実装パターン
// 2026-05 時点の Gemini SDK 例
const result = await model.generateContent({
contents: [{ role: "user", parts: [{ text: prompt }] }],
generationConfig: {
responseMimeType: "application/json",
responseSchema: {
type: "object",
properties: {
title: { type: "string" },
tags: { type: "array", items: { type: "string" } },
},
required: ["title"],
},
},
});
try {
return JSON.parse(result.response.text());
} catch (e) {
// 本記事の 3 段構え fallback を呼ぶ
return tripleLayerParse(result.response.text());
}
実運用では 第一選択 + 本記事の fallback という二重構造で 成功率 95%+ が現実的(経験則)。LLM 出力の幻覚対策の上位設計は エラーハンドリング設計ガイド と Responses API 時代のツール呼び出し設計 を参照。
FAQ
Q1. responseSchema を使えば 3 段構えは不要ですか?
不要にはなりません(経験則)。responseSchema でも稀にスキーマ違反が発生するため、fallback として 3 段構えを残すのが現実解。「第一選択は schema、最後の砦は本記事の戦略」という二段構えが安定します。
Q2. OpenAI / Anthropic でも同じ戦略は使えますか?
使えます(経験則)。OpenAI Responses API の text.format[公式値]、Anthropic Tool Use の strict: true[公式値] でも稀にスキーマ違反が起きるため、本記事の 3 段構え fallback を共通実装として持つ価値があります。詳細は Responses API 時代のツール呼び出し設計。
Q3. JSON Schema が複雑だとモデルが混乱しませんか?
混乱します(経験則)。ネストを 2 段以内、プロパティ数を 10 個以内に抑えるのが現実解。それ以上が必要なら、Schema を分割して 複数回呼び出すか、Tool Use で構造化された関数呼び出しに切り替えます。
Q4. レイテンシと正確性のトレードオフは?
responseSchema 使用時は レイテンシが 1.2-1.5 倍になる傾向があります(経験則)。バッチ処理なら許容、リアルタイム応答なら schema を最小化する判断が必要。詳細な観測指標は AIエージェントの可観測性と障害解析 を参照。
Q5. テスト・評価はどう設計すべきですか?
3 段構えで設計します(経験則): (1) 期待 JSON で 成功率の base line、(2) 不正 JSON 注入で fallback の動作確認、(3) 大量 sample で 失敗率モニタリング。詳細な LLM テスト設計は LLM 契約テストとプロンプト回帰検証 を参照。
References
- Google AI for Developers - Structured Output 公式 — responseSchema の公式仕様
- Google AI for Developers - Gemini API — API 全体ドキュメント
- OpenAI Responses API Reference — text.format による Structured Output
- Anthropic Tool Use 公式 — strict: true による厳密スキーマ強制
- JSON Schema 公式 — スキーマ定義の標準仕様
