コンテンツにスキップ

LLM から構造化 JSON を確実に取り出す#

Techniques #json-mode #structured-output #llm updated 2026-04-13 5 min read

LLM から構造化データ(JSON)を取り出す際、JSON ModeFunction Calling を使わないと、プレーンテキストの中に JSON が混じって返ってきて、パースに失敗しやすい。確実に取り出す方法を整理する。

3 つのアプローチ#

flowchart TD
    Q[JSON で取得したい] --> A{API に<br/>対応機能?}
    A -->|あり| B[JSON Mode や<br/>Function Calling を使う]
    A -->|なし| C{多少の揺れ OK?}
    C -->|はい| D[プロンプトで指示 +<br/>堅牢なパース]
    C -->|いいえ| E[スキーマバリデーション +<br/>リトライ]

アプローチ 1: JSON Mode / Function Calling#

OpenAI, Anthropic 等の主要 API は JSON Mode またはそれに相当する機能を提供する。これを使えば、文法的に正しい JSON が保証される(スキーマ遵守は別問題)。

# OpenAI 例
response = openai.chat.completions.create(
  model="gpt-4o",
  response_format={"type": "json_object"},
  messages=[...]
)
  • 利点: JSON として parse できることが保証される
  • 欠点: キーの有無・型までは保証されない。スキーマバリデーションは別途必要

アプローチ 2: スキーマ指定(Structured Output)#

JSON Schema を渡せる API(OpenAI の response_format: json_schema 等)を使えば、型やキーの存在まで保証できる。

response_format = {
  "type": "json_schema",
  "json_schema": {
    "name": "task_spec",
    "schema": {
      "type": "object",
      "properties": {
        "feature": {"type": "string"},
        "priority": {"enum": ["high", "medium", "low"]},
      },
      "required": ["feature", "priority"],
    }
  }
}
  • 利点: スキーマ遵守が API レベルで保証される
  • 欠点: API が対応している必要がある。複雑なスキーマだと遅くなる

アプローチ 3: プロンプト指示 + パース#

JSON Mode がない環境では、プロンプトで指示しつつパース側で堅牢に処理する。

プロンプト側:

回答は有効な JSON のみを返してください。説明文や markdown フェンスは含めないでください。

パース側の工夫:

import re, json
def extract_json(text):
    # ```json ... ``` で囲まれていたら中身を取り出す
    m = re.search(r'```(?:json)?\s*(\{.*?\})\s*```', text, re.DOTALL)
    if m:
        text = m.group(1)
    # 最初の { から最後の } までを抽出
    start = text.find('{')
    end = text.rfind('}')
    if start >= 0 and end > start:
        text = text[start:end+1]
    return json.loads(text)

バリデーションとリトライ#

どのアプローチでも、返ってきた JSON をスキーマでバリデーションし、失敗したらリトライする仕組みを組み込む。

flowchart TD
    R[LLM 呼び出し] --> P[JSON パース]
    P -->|成功| V[スキーマ検証]
    P -->|失敗| T{リトライ< 3回?}
    V -->|合格| OK[採用]
    V -->|不合格| T
    T -->|はい| R
    T -->|いいえ| F[失敗として扱う]

アンチパターン#

  • プロンプトで「JSON で返して」とだけ指示: markdown フェンス付きで返ったり、説明文が混じる
  • バリデーションなしで使う: 必須キーがないまま下流で KeyError
  • リトライなし: たまに失敗するのを放置して、本番で落ちる

まとめ#

原則: 使える API なら JSON Mode / Structured Output を必ず使う。ない場合もプロンプト指示・パース・バリデーション・リトライの 4 層で守る。

関連エントリ#