同一份 JSON schema,透過某個 OpenRouter 模型可以穩定得到乾淨、型別正確的結果;換到下一個模型,即使 request body 完全相同,卻可能回傳不同欄位、空字串,甚至直接噴出 400 錯誤。Reddit 使用者 u/MicBeckie 曾透過 OpenRouter structured outputs 測試 Qwen 模型,結果是「10 次裡有 9 次都會出錯」;同一套設定下,OpenAI 模型卻能遵守 schema。
這並不是一個可以單純回報為 bug 的問題。OpenRouter 的 structured output 支援是以端點為單位,而不是以模型為單位;而且所謂「支援」還分成三種強制執行等級,從原生嚴格 schema 強制,到只把 schema 當成建議的供應商都有。本篇會拆解功能如何路由、實務上常見的六種失敗形式,以及讓 schema 輸出能真正上線的強化做法。強制執行機制依據官方 structured outputs 文件;失敗案例則來自文中連結的開發者討論串。
OpenRouter 所說的 structured output 支援,實際上是什麼?
OpenRouter 接受帶有 type: "json_schema" 的 response_format 參數,其中包含 schema 的 name、strict 旗標,以及 JSON Schema 本身。最小請求大致如下:
{
"model": "openai/gpt-4o",
"messages": [{ "role": "user", "content": "Extract the shipping info" }],
"response_format": {
"type": "json_schema",
"json_schema": {
"name": "shipping_info",
"strict": true,
"schema": {
"type": "object",
"properties": {
"tracking_number": { "type": "string", "description": "Carrier tracking ID" },
"carrier": { "type": "string" },
"eta_days": { "type": "number", "description": "Days until delivery" }
},
"required": ["tracking_number", "carrier", "eta_days"],
"additionalProperties": false
}
}
}
}
官方文件有兩個重點,直接決定這件事能不能運作:
- 支援狀態看端點,不看模型。 同一個模型可能由五家供應商提供服務,但只有其中兩家能正常支援 structured outputs。模型頁面的 Providers 區塊會列出各供應商的
structured_outputs參數;文件也提醒,「端點支援狀態也可能隨時間改變」。 - 這項功能的起點其實很窄。 OpenRouter 在 2024 年 12 月 12 日宣布 structured outputs 時,只有 OpenAI 4o 與 Fireworks 模型支援。其他支援都是後續由各家供應商逐步加入,因此今天整理的模型清單很快就會過時。
文件也建議每個 property 都加上 description,並設定 additionalProperties: false。原因是落在較低強制等級時,schema 同時也會成為模型的提示內容。
同樣開啟 strict,背後其實有三種執行方式
strict: true 不代表每個端點都會以相同方式處理。官方指南將供應商行為分成三個層級:
| 層級 | 供應商如何處理你的 schema | 輸出能不能信? |
|---|---|---|
| 原生嚴格模式 | 在解碼階段嚴格強制 schema | 可以:輸出在生成時就符合 schema |
| 格式轉譯 | 將你的 schema 轉換為供應商專屬的 structured output 格式 | 大致可以:但僅限該格式支援的 schema 功能 |
| 強提示 | 把 schema 當作引導資訊注入給模型 | 不行:表現好時長得像 schema,差時會亂編欄位 |
OpenRouter 不會在請求當下標示某個端點使用哪個層級;官方文件要求你參考各供應商自己的說明。原生嚴格模式也會限制可用的 JSON Schema 功能,因此某些較少見的 keyword,可能在最嚴格的端點被拒絕,卻能在其他端點以提示形式通過。
Claude 還有一個記載在供應商路由頁面的特殊情況:當使用 response_format.type: "json_schema" 時,OpenRouter 會自動加上 Anthropic 的 structured-outputs-2025-11-13 beta header,藉此啟用嚴格、會驗證 schema 的工具引數。不過,若是透過 tools 傳送帶有 strict: true 的工具定義,呼叫端必須自行明確傳送該 beta header。否則 OpenRouter 會移除 strict,並在沒有它的情況下路由請求。這是一種靜默失敗:工具呼叫不再經過 schema 驗證,但也不會報錯。
同一份 schema 最常見的六種失敗方式
其中兩種失敗會立刻報錯,官方指南已有記載;另外四種則出現在社群討論裡,也是最容易耗掉你整個下午的問題。
立即失敗 1:端點不支援 structured outputs。 請求會直接回傳能力不受支援的錯誤。雖然麻煩,但至少原因明確。立即失敗 2:JSON Schema 無效。 API 會拒絕請求,因為 schema 本身無法解析,或違反該端點的 schema 規則。
靜默失敗 1:schema 被直接忽略。 回應是有效 JSON,但完全符合另一種 schema。在 r/LocalLLaMA 的schema-not-followed 討論串中,u/DaniyarQQQ 表示:
它回傳的 json 完全不像我的 schema。
同一串裡,u/MicBeckie 描述了診斷上的困境:
我不是看到完全符合需求的 json,就是收到錯誤,卻無法查看 json。
包裝器失敗 2:你沒傳 tool_choice,卻收到相關的 400 錯誤。 在連結的 LangChainJS 案例中,withStructuredOutput() 是透過強制將 tool_choice 指向自動產生的 function,來實作「structured output」。對於宣稱支援 tool call、卻不支援強制 tool choice 的模型,請求會以 invalid_request_error 失敗;以 DeepSeek v4 為例,錯誤甚至直接點名模型:deepseek-reasoner does not support this tool_choice。u/shansoft 透過 LangChainJS 遇到的正是這個問題(討論串)。u/eyueldk 原本以為「既然它說支援 tool calls,就應該支援 structured output」,但事實並非如此。Tool-call 支援與嚴格 schema 支援是兩種獨立能力。
靜默失敗 3:沒有錯誤,也沒有內容。 一份關於 gpt-oss-120b 的回報指出,嚴格 schema 請求若直連供應商路由會得到 400,但透過 OpenRouter 卻回傳 200,以及空白的 message.content。r/openrouter 另一篇討論串則顯示,一個標示為「支援」的模型只回傳 [1] 或 [1.1]。如果 SDK 若無其事地解析空字串,問題就會被推遲到下游三層之後才爆開。
靜默失敗 4:端點卡住不回應。 u/Beneficial-Loss-1031 在討論標示支援 DeepSeek v4 structured output 的端點時表示(討論串):
deepinfra/fp4和akashml/fp8都有 structured output 選項,但我各等了 3 分鐘,API 都沒有回傳任何東西。
| # | 失敗形式 | 你會看到什麼 | 典型原因 |
|---|---|---|---|
| 1 | 端點不支援 | 錯誤:不支援 structured outputs | 路由到沒有該能力的供應商 |
| 2 | Schema 無效 | 請求時 API 報錯 | Schema 違反端點規則 |
| 3 | Schema 被忽略 | 有效 JSON,但欄位錯誤 | 提示層級的強制模式 |
| 4 | tool_choice 400 | invalid_request_error | SDK 透過強制 tool call 模擬 schema |
| 5 | 空白內容 | 200,message.content 為空 | 供應商錯誤處理 strict mode |
| 6 | 卡住 | 數分鐘沒有回應 | 回報尚未確認——fp4/fp8 端點等待了 3 分鐘 |
先把請求做硬化,再來怪模型
最有影響力的設定,是在 provider 物件加入 require_parameters: true。它預設為 false,未知參數會被傳給那些可能靜默忽略它們的供應商。即使維持 false,response_format 與 structured outputs 對端點來說也只是軟性偏好:會優先考慮,但不保證。根據供應商路由文件,設為 true 後,路由只會選擇支援你所傳送全部參數的端點:
{
"model": "deepseek/deepseek-chat",
"messages": [{ "role": "user", "content": "Extract the shipping info" }],
"response_format": { "type": "json_schema", "json_schema": { "name": "shipping_info", "strict": true, "schema": { "...": "..." } } },
"provider": {
"require_parameters": true,
"order": ["fireworks"],
"allow_fallbacks": false
}
}
每增加一項限制,可用供應商池就會縮小;allow_fallbacks: false 則是用可用性換取決定性。相同的路由文件指出,預設策略會根據前 30 秒內的正常運作時間,以及價格倒數平方進行負載平衡;它優化的是便宜且健康的端點,而不是最能遵守 schema 的端點。將 order 固定到單一供應商、並停用 fallback,可讓路由結果可重現:服務中斷時,請求不會飄到另一家供應商。至於該端點本身屬於哪種強制等級,仍需要你自行驗證。
還有兩個稽核習慣,能補足路由無法處理的問題:
- 確認實際服務請求的供應商。 OpenRouter 的generation metadata 會提供每次 generation 的供應商路由資訊,以及模型、延遲與 token 數量。當輸出品質飄移時,這份歸因資料能幫你判斷是模型行為變了,還是 router 換了供應商。
- 無論如何都要在客戶端驗證。 三個層級沒有任何一種能取代你端的 Pydantic 或 Zod parse。r/LLMDevs 測試討論串一再重申: 「有效 JSON」、「符合 schema」與「語意正確」是三個不同門檻,而 API 甚至只對前兩者負有部分責任。
可以串流,但解析責任仍在你的應用程式
Structured outputs 可搭配 stream: true 使用。文件定義的行為是:模型串流傳送有效的部分 JSON,待串流完成後,組裝出的完整回應應符合 schema。不過,這個保證仍承襲端點的強制等級;提示層級端點依然可能組裝出不符合規格的結果,因此最終物件仍要自行驗證。官方文件也沒有提供增量 parser;對重視延遲的 UI 而言,這才是真正的工程難題。r/LLMDevs 的串流最佳實務討論串中,有人這樣說:
我最後只好自己寫一個函式來補完 JSON。—— u/am174744
「……這其實是一個真正的狀態機。」—— u/ImNotLegitLol,用來修正那種「修復後再解析」的思考方式
實務上有幾種選擇:採用可容忍不完整 JSON 的串流 parser、只渲染已完成的欄位,或乾脆放棄增量渲染,等待完整物件組裝完成前只顯示 spinner。
Response Healing 能修什麼,又修不了什麼?
OpenRouter 的 Response Healing plugin 針對非串流的 json_schema 請求,修復格式不完美的回應,例如截斷的 JSON、殘留的 Markdown code fence 等問題。不過,比起它能修什麼,更重要的是以下兩個限制:
- 不處理串流。 文件明確指出,這個 plugin 僅適用於非串流請求。
- 不修復 schema 違規。 Healing 能讓 JSON 可被解析,但無法讓一個忽略 schema 的回應突然符合 schema。前面提到的第 3 種失敗形式不在它的處理範圍內。
如何挑選真的會遵守 schema 的模型
模型清單會過時,但篩選標準不會。以下三個條件可以攔下大多數前述問題:
- 原生嚴格強制。 優先選擇服務供應商在解碼階段強制 schema 的模型,而非轉譯或提示型供應商。模型頁面的 Providers 表格會顯示哪些端點標示
structured_outputs;真正決定強制品質的是供應商層級。 - 單一、可稽核的供應商。 用多次請求,將供應商歸因資料與一個已知正常的端點交叉比對。若 router 將請求分配到不同強制等級的供應商,你的失敗率就是一場路由彩票。固定供應商,或選擇只有單一供應商的模型。
- 自己實跑過的冒煙測試,而不是看過的評價。 社群訊號雙向過時得很快:上面的 Qwen 錯誤回報與 DeepSeek v4 缺少支援的情況,都可能隨供應商更新端點而改變。唯一有意義的可靠性數字,是你自己的 schema 實測出來的結果。
OpenRouter structured outputs 常見問題
json_object 和 json_schema 有什麼差別?
json_object 只要求回應是語法正確的 JSON;json_schema 則提供一份 schema,要求回應符合該規格。json_object 保證的是 JSON 語法,不保證符合你的欄位層級 schema;如果下游程式需要具名欄位,仍應自行驗證。
哪些 OpenRouter 模型支援 structured outputs?
沒有一份靜態清單值得信任:支援狀態按端點而定、會隨時間變化,而且在 2024 年 12 月起初只有 OpenAI 4o 與 Fireworks 模型支援。請查看模型頁面的 Providers 區塊,確認每個端點是否有 structured_outputs 旗標。
為什麼模型會忽略我的 schema?
常見有三個原因:請求被路由到提示層級或不支援的端點,可透過 require_parameters: true 與固定供應商改善;schema 使用了該端點嚴格模式不接受的 keyword;或 SDK wrapper 嘗試透過 tool calling 模擬 structured output,但模型不支援強制 tool choice。
可以把 Pydantic 或 LangChain 和 OpenRouter structured outputs 一起用嗎?
可以。官方文件將請求格式描述為相容於 OpenRouter 的 chat-completions-style API,因此 Pydantic 產生的 schema 與 OpenAI SDK 都能直接使用。LangChain 的 withStructuredOutput() 也可以運作,但要確認它傳送的是 response_format,而不是透過 tool_choice 模擬;後者正是 DeepSeek v4 出現 400 錯誤的原因。
structured output 支援 streaming 嗎?
支援。串流會輸出有效的部分 JSON,但最終是否符合 schema 取決於端點的強制等級,因此完整組裝後仍應自行驗證。片段的增量解析是應用程式自己的工作,而 Response Healing 不適用於串流。
OpenRouter 會依照我的 schema 驗證回應嗎?
無法保證所有端點都會這樣做:強制程度取決於供應商層級,而 Response Healing 只會修復格式錯誤的 JSON,不會修復 schema 違規。因此,客戶端驗證仍是必要措施。
10 次呼叫冒煙測試
任何模型要透過 structured outputs 進入正式環境前,先跑完以下流程:
- 固定一份具代表性的 schema:中等複雜度、設定
additionalProperties: false,並為所有 property 加上 description。 - 以
strict: true和require_parameters: true發送 10 次完全相同的請求,且保持 fallback 啟用——這一輪就是刻意測試 fallback 行為,所以不要關掉它們。 - 用三個門檻評分每次回應:JSON 能否解析?是否符合 schema?語意是否合理?
- 透過 generation metadata 記錄每次回應由哪個供應商服務。由四家不同供應商提供的 10/10 通過率,是路由彩票,不是保證。
- 做出決策:直接上線;將
provider.order固定到通過的端點後,再跑一次固定端點的 10 次測試;或更換模型,並加入客戶端驗證與重試層。
通過門檻由你自己決定,但固定 schema 的結果若低於 9/10,重試與驗證程式碼就不是可選項,而是產品本身的一部分。
延伸閱讀:OpenRouter auto router 如何選擇供應商、透過 OpenRouter prompt caching 降低成本,以及解決 OpenRouter 429 rate limit 問題。