AI 如何生成符合 JSON Schema 的 JSON?從 Prompt 到 Structured Output

從純提示詞、JSON Mode 到各廠商 Structured Outputs,講清 JSON Schema 如何約束模型輸出、OpenAI / Gemini / Anthropic 對照、落地校驗流水線,以及與 Tool Calling 的分工。

本系列前幾篇分別講了為什麼 Agent 需要 JSON(《Tool Calling 到 MCP 的資料流》)、JSON Schema 為何成為基礎設施(《Schema、Function Calling 與 MCP 演進》),以及 Gemini 一家的 Structured Output 設定(《Gemini API 教程》)。

本文把視角拉高:不論你用的是哪家模型 API,怎樣從「請在回覆裡輸出 JSON」走到「輸出必定符合這份 JSON Schema」。這是 2024–2026 年各廠商 converged 的能力,名字都叫 Structured Outputs / JSON Schema mode,但設定欄位、支援子集、與 Tool Calling 的邊界略有差異。

四個層級:一層比一層硬

團隊裡常見四種「讓模型出 JSON」的做法,可靠性差一個數量級:

層級做法你控制什麼典型失敗
L0只在 Prompt 寫「請輸出 JSON」軟約束```json 圍欄、解釋文字、單引號、尾逗號
L1Prompt + 示例(few-shot JSON)形狀有樣例,無硬約束欄位名拼寫漂移、缺欄位、型別混用
L2JSON Mode(response_format: json_object 等)輸出必須是合法 JSON能 parse,但 price 可能是字串
L3Structured Output + JSON Schema欄位、型別、enum、必填語義幻覺、截斷、個別關鍵字被忽略

生產抽取、分類標籤、填表入庫,至少做到 L3。L0–L1 適合探索;L2 適合形狀多變、只需保證能 JSON.parse 的場景。L3 才是「程式可以直接消費」的契約。

JSON Schema 管什麼、不管什麼

JSON Schema 是一份描述 JSON 檔案結構的後設資料:有哪些欄位、各是什麼型別、哪些必填、列舉取值範圍、陣列元素形狀等。各廠商 Structured Output 本質上都是把這份 Schema 編譯進生成過程,而不是隻貼在 Prompt 裡當說明。

Schema 能管:語法形狀(object / array / string / integer)、required、enum、minimum / maximum、additionalProperties: false(禁止未宣告欄位)、巢狀物件與陣列。

Schema 管不了:業務正確性。例如「total_cents 必須等於各行 qty × unit_price 之和」——這類不變數要在 Schema 校驗透過後再用程式碼斷言。也不要指望 Schema 替你做事實核查:型別對的幻覺值(編造的發票號)仍可能出現。

和 Tool Calling 裡工具的 inputSchema 是同一套語言;區別只是 Structured Output 約束的是最終答覆,Tool Calling 約束的是工具引數。詳見《資料流解析》。

約束解碼:為什麼 Schema 比 Prompt 硬

Prompt 只能提高「模型願意遵守」的機率。Structured Output 走的是約束解碼(constrained decoding):在生成每個 token 時,解碼器根據當前已輸出片段和 Schema,把會導致 JSON 語法錯誤或 Schema 違規的 token 機率壓到接近零。

因此你拿到的文字通常已經是一份可解析、形狀正確的 JSON,而不必先寫正則剝 markdown 圍欄。各廠商實現細節不同(有限狀態機、grammar、logit mask 等),但對開發者暴露的介面一致:把 Schema 交給 API,而不是只寫在 Prompt 裡。

注意:約束解碼保證的是結構,不是語義。上線後仍應用同一份 Schema 做二次校驗,並加上業務規則層。

OpenAI、Gemini、Anthropic 對照

概念相同,欄位名各有一套。下面以「抽取一張發票物件」為例:

廠商JSON ModeStructured Output / Schema備註
OpenAIresponse_format: { type: "json_object" }response_format: { type: "json_schema", json_schema: { name, schema, strict: true } }strict: true 時儘量拒絕 Schema 外欄位;配合 Pydantic model_json_schema()
Google GeminiresponseMimeType: "application/json"同上 + responseJsonSchema 或 SDK response_schema詳見《Gemini 教程》
Anthropic依賴 Prompt + 解析output_format(Claude 結構化輸出)或 Messages API 中的 schema 約束欄位隨 SDK 版本更新;Schema 建議保持扁平

跨廠商遷移時,Schema 本體儘量用標準 JSON Schema(type、properties、required、enum),各 SDK 只負責包一層請求體。不要把 OpenAPI 3.0 大寫型別(OBJECT、STRING)和 JSON Schema 小寫(object、string)交叉貼上。

寫好一份 Schema:從 Pydantic 到線上

推薦工作流:先在程式碼裡用 Pydantic / Zod 定義型別 → 匯出 JSON Schema → 微調後送進 API。欄位說明寫進 description:它會進入模型上下文,決定「qty 是件數還是箱數」這類語義,光靠 type: integer 擋不住。

from pydantic import BaseModel, Field


class LineItem(BaseModel):
    name: str = Field(description="商品名稱")
    qty: int = Field(description="數量,正整數", ge=1)
    unit_price_cents: int = Field(description="單價,分", ge=0)


class Invoice(BaseModel):
    vendor: str
    currency: str = Field(description="ISO 4217,如 CNY")
    items: list[LineItem]
    total_cents: int

schema = Invoice.model_json_schema()
# 生產建議補上 additionalProperties: false

實用原則:

  • 根型別優先用 object,少用根級 array;部分 API 對 { "items": [...] } 更穩。
  • 先上 type / properties / required / enum,再加 additionalProperties、min/max;別把完整 Draft 2020-12 一股腦塞進去,部分關鍵字會被忽略。
  • 巢狀不要太深;迴圈引用會被拒絕,應把 Schema 寫扁。
  • Schema 與 Prompt 分工:Schema 管形狀;Prompt 管語義(「從下面文字抽取發票……」)。

OpenAI Structured Outputs 示例

OpenAI Chat Completions 在 2024 年後支援 json_schema 響應格式。strict: true 時模型應只輸出 Schema 內欄位:

from openai import OpenAI
from pydantic import BaseModel

client = OpenAI()

class Invoice(BaseModel):
    vendor: str
    total_cents: int

response = client.chat.completions.create(
    model="gpt-4o-2024-08-06",
    messages=[
        {"role": "user", "content": "從文字抽取發票:Acme 賣了 2 個鍵盤,共 398 元。"}
    ],
    response_format={
        "type": "json_schema",
        "json_schema": {
            "name": "invoice",
            "strict": True,
            "schema": Invoice.model_json_schema(),
        },
    },
)

data = response.choices[0].message.content  # JSON 字串
import json
invoice = json.loads(data)

Gemini 側對應 response_mime_type + response_json_schema,完整示例見 Gemini 專題文。Anthropic 請查閱當前 SDK 的 structured output 檔案——概念一致,欄位名以官方為準。

落地流水線:生成 → 解析 → 校驗 → 重試

Structured Output 不是「調一次 API 就結束」。建議固定四步:

  1. 生成:帶 Schema 調模型 API;記錄 prompt、Schema 版本、原始 content。
  2. 解析:JSON.parse(或 SDK 的 parsed);失敗則整段重試,不要半解析。
  3. Schema 校驗:用同一份 JSON Schema 跑 AJV / jsonschema / Pydantic;失敗則重試或降級。
  4. 業務校驗:自定義斷言(金額合計、外來鍵存在等);失敗則人工或規則引擎。

開發階段把 Schema 與 2~3 組正/反例存進倉庫,用 JSON 工具箱在瀏覽器本機看結構、做 Diff——這和測 REST 契約同一思路,只是消費者換成了 LLM。

常見坑:開了 JSON Mode 後仍要求「先解釋再輸出 JSON」;超長輸出被截斷(提高 max tokens 或拆任務);API Key 進前端演示倉庫;Schema 版本與 Prompt 不同步導致 silently ignore 新欄位。

和 Tool Calling 怎麼分工

Structured OutputTool Calling / MCP
約束物件模型對使用者的最終 JSON工具引數 JSON(inputSchema)
誰執行副作用無;只是資料宿主 / MCP Server
典型場景抽取、分類、填表、Agent 間傳遞查庫存、寫檔案、調外部 API
失敗迴流校驗失敗 → 重試或人工錯誤寫入 tool 訊息 → 再問模型

一條完整 Agent 鏈路常常是:Structured Output 抽出結構化意圖 → Tool Calling 執行動作 → Structured Output 或自然語言總結給使用者。不要用 Structured Output 假裝「已經調過支付 API」——模型沒有真的調。

常見問題 FAQ

只在 Prompt 裡寫「請輸出 JSON」夠嗎?

不夠。提示詞只能提高模型遵守的機率,仍可能出現 markdown 圍欄、尾逗號、欄位名漂移。生產環境應至少開啟 JSON Mode,最好把 JSON Schema 交給 API 的 Structured Output 通道,讓解碼階段就排除非法 token。

JSON Mode 和 Structured Output 有什麼區別?

JSON Mode 只保證輸出是合法 JSON 文字,不約束欄位名、型別和必填。Structured Output 在 JSON Mode 基礎上附帶 JSON Schema,生成每個 token 時按 Schema 過濾,形狀才穩定,才能直接入庫或傳給下一跳程式。

OpenAI、Gemini、Anthropic 的設定欄位一樣嗎?

概念相同、欄位名不同。OpenAI 用 response_format: { type: json_schema, json_schema: { schema, strict } };Gemini 用 responseMimeType + responseJsonSchema;Anthropic 用 output_format 或 tools 裡的 structured output。Schema 本體儘量用標準 JSON Schema,各 SDK 再包一層。

Structured Output 能替代 Tool Calling 嗎?

不能。Structured Output 約束的是模型對使用者的最終 JSON 答覆;Tool Calling 約束的是工具引數 JSON,且需要宿主真正執行工具。抽取、分類、填表用前者;查庫存、寫檔案、調 MCP 用後者。完整 Agent 鏈路常常兩者都用。

模型輸出還需要再校驗嗎?

需要。約束解碼能大幅降低語法錯誤和型別漂移,但不能保證語義正確(欄位型別對、值是編的)。生產環境應把同一份 JSON Schema 再跑一遍校驗器,失敗則重試、降級或人工稽核。

如何在本機驗證 Schema 與樣例輸出?

把 JSON Schema 和兩三組模型輸出樣例存成 JSON 檔案,用 JSON 工具箱在瀏覽器本機做語法校驗與結構對照,資料不上傳伺服器。

總結

讓 AI 生成符合 JSON Schema 的 JSON,正確順序是:先定 Schema,再開 JSON Mode / Structured Output,最後才寫 Prompt。Prompt 負責語義;Schema 負責形狀;Pydantic / Zod 是作者友好的前端,線上走各廠商的 Schema 通道。

建議你用一張真實發票或一段客服對話跑通:寫出 Schema → 調一次 API → 把輸出貼進校驗器對照。對得上再接資料庫或下一跳 Agent。工具引數那一側仍然走 Tool Calling / MCP,不要混成一種 API。