Menu

構造化出力:LLM から確実に JSON を受け取る方法

構造化出力とは、JSON、表、テンプレートのような決まった形で答えるようモデルに頼むことです。そうすれば、プログラムも人も形を整え直さずに使えます。スキーマを示し、値がないときの扱いを伝え、結果をコードで検証します。

このページのプロンプトはすべて編集でき、そのまま ChatGPT や Claude などの AI アプリで開けます。

構造化出力とは、あらかじめ決めた形の答えのことです。名前付きのキーを持つ JSON オブジェクト、列の決まった表、毎回同じ見出しのテンプレートなどです。答えをプログラムが読むときには必ず必要で、人が読むときにも役立ちます。どの答えも同じ見た目になるので、ざっと目を通したり比べたりしやすくなるからです。

コツは、モデルが選ぶ余地が何も残らないほど正確に形を説明することです。「JSON」という単語だけでは説明になりません。下のブロックはバグ報告から詳細を抜き出します。「主な詳細」を頼んだ場合と、スキーマを明記して頼んだ場合を比べてください。

このバグ報告から主な詳細を抜き出してください: 「昨日のアップデート以降、写真が50枚を超えるプロジェクトで『エクスポート』をタップするとアプリがクラッシュします。小さいプロジェクトは問題なくエクスポートできます。Android 14 で、アプリのバージョンは 3.2.0 です。クライアントへの納品が止まっています。」
Try it
Example replyReplies vary between models and runs.

バグ報告の主な詳細は次のとおりです。

  • 問題:プロジェクトのエクスポート時にアプリがクラッシュする
  • 発生条件:写真が50枚を超えるプロジェクトで「エクスポート」をタップする
  • 発生時期:直近のアップデート以降
  • プラットフォーム:Android 14
  • アプリのバージョン:3.2.0
  • 影響:高。クライアントへの納品が止まっている

小さいプロジェクトは問題なくエクスポートできているようです。

自由記述の答えは正確で読みやすいものの、実行するたびにラベルが変わり、深刻度は値ではなく文章になっていて、プログラムは各フィールドがどこから始まるかを推測するしかありません。JSON の答えなら、そのままバグ管理ツールに入れられます。返答がやはりコードブロックの中に入って届いていることに注目してください。チャットアプリはたいてい JSON をそのように囲みます。コードでパースするときには、これが問題になります。

JSON の頼み方

良い JSON の依頼は、頼まなければモデルが代わりに決めてしまうことに、すべて先に答えています。

  • すべてのキーを正確な綴りで。 キー名は、出力に現れるべきとおりに引用符付きで書きます。「ちょうど次のキー」と書き、モデルが余計なキーを足さないようにします。
  • すべての値の型。 文字列、数値、真偽値、文字列の配列、入れ子のオブジェクト。
  • 許される値。 深刻度やカテゴリのように値が決まった集合から選ばれるフィールドには、その一覧を示します。一覧がないと、4回の実行で「High」「high」「severe」「P1」がばらばらに出てきます。
  • 入力に値がないときの扱い。 「記載がなければ null」と書かないと、モデルはもっともらしい推測で穴を埋めがちで、推測されたアプリのバージョンは本物とまったく同じに見えます。
  • 前後には何も付けない。 「JSON だけを返し、前後に文章を付けないでください」で、親切な書き出しの一文がなくなります。

形が入れ子になっていたり特殊だったりするときは、説明するより完全なオブジェクトの例を1つ見せるほうがうまくいきます。これは形式に対する Few-shot プロンプティングです。モデルが値をまねしないよう、例の値は本当の入力とはっきり違うものにしてください。

再利用できる抽出プロンプト

このブロックは同じ依頼をパートに分けたものです。形式のパートをオフにしても、制約が JSON を求めているのでモデルは JSON を返しますが、キー名は job_title ではなく title のように自分で選んでしまい、あなたのキーを前提にしたコードは壊れます。書きかけの電話番号を補完したり、メールのドメインから会社名を推測したりするのを止めているのは制約のパートです。入力欄に本物の署名を貼り付けて試してみてください。

メールの署名から連絡先を JSON で抜き出す
Fill in
Parts
下のメールの署名から連絡先を抜き出してください。
ちょうど次のキーを持つ JSON オブジェクトを返してください: { "name": "string", "job_title": "string or null", "company": "string or null", "email": "string or null", "phone": "string or null" }
文章に含まれていない値にはすべて null を使ってください。推測したり、途中までの値を補完したりしないでください。JSON だけを返してください。
山田 花子 | データ部長 | Northwind Labs | hanako.yamada@northwind.example
Try it
Example replyReplies vary between models and runs.
{
  "name": "山田 花子",
  "job_title": "データ部長",
  "company": "Northwind Labs",
  "email": "hanako.yamada@northwind.example",
  "phone": null
}

表と決まったテンプレート

構造化出力はプログラムのためだけのものではありません。答えを自分で読むときも、Markdown の表や決まったテンプレートには同じ利点があります。見る前から、どの情報がどこにあるかがわかります。

表
Python のリスト、タプル、セットを、次の列を持つ Markdown の表で比較してください:型、順序あり、変更可能、重複可、主な用途。1つの型につき1行。表以外の文章は書かないでください。
Try it
Example replyReplies vary between models and runs.
型順序あり変更可能重複可主な用途
listはいはいはい追加、削除、並べ替えをする並び
tupleはいいいえはい座標のような、決まった値の組
setいいえはいいいえ重複の除去と、要素が含まれるかの高速な判定

