只要你把模型輸出接到資料庫、表單或下游 Agent,就不能再靠「提示詞裡寫請回傳 JSON」碰運氣。OpenAI Structured Outputs 用受限解碼把輸出釘在你提供的 JSON Schema 上:欄位不會丟、列舉不會亂編、型別不會在字串和數字之間漂移。本文面向要做抽取、工單分類、評測打分和多步工作流程的工程師,走完從 JSON Mode 升級到 strict: true 的完整路徑。
JSON Mode 為什麼不夠穩
JSON Mode(type: "json_object")只保證「能被 JSON 解析器讀進去」。它不保證鍵名、必填欄位、列舉集合或數字型別。生產裡最常見的三種翻車是:漏掉 severity、把 42 寫成 "42"、在 enum 外發明一個 urgent-plus。下游一旦用強型別解析,整條管線就會在隨機樣本上失敗。
| 能力 | JSON Mode | Structured Outputs |
|---|---|---|
| 輸出合法 JSON | 是 | 是 |
| 遵守你給的 JSON Schema | 否(靠提示詞) | 是(受限解碼) |
| 開啟方式 | json_object |
json_schema + strict: true |
| 典型模型 | 較早期 GPT-4o / GPT-3.5 等 | gpt-4o-2024-08-06、gpt-4o-mini 及更新快照 |
| 拒答處理 | 仍可能吐出「看起來像 JSON」的拒絕話術 | 走獨立的 refusal 欄位 |
官方把 Structured Outputs 稱為 JSON Mode 的演進:新專案應預設走 Schema,而不是在解析失敗後再重試三次。若你同時在比較不同模型的 Token 帳單,可以對照 Kimi K3 與 GPT-5.5 的 API 成本對比——結構化輸出本身幾乎不增加輸出體積,真正拉開帳單的是重試和過長推理。
response_format;Responses API 放在 text.format。欄位路徑不同,strict、additionalProperties: false 和「全部 required」這些約束完全一樣。
最小可運行請求
下面這個工單抽取 Schema 涵蓋了生產裡 80% 的需求:字串、列舉、整數,以及用 null 聯合型別模擬可選欄位。模型必須回傳所有鍵;沒有帳號 ID 時明確給 null,而不是省略鍵。
{
"model": "gpt-4o-2024-08-06",
"messages": [
{"role": "system", "content": "把使用者描述抽取成工單物件。"},
{"role": "user", "content": "結帳頁用已儲存卡會 500。今天開始。帳號 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": { "type": ["string", "null"] }
},
"required": ["summary", "category", "severity", "account_id"],
"additionalProperties": false
}
}
}
}
Responses API 的等價寫法是把同一份 Schema 放進 text.format,type、name、schema、strict 作為兄弟欄位。首次用某個 Schema 時,伺服器會編譯約束,延遲可能略高;之後同一 Schema 會被快取,後續請求回到正常水準。
strict Schema 必須滿足的規則
開啟 strict: true 之後,你提交的不再是「寬鬆 JSON Schema」,而是官方支援的子集。不合規會直接 400,而不是「盡量遵守」。
- 根必須是 object:根節點不能是
anyOf或陣列。Zod 的discriminatedUnion若產生頂層anyOf,需要包一層物件,例如{ "result": ... }。 - 每個 object 都要
additionalProperties: false:包含巢狀物件。漏一個就會被拒絕。 - properties 裡出現的鍵必須全部進入
required:可選語意用"type": ["string", "null"]或anyOf加null。 - 支援的型別:string、number、integer、boolean、object、array、enum、
anyOf。 - 字串約束:
pattern、format(email、date-time、uuid、ipv4等)。 - 數字與陣列:
minimum/maximum/multipleOf;minItems/maxItems。 - 明確不支援:
allOf、not、if/then/else、dependentRequired等組合關鍵字。 - 規模上限:合計最多約 5000 個物件屬性、10 層巢狀;所有屬性名、定義名、列舉值字串總長不超過 12 萬字元;列舉值總數最多 1000。
pattern、format、minLength、minimum、minItems 等約束目前仍可能不受支援。先在基礎快照上驗證 Schema,再決定是否微調。
工具呼叫 vs 回覆正文
同一套 strict 也能用在函式 / 工具參數上。差別是意圖:工具 Schema 描述「模型要呼叫什麼」;response_format / text.format 描述「模型要回覆使用者什麼形狀」。抽取、評分、產生 UI 狀態用後者;查天氣、寫檔案、跑指令用前者。若你在對比終端 Agent 與 GUI 操控能力,可參考 Claude Code 與 OpenAI Computer Use 的關係——那是另一層「怎麼行動」,本文解決的是「行動前後的資料契約」。
用 SDK 直接解析成物件
手寫 JSON Schema 容易漏 required。官方 SDK 提供 Pydantic / Zod 助手:從型別產生 Schema,並在回應側直接給出解析後的物件。下面是 Python 的典型寫法。
from typing import Literal, Optional
from pydantic import BaseModel
from openai import OpenAI
class SupportTicket(BaseModel):
summary: str
category: Literal["billing", "bug", "account", "other"]
severity: int
account_id: Optional[str]
client = OpenAI()
completion = client.beta.chat.completions.parse(
model="gpt-4o-2024-08-06",
messages=[
{"role": "system", "content": "抽取工單。"},
{"role": "user", "content": "結帳頁 500,帳號 acct_8842。"},
],
response_format=SupportTicket,
)
ticket = completion.choices[0].message.parsed
if completion.choices[0].message.refusal:
raise RuntimeError(completion.choices[0].message.refusal)
print(ticket.category, ticket.severity)
報錯、拒答與工程清單
上線前按這張清單過一遍,能消掉絕大多數「本機 curl 能跑、管線卻 400」的問題。
- 400 Unsupported schema:缺
additionalProperties: false、有未 required 欄位、根是anyOf,或用了allOf。 - 模型拒答:安全策略觸發時,內容不會塞進 Schema;讀取
refusal,在 UI 裡展示,不要當 JSON 解析失敗重試。 - 可選欄位變成空字串:契約裡寫了
null就要接受null;下游入庫前把null對應成 SQLNULL,不要再二次猜測。 - 列舉過寬:把業務狀態收成 5–8 個值;上百個列舉既燒上下文,也逼近 1000 條上限。
- 鍵順序:輸出鍵順序與 Schema 中宣告順序一致,串流消費時可以按欄位增量解析。
- 提示詞仍有用:Schema 管形狀,提示詞管語意。例如「severity 1–5,5 為全站故障」要寫在 system 裡,整數範圍也可以用
minimum/maximum再鎖一層。
落地時建議固定三件事:Schema 進 Git;對拒答、400、逾時分別打點;用真實工單做一組回歸樣本,而不是只用「Hello 回傳 JSON」這種玩具輸入。結構化輸出讓解析層穩定之後,你才能把精力放到分類準確率和延遲上,而不是每週修一次正規表示式。
在 Mac mini 上跑抽取管線,環境更省心
Structured Outputs 的除錯循環很短:改 Schema、跑一小批樣本、看解析物件。macOS 上 Python、Node.js、Docker 和 Homebrew 開箱即用,不必先搭 WSL。Apple Silicon 統一記憶體讓你同時開編輯器、本機校驗腳本和長上下文評測,不至於在型別檢查時把機器拖進 swap。
Mac mini M4 待機大約 4W,適合把回歸樣本集整晚掛著跑;macOS 崩潰率低,Gatekeeper 與 SIP 也降低了相依腳本被污染的風險。和同價位 Windows 盒子比,長期無人值守跑 Agent 評測時,穩定性和電費都更友善。
若你希望有一台一直在線、專門跑 JSON 回歸與模型對比的節點,了解 Kvmzen 的套餐方案,把 Schema 契約從筆電搬到固定規格的雲端 Mac 上。