![]()
2024年Q3,我的翻譯功能日活剛破2萬,系統就開始在凌晨三點報警。不是翻譯質量下滑,是整段JSON直接解析失敗——用戶看到的不是蹩腳英文,是空白。
罪魁禍首是三行反引號。
模型突然開始"貼心"地用Markdown代碼塊包裹JSON響應,JSON.parse()當場窒息。沒有預警,沒有版本說明,OpenRouter的某個上游模型在負載高峰時改變了行為模式。我花了72小時搭建的三層防御系統,全部源于這次生產事故。
第一層:JSON模式是底線,不是選項
最初的實現堪稱裸奔:系統提示詞寫"請返回JSON",response_format字段空著。這在原型階段跑了三個月沒出問題,直到那個凌晨。
修復方案看起來簡單——加上response_format: { type: 'json_object' }。但OpenRouter的文檔埋了個細節:結構化輸出并非全模型支持,得逐頁檢查兼容性列表。我用的Claude 3.5 Sonnet當時剛更新支持,而部分Gemini模型至今沒有。
這層防御解決的是"模型故意輸出合法JSON"的場景。但LLM的"故意"和人類的理解常有偏差,比如它可能認為在JSON外面套個代碼塊是"幫助用戶閱讀"。
第二層:OpenRouter的隱藏插件
JSON模式能約束格式,卻修不了語法錯誤。模型可能在超長輸出時截斷,可能把Unicode轉義寫成亂碼,可能在嵌套對象里漏掉閉合括號。
OpenRouter有個幾乎沒宣傳的response-healing插件,啟用方式是在請求體里加一行:
plugins: [{ id: 'response-healing' }]
它會在服務端嘗試自動修復常見JSON缺陷,比如補全截斷的字符串、修正轉義序列。但有兩個硬限制:僅支持非流式響應,且對max_tokens導致的截斷無能為力——如果模型被令牌上限硬生生切斷,healing插件也拼不回完整的JSON結構。
這層防御讓我扛過了兩次小規模故障,但第三次崩潰來自更隱蔽的角落:模型開始在中文字符里混用全角半角引號,healing插件識別為合法JSON,我的下游解析器卻爆了。
第三層:客戶端的"不信任"解析器
前兩層都是上游防護,第三層必須握在自己手里。我寫的防御性解析器只做一件事:拒絕任何"看起來不對"的數據,而不是試圖修復。
具體策略包括:預檢響應體是否以{或[開頭(過濾掉代碼塊包裝);用zod或類似庫做運行時schema驗證;對解析失敗返回明確的錯誤碼,觸發降級流程而非靜默崩潰。
這套邏輯多攔截了17%的"邊緣合法"響應——比如模型在JSON末尾加了換行和注釋,或者把數字鍵名不加引號地返回。這些在寬松解析器里能過,在我的生產環境里會被拒掉。
被忽略的周邊:重試與語言檢測
JSON處理占了80%的工時,但另外兩層也 worth 提。
重試策略我設了三檔:首次失敗立即重試(可能是瞬時網絡抖動);第二次失敗切換備用模型(OpenRouter的model字段支持按優先級數組傳入);第三次失敗返回原文并標記待人工審核。沒有無限重試,沒有指數退避到地老天荒。
語言檢測則是前置過濾。用戶生成的日文內容里混著英文配料名、片假名外來語、甚至顏文字,直接扔給翻譯模型會浪費令牌。我用fast-text做初篩,置信度低于0.85的段落跳過翻譯,原樣展示。
這套組合拳跑下來,翻譯功能的可用性從事故后的94.3%爬回99.6%。但數字背后有個更硬的認知:LLM API的"穩定性"是工程堆出來的,不是供應商承諾出來的。
OpenRouter的文檔里至今沒提response-healing的完整故障模式列表,JSON Mode的兼容性矩陣更新滯后于實際上線時間。生產環境的防御深度,取決于你愿意為"模型可能變卦"這個假設寫多少代碼。
上周我在日志里又發現一批異常:某個模型的響應開始隨機包含BOM頭(字節順序標記),JSON.parse()不報錯,但我的schema驗證會掛。第四層防火墻已經在寫了。
你的LLM生產環境里,最近一次"不是bug是特性"的模型行為變更是什么?
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.