JSON 模式不等於結構化輸出
這兩個功能常被混為一談。JSON 模式只要求模型輸出語法正確的 JSON,並不保證鍵名、型別或允許的值符合程式的預期。結構化輸出更進一步:你提供 JSON Schema,供應商在生成時加以約束或驗證,讓回應符合 Schema。當供應商在解碼階段就套用 Schema,缺少欄位或未知的列舉值就不會出現,一整類解析錯誤因此消失。
但這兩個功能都無法保證內容正確。符合 Schema 的答案仍可能很有把握地選錯標籤。請把 Schema 視為「形狀」的保證,判定本身仍需要評估與覆核。
分類判定用的一份 Schema
本文的範例都使用同一份小 Schema:一個以 enum 限制的標籤,加上一段簡短理由。對分類來說,enum 是最有用的限制,它把開放的文字欄位變成程式可以直接分支處理的封閉清單。請把 additionalProperties 設為 false,並把每個欄位列為必填;有些供應商兩者都需要才能啟用嚴格模式。
{
"type": "object",
"properties": {
"label": { "type": "string", "enum": ["billing", "technical support", "account access", "other"] },
"reason": { "type": "string" }
},
"required": ["label", "reason"],
"additionalProperties": false
}OpenAI 與 Gemini
OpenAI 的 Structured Outputs 在 Chat Completions 中以 response_format 指定:type 為 json_schema,並提供名稱、Schema 與 strict: true;Responses API 則把同一份 Schema 放在 text.format。嚴格模式只支援文件列出的 JSON Schema 子集,要求所有屬性都列為必填,且 additionalProperties 必須為 false。Python 與 JavaScript SDK 可以從 Pydantic 模型或 Zod 物件產生 Schema 並代為解析。模型因安全原因拒答時,回應會以 refusal 形式出現而不是 Schema 內容,這個分支要明確處理。舊的 json_object 模式只保證 JSON 語法正確。
Gemini 在生成設定中同時指定 response_mime_type 為 application/json 與 response_schema;Schema 格式是 OpenAPI Schema 物件的子集,新版 API 也接受標準 JSON Schema。純分類任務有個捷徑:把 response_mime_type 設為 text/x.enum 並提供 enum Schema,回應就是允許字串中的其中一個,完全沒有 JSON 外殼。過大或巢狀過深的 Schema 可能被拒絕,分類用的 Schema 請保持扁平。
Anthropic、LangChain 與 Ollama
Anthropic 模型長期以來的做法是工具呼叫:定義一個 input_schema 為你的 JSON Schema 的工具,以 tool_choice 指定強制使用它,再把工具輸入當成結構化結果讀取。請查閱你所用模型的最新 Anthropic 文件,因為除了工具呼叫之外,已陸續加入新的結構化輸出選項。
LangChain 用一個呼叫包裝這些功能:with_structured_output 接受 Pydantic 類別、TypedDict 或 JSON Schema,並回傳解析後的物件。method 參數可以在函式呼叫、JSON 模式與供應商原生的 JSON Schema 之間選擇。設定 include_raw=True 時,會連同原始訊息與解析錯誤一起回傳,正式環境應該這樣做,而不是讓例外把原始回應吃掉。
Ollama 的 chat 與 generate 端點接受 format 欄位。值為 json 時只要求合法 JSON;傳入完整的 JSON Schema 物件則會把本機模型限制在該 Schema 內。Ollama 建議同時在提示中描述預期結構並使用較低的 temperature,因為小型本機模型遵循 Schema 的穩定度不如大型雲端模型。
# OpenAI Chat Completions
response_format = {
"type": "json_schema",
"json_schema": { "name": "ticket_label", "strict": True, "schema": SCHEMA }
}
# Gemini (google-genai SDK)
config = { "response_mime_type": "application/json", "response_schema": TicketLabel }
# LangChain (any supported chat model)
labeller = llm.with_structured_output(TicketLabel, include_raw=True)
# Ollama /api/chat
{ "model": "llama3.1", "messages": [...], "format": SCHEMA, "stream": false }結構化輸出解決不了的事
Schema 無法告訴模型如何在兩個重疊的標籤之間取捨;除非你另外加一個標籤,它也無法表達「這則訊息太模糊、無法分類」。它也不會產生經過校準的信心值:由模型自己填寫的 confidence 欄位只是另一個生成的值,除非供應商回傳 token 機率,或你自行量測一致性。
它同樣不包含你的覆核政策。應用程式裡仍必須有東西決定哪些結果自動套用、哪些交給人看,以及修正如何記錄。你在原始模型呼叫外圍重建的這類邏輯越多,就越接近自己寫一套判定服務。
什麼時候判定 API 更簡單
如果任務是從清單中選一個標籤,Jev API Pro 接收文字、你的指示與允許的標籤,回傳其中一個標籤;模型有提供時附上信心值,結果低於門檻時標記為需要覆核。你不必維護各家供應商的 Schema 程式、拒答處理或解析器。若任務需要自由擷取大量欄位,供應商的結構化輸出 API 才是更合適的工具,而上面的比較會告訴你該設定哪個欄位。