長い文章ではテンプレートが同じ働きをします。見出しを順番に示し、それぞれの下に何を書くかを伝えます。「太字のラベル3つで答えて:原因、修正方法、確認方法」と書けば毎回同じ3つのラベルが出てくるので、まとめて得た答えを比べやすくなります。

API の JSON モード

いくつかのモデルの API には、構文的に正しい JSON を強制する設定があります。OpenAI の Python SDK では response_format です。JSON モードではメッセージのどこかに「JSON」という単語が含まれている必要があるので、下のシステムプロンプトではその単語を使い、キーを列挙しています。

import json
from openai import OpenAI

client = OpenAI()
MODEL = "your-model-id"  # e.g. from your provider's model list

report_text = "Since yesterday's update the app crashes when I tap Export..."

response = client.chat.completions.create(
    model=MODEL,
    response_format={"type": "json_object"},
    messages=[
        {
            "role": "system",
            "content": (
                "Extract the bug report into JSON with the keys "
                "summary (string), severity (one of low, medium, high, critical) "
                "and steps_to_reproduce (array of strings)."
            ),
        },
        {"role": "user", "content": report_text},
    ],
)

data = json.loads(response.choices[0].message.content)

JSON モードが保証するのは、テキストがパースできること(返答がトークンの上限で途切れない限り)であって、スキーマに合っていることではありません。キーが欠けることも、深刻度が「urgent」になることもあります。いくつかの提供元は、出力形式として、あるいはツール(関数)定義の入力スキーマとして、完全な JSON Schema も受け付け、その中には答えをスキーマに合わせて制約するモードもあります。こうした機能は API ごとに違うので、正確なパラメータは提供元のドキュメントで確認してください。

結果をコードで検証する

モデルの JSON は、プログラムの外から来るほかの入力と同じように扱ってください。パースして、それから確認します。パースで壊れた構文を見つけ、確認で、正しいオブジェクトなのに中身が違うものを見つけます。

ALLOWED_SEVERITIES = {"low", "medium", "high", "critical"}

def problems(data):
    if not isinstance(data, dict):
        return ["the answer must be a JSON object"]
    found = []
    if not isinstance(data.get("summary"), str):
        found.append("summary must be a string")
    if data.get("severity") not in ALLOWED_SEVERITIES:
        found.append("severity must be low, medium, high or critical")
    steps = data.get("steps_to_reproduce")
    if not isinstance(steps, list) or not all(isinstance(s, str) for s in steps):
        found.append("steps_to_reproduce must be an array of strings")
    return found

確認に失敗したら、1回の再試行で直ることがよくあります。モデル自身の出力と問題の一覧を一緒に送り、修正した JSON を頼みます。再試行の回数には上限を設け、失敗を記録してください。よく失敗するフィールドは、プロンプトがわかりにくいというサインです。大きなプロジェクトでは、Pydantic のような検証ライブラリや JSON Schema のバリデーターが、手書きの関数の代わりになります。

さらに2つの習慣で、気づかないエラーを防げます。出力トークンの上限は、予想される最大の答えに十分な高さに設定してください。途中で切れたオブジェクトは決してパースできません。そして入力が長いときやユーザーから来るときは、区切り文字や XML タグで指示と分けてください。そうすれば、その中の文章が指示として読まれにくくなります。1つのプロンプトで推論と JSON の生成の両方をしなければならないなら、プロンプトチェーンを検討してください。1つ目のステップで自由記述で考えさせ、2つ目のステップで結果を構造に変換します。

よくある質問

ChatGPT に JSON で出力させるにはどうすればいいですか?

答えは JSON でなければならないと伝え、すべてのキーとその型を列挙し、値が決まった集合から選ばれるフィールドには許される値を示します。「JSON だけを返し、前後に文章を付けないでください」と書き足します。API では、response_format={"type": "json_object"} で JSON モードもオンにします。JSON モードでは、メッセージのどこかに JSON という単語が含まれている必要があります。

モデルが JSON の前後に文章を付けてしまうのはなぜですか?

チャットモデルは会話するように学習されているので、「こちらが JSON です」のような一文から始めたり、オブジェクトを Markdown のコードブロックで囲んだりすることがよくあります。JSON だけを頼み、コード側では API の JSON モードを使うか、パースする前に前後のコードブロックの記号を取り除いてください。

JSON モードとは何ですか?

JSON モードは、返答が出力トークンの上限で途切れない限り、構文的に正しい JSON をモデルに出させる API の設定です。モデルがあなたのスキーマに従うようにするものではありません。キーが欠けていたり、綴りが違っていたり、型が違っていたりすることはあります。提供元によっては、完全な JSON Schema を受け取り、出力をそれに合わせて制約する、より厳密なモードもあります。

LLM は常に正しい JSON を返せますか?

プロンプトだけでは無理です。明確な指示でもときどき失敗しますし、出力トークンの上限で途切れた答えは必ず不正な JSON になります。すべての返答を本物の JSON パーサーでパースし、必要なフィールドを確認して、確認に通らなければ再試行するか、はっきりエラーにしてください。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める