教程
AI Structured Output 是什麼?JSON Schema 如何讓大模型輸出可靠 JSON
提示詞裏寫「只輸出 JSON」只能降低概率。Structured Output 把形狀釘在解碼階段,JSON Schema 再把字段、類型和枚舉寫成契約。
把大模型接到流水線裏,最怕的不是文筆差,而是返回值沒法被代碼喫掉:少一個逗號、字段改了名字、數字變成字符串。Structured Output 要解決的就是這件事——讓模型在生成 token 時就按你聲明的形狀說話,而不是先寫一段散文再指望你用正則摳出 JSON。JSON Schema 則是這份形狀最常見的書面形式。讀完你可以判斷自己卡在「語法」「形狀」還是「業務對錯」,並知道 Schema 之後仍然要本地校驗。
提示詞裏的 JSON 爲什麼不可靠
「請只返回 JSON,不要解釋」是最常見的權宜之計。它有時有效,因爲它把偏好寫進了上下文。它不可靠,因爲偏好不是約束:模型仍可能在對象外包一層 markdown 代碼塊,漏掉必填鍵,或把priority寫成更「通順」的urgency。下游一旦JSON.parse,失敗就不是文案問題,而是整條鏈路中斷。
更隱蔽的失敗是「能 parse,但形狀不對」。你期望items是數組,模型給了對象;你期望分數是數字,模型給了"0.91"。代碼讀到undefined或字符串拼接,問題會拖到很後面才爆。提示詞解決不了這類漂移,因爲它沒有在解碼階段否決非法 token。
所以不要把 Structured Output 理解成「一種更嚴厲的提示詞」。它是生成機制的一部分:服務端用 Schema 編譯出允許的下一 token 集合,模型無法吐出括號不配、鍵名不在名單裏的片段。提示詞仍然負責任務說明,形狀交給 Schema。
Structured Output 約束哪一層
可以把它想成三層,混用其中兩層就會覺得「模型胡來」。第一層是語法:輸出必須是能 parse 的 JSON。第二層是形狀:鍵名、類型、必填、枚舉必須符合 Schema。第三層是語義:分類是否選對、金額是否真實。Structured Output 覆蓋前兩層,第三層永遠是你的責任。
| 檔位 | 你配置什麼 | 實際保證 |
|---|---|---|
| 提示詞約定 | 「只輸出 JSON」 | 偏好,不是契約 |
| JSON 模式 | MIME 或json_object |
大概率是合法 JSON,字段仍由模型決定 |
| Schema 模式 | JSON Schema + 嚴格開關 | 形狀、類型、必填與枚舉按 Schema 約束 |
OpenAI 把 Structured Outputs 寫成 JSON mode 的下一步:兩者都能產出合法 JSON,只有前者保證貼合你提供的 Schema。官方對照見Structured model outputs。Gemini 一側同樣區分「只要 JSON」和「按 Schema 吐字段」,具體開關見站內的Gemini API JSON 輸出教程,本文不再重複 SDK 細節。
還有一個容易忽略的邊界:安全拒答、截斷、工具調用失敗,不一定還能塞進你的成功對象。有的接口會另給refusal或空內容。Schema 約束的是「一旦開始按格式說話」的那一段,不是「這次調用一定成功」。
JSON Schema 如何變成契約
JSON Schema 本來是描述 JSON 文檔的詞彙表:類型、必填、枚舉、數值範圍、數組元素。寫 Schema 的標準講法見Understanding JSON Schema。接到大模型之後,同一份詞彙表多了一層用途:它不再只用於事後校驗,而是在生成時收窄搜索空間。
對工程來說,這意味着 Schema 是編譯期和運行期共用的契約。Pydantic、Zod、Swift 的可生成類型,最後往往都要落到一份語言無關的 JSON Schema,才能交給雲端模型、寫進日誌、給測試夾具回放。字段表只維護一份,避免 App 說totalCents、API 說amount、提示詞裏又寫「金額」。
契約要寫「你真正會讀的對象」,不要寫「完整的世界模型」。每多一個可選字段,模型就多一次填錯或填空的機會。必填進required,封閉集合進enum,數字範圍用minimum / maximum。屬性上的description往往比在 prompt 裏再解釋一遍更穩,因爲約束和解碼綁在一起。
嚴格模式還有一條常見規則:對象必須additionalProperties: false,且聲明過的字段都進required。真正可選的值不要「不寫 required」,而要允許null。否則模型可能省略鍵,你的代碼還在obj.field上假設鍵一定存在。
各家 API 怎麼接 Schema
各家產品名都叫 Structured Output,包裝字段並不相同。選型時先問兩句:這份 Schema 是約束最終答覆,還是約束工具參數?當前模型快照是否真的支持嚴格模式?不要假設文檔裏的關鍵詞在所有端點行爲一致。
OpenAI:json_schema 加 strict
Chat Completions 裏把response_format設爲json_schema,並打開strict: true。Responses API 把同一件事寫在文本格式字段下,Schema 規則相同,只是外殼不同。下面這份請求抽取一張工單:類別只能是枚舉,賬號可空。
{
"model": "gpt-4o-2024-08-06",
"messages": [
{ "role": "system", "content": "Extract the ticket into the schema." },
{ "role": "user", "content": "Checkout 500s on a saved card. Account acct_8842." }
],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "support_ticket",
"strict": true,
"schema": {
"type": "object",
"properties": {
"summary": { "type": "string" },
"category": {
"type": "string",
"enum": ["billing", "bug", "account", "other"]
},
"severity": { "type": "integer" },
"account_id": {
"anyOf": [{ "type": "string" }, { "type": "null" }]
}
},
"required": ["summary", "category", "severity", "account_id"],
"additionalProperties": false
}
}
}
}
接到響應後按「整段都是 JSON」處理,不要先用正則摳代碼塊。SDK 的 parse 助手可以直接反序列化成類型對象;同時要處理拒答分支,安全策略觸發時內容可能對不上成功 Schema。
Gemini 與其他棧
Gemini 用 MIME 類型聲明「這是 JSON」,再用responseSchema或responseJsonSchema釘字段。後者更接近標準 JSON Schema,適合anyOf、$ref和數值範圍。細節、Python / JS 示例見Gemini Structured Output 教程。
Apple 的 on-device 模型走的是@Generable一類編譯期形狀,不是你手寫一份 JSON Schema 文件;但跨出進程、打 HTTP、寫日誌時,仍然要有一份可序列化的 JSON。三條調用鏈怎麼分,見Apple AI Agent 與 JSON。外部 Agent 走 MCP 或 REST 時,載荷幾乎一定是 JSON,Schema 仍然是適配層該對齊的那張表。
一份能落地的 Schema
下面這份 Schema 模擬工單分類:類別封閉、優先級是整數、摘要是字符串。這就是 Schema 該管的事——形狀,而不是「這張工單在業務上該不該標緊急」。
{
"type": "object",
"properties": {
"category": {
"type": "string",
"enum": ["billing", "bug", "feature"],
"description": "Ticket category"
},
"priority": {
"type": "integer",
"minimum": 1,
"maximum": 5
},
"summary": {
"type": "string"
}
},
"required": ["category", "priority", "summary"],
"additionalProperties": false
}
數組用items描述元素。元組感更強的固定長度列表,在 JSON Schema 路徑下看prefixItems。嵌套對象先內聯;只有第三次出現相同結構,或樹節點引用自身時,才值得上$defs + $ref。過早抽象會讓報錯更難讀:服務端拒絕 Schema 時,你要對着一份展開後的大對象排查。
Prompt 和 Schema 不要各寫一遍結構。重複描述會讓模型在兩種說法之間搖擺,也浪費輸入 token。任務說明留在提示詞,鍵名和類型只出現在 Schema。本地類型改了字段,請求裏的 Schema 必須一起改,否則線上會靜默出現新鍵或缺鍵。
約束之後仍要本地校驗
Structured Output 讓解析層安靜很多,它不保證分類正確,也不保證數字符合你的業務上限。枚舉能限制類別集合,不能阻止模型選錯類別。優先級寫成 5 合法,寫成「其實該是 2」也合法。關鍵路徑要抽檢或上規則。
落地時把模型返回值當成一份普通 JSON 來看。先JSON 格式化看清嵌套,再用JSON 校驗確認能 parse,然後把同一份對象丟進JSON Schema做本地二次校驗。和黃金集對比時用JSON Diff,一眼看出鍵順序或可空字段是否漂移。
建議存三份夾具:ticket.valid.json、ticket.missing-field.json、ticket.wrong-enum.json。第一份確認 happy path,後兩份確認你的本地校驗真的會拒絕。模型側已經約束過的錯誤,本地仍應能復現——否則哪天換模型或關掉嚴格模式,流水線會在沒有網的時候才發現。
常見問題 FAQ
Structured Output 和 JSON mode 有什麼差別?
JSON mode 保證輸出是能 parse 的 JSON。Structured Output 在此之上保證貼合你提供的 JSON Schema:鍵名、類型、必填和枚舉都按契約來。代碼要讀具名字段時,用後者。
有了 Schema 還要不要寫「只輸出 JSON」?
可以留一句短說明,但不要再把字段表抄進提示詞。形狀以 Schema 爲準。重複兩套描述會拉低質量,也浪費配額。
嚴格模式裏怎樣表示可選字段?
多數嚴格實現要求對象關閉額外屬性,並且列出的字段都必填。可選值寫成可空類型,例如字符串或 null 的聯合,而不是從 required 裏刪掉鍵。
Schema 通過了,業務還可能錯嗎?
會。Schema 管形狀,不管真僞。分類選錯、金額幻覺、該拒答卻硬填,都可能是合法 JSON。生產裏仍然要規則、抽檢或人工確認。
總結與下一步
Structured Output 不是文案技巧,而是把 JSON 形狀從「希望」改成「解碼約束」。JSON Schema 是這份約束最通用的寫法:必填、枚舉、範圍和禁止多餘鍵,決定了下游能不能穩定JSON.parse並讀到預期字段。各家 API 的開關名字不同,分層是一樣的——語法、形狀、語義,不要混爲一談。
下一步先寫一份你真正會讀的小 Schema,用嚴格模式跑通一條抽取或分類,再在瀏覽器裏對着返回值做本地校驗。需要 Gemini 請求示例時打開上一篇教程;需要看 Agent 參數如何落到 JSON 時打開 Apple 那篇。