從 LLM API 拿到可靠的 JSON,過去意味著小心翼翼地寫 prompt、防禦性地解析。結構化輸出改變了這一點,但它帶來兩個層次,團隊把它們混為一談時後果自負:“響應是有效的 JSON”和“響應符合你的 schema”。第二個層次如今在兩個主要生態裡都存在,而且都帶著同一個星號:只支援 JSON Schema 的一個子集,而不是全部。把嚴格模式當成一攬子保證的架構,會在生產裡碰上那個星號。本指南比較兩家廠商各自如何實現結構化輸出,逐一走一遍在嚴格模式下依然存在的三類失效模式,並列出無論文件承諾什麼都值得保留的防線。
結構化輸出的 API 契約,分兩部分
兩個生態都把這項能力拆成同樣的兩種形態,選對是第一個決定。
約束答案格式。OpenAI 文件記錄了response_format在 Chat Completions 介面上配合 schema 定義使用的方式,type: "json_schema"以及等價的配置塊text.format同樣配合 schema 定義type: "json_schema"和格式型別strict: true在較新的 Responses 介面上的用法。Anthropic 把 schema 放進output_config.format輸出配置裡,配合type: "json_schema"格式型別使用。
約束工具呼叫。當模型呼叫你的函式時,真正重要的 schema 是函式的引數 schema。OpenAI 在函式定義裡應用strict: true嚴格模式;Anthropic 通過strict: true提供嚴格工具呼叫,文件把它描述為對工具名和輸入保證 schema 校驗。
兩家廠商對分工都說得很明確:一個機制管模型說什麼,另一個管它如何呼叫你的程式碼。Anthropic 的頁面把 JSON 輸出框定為“Claude 說什麼”,把嚴格工具呼叫框定為“Claude 如何呼叫你的函式”,並指出兩者可以獨立使用,也可以在同一請求裡一起用。如果你的整合目前把兩者搞混了,僅這一點就能解釋一類間歇性的詭異現象。
json schema 嚴格模式到底承諾了什麼
來自文件本身的誠實版本。OpenAI 的對比表把兩種模式區分得很清楚:JSON 模式保證響應在語法上有效,但不遵循 schema;Structured Outputs 則遵循 schema,前提是在受支援的子集之內。列出的好處值得照原樣讀:可靠的型別安全,免去校驗或重試格式錯誤響應的需要;對基於安全的場景提供可被程式檢測的拒絕;以及更簡單的 prompt。
Anthropic 把它的機制描述為受約束解碼,同樣具備三個特性:始終有效(沒有解析錯誤)、型別安全(保證欄位型別和必填欄位)、可靠(不會因違反 schema 而需要重試),而 schema 本身受文件記載的限制約束。
所以準確的說法是:嚴格模式消除了由格式錯誤或不符合 schema 的輸出所導致的那一類失敗——前提是 schema 落在受支援的子集內。它並沒有消除處理拒絕、截斷,以及 schema 落在子集之外的各種情形的需要。記住這個區別;下面的一切都建立在它之上。
OpenAI:response_format json_schema、strict 與受支援的子集
在 OpenAI 上,現代做法取代了更早的 JSON 模式,type: "json_object"後者只保證 JSON 有效,response_format而 json schema 形式從gpt-4o-mini和gpt-4o-2024-08-06快照及之後版本開始支援,所以更老的模型仍停留在較弱的模式上。
Supported schemas 那一節是你在寫第一個 schema 之前該讀的部分。受支援的型別包括 String、Number、Boolean、Integer、Object、Array、EnumanyOf、null 等型別。受支援的約束包括:pattern字串格式取值的白名單、format數字的上下界(例如最小值與minimum最大值)、maximum陣列的長度限制(例如最小條數與minItems最大條數)maxItems。任何不在受支援列表內的東西都不在保證範圍內,即使請求成功。實用的習慣是:釋出 schema 之前對照那一節掃一遍,並讓嚴格 schema 保持無聊;enum、必填欄位和樸素型別通行無阻,奇特的組合則走不通。
同一頁面還有兩條運維說明:拒絕是可被程式檢測的,所以它應該是你的程式碼要分支處理的狀態,而不是記進日誌的異常;不完整的響應,例如在 token 上限處被截斷的響應,是一個你必須顯式處理的邊界情況。對於仍在使用 JSON 模式的人:它需要一條顯式指令來要求輸出 JSON,否則可能行為異常;這也是更該選有 schema 支撐的模式的另一個理由。
Anthropic:output_config.format 與嚴格工具呼叫
Anthropic 的結構化輸出頁面把該特性描述為從 agent 工作流獲取經過校驗的 JSON 結果,通過兩個互補的特性交付:一個是 JSON 輸出,用於抽取資料、生成報告或格式化 API 響應;output_config.format另一個是嚴格工具呼叫,用於經過 schema 校驗的工具名和輸入;前者在輸出配置裡開啟,strict: true後者在工具定義的相關選項裡開啟。
機制是:你把 JSON schema 放在output_config.format輸出配置裡,並指定格式型別type: "json_schema",Claude 就會在響應的文本內容塊裡返回與該 schema 匹配的有效 JSON。SDK 可以替你完成接線:Python 的client.messages.parse(..., output_format=PydanticModel)助手接受一個 Pydantic model,轉換它的 schema、傳送並校驗響應,最後以parsed_output解析後的物件返回;TypeScript 用一個輔助函式包裹 Zod schema,並從zodOutputFormat()響應裡讀取parsed_output解析後的client.messages.parse()內容。
限制那一節值得給 OpenAI 那一節同樣的尊重。有些約束關鍵字不會被髮送到線上:文件記載的轉換步驟會移除不受支援的約束,例如minimum數值上下界maximum、minLength字串模式maxLength等關鍵字,把它們追加到欄位描述裡,給物件加上additionalProperties: false禁止額外屬性的設定,並把字串格式過濾到一個受支援的列表。如果你傳入使用不受支援特性的原始 schema,會得到帶詳情的 400 錯誤。SDK 助手各有各的脾性:transform_schema()有的只返回轉換後的 schema 而不傳送任何請求,在你想檢查實際會發出去什麼時很有用;Java 的 SDK 會在任何請求發出之前做本地校驗、拒絕不受支援的約束關鍵字,但要注意它檢查的是關鍵字而不是取值;而jsonSchemaOutputFormat(..., { transform: false })另一個則原樣傳送你的 schema;與zodOutputFormat()需要校驗的那個助手不同,它不校驗響應,所以違反不受支援約束的情況會直接流過。如果你要從這一節帶走一句運維上的話:搞清楚你的助手是轉換、校驗響應、兩者都做,還是都不做。
三類在嚴格模式下依然存在的失效模式
Schema 子集違規。最常見的生產故障不是輸出格式錯誤,而是一個從未真正可被強制執行的 schema。數值邊界在不同廠商那裡行為不同:OpenAI 的受支援列表包含它們,Anthropic 的轉換會把它們剝成描述,這意味著該約束只能靠會校驗的 SDK 在返回時強制執行,而完全不校驗的 SDK 則根本不執行。把 schema 寫在文件記載的子集內,凡是無法強制執行的約束,就像廠商自己的工具那樣寫進欄位描述,然後在你的本地校驗裡執行它。
拒絕。基於安全的拒絕是模型的正當行為,不是格式 bug。在 OpenAI 上它可以被程式檢測到;在任何生態裡,正確的處理方式都是把它當作一等狀態:檢測它、把它暴露給呼叫方,不要把它和解析失敗混為一談,也不要按解析失敗去重試。把拒絕混進“無效 JSON”報表的流水線,會誤導讀這些報表的人。
SDK 與 API 的漂移。結構化輸出請求的確切形態在不同介面和版本之間不同:response_format一套欄位在 OpenAI 的一個介面上,text.format另一套在另一個介面上;助手的預設行為還會決定響應是否被校驗。把你整合實際依賴的呼叫形態固定下來,並在整合測試裡放一個 schema 往返用例,這樣某次助手升級導致不再校驗時,會被 CI 抓住,而不是被下游消費者抓住。
無論如何都要保留的防線
嚴格模式是失敗率的降低器,不是錯誤處理的替代品。在響應進入系統的邊界保留本地 schema 校驗;它開銷很低,能抓住子集缺口,也能抓住以型別有效但不完整的載荷形式到來的拒絕和截斷。為截斷輸出和拒絕保留顯式的、有名字的狀態處理。並且保留你的狀態碼分診;schema 問題常常在請求時就以 400 出現,而不是以壞響應的形式出現,而這一類錯誤有自己的分診路徑。防線分層重要的另一個原因:嚴格工具呼叫正是 agent 迴圈所依賴的東西,而依賴工具呼叫有效性的 agent 在 CI 指南裡有講;當護欄被一個不做校驗的助手繞過時,agent 不會慢下來,而是靜默地行為異常。
而當 schema 或背後的模型發生變化時,讓變更先過一遍黃金集再到達使用者;變更驗證流程講了其中的順序。
從統一端點呼叫它
如果你通過一個 OpenAI 相容端點訪問多個模型,在信任結構化輸出之前,你的整合裡應該做三項檢查:確認客戶端傳送的是哪種請求形態、端點是否接受它;核實拒絕是否以可被程式檢測的形式返回;並弄清楚 schema 子集限制是在哪一側被執行的,是你這邊還是上游那邊。協議形態的問題正是相容性檢查清單的主題,而如果你正處在第一次成功呼叫的起點,快速開始是更好的入口;你所呼叫服務的端點級細節都在它自己的文件裡。
其中一個形態問題就是結構化輸出本身:response_format兩套欄位text.format分屬不同的 API 形態,正如形態指南所解釋的。
檢查清單
- 按意圖選擇機制:響應內容用 schema 支撐的答案格式,函式呼叫用嚴格的工具定義。
- 在寫程式碼之前,先把 schema 放進文件記載的受支援子集。
- 對於線上 schema 無法承載的約束,像廠商工具那樣把它們移到描述裡,並在本地強制執行。
- 搞清楚你的 SDK 助手的行為:轉換、校驗響應、兩者都做,還是都不做。
- 把拒絕和截斷當作有名字的狀態,而不是解析錯誤。
- 即使開著嚴格模式,也保留本地校驗和 400 類錯誤的分診。
- 固定你的呼叫形態,並在 CI 裡測試 schema 往返。
- 模型輪換時重跑 schema 迴歸,沿用棄用手冊裡的遷移習慣,因為受支援的子集可能隨模型代際演進而變化。
結構化輸出是現代 API 工具箱裡槓桿最高的特性之一,恰恰因為用對了會很無聊。得到無聊結果的團隊,正是那些先讀子集那一節的團隊。