别让json.loads毁掉你的LLM管道:静默失败的排查与修复

LLM 返回的是文本而非 JSON,结构验证与修复循环才是生产管道的必备防线。
文章通过一位工程师的实战经验指出,生产环境中 LLM 的输出不是 JSON,而是“看起来像 JSON 的文本”。常见的五种失败中,Markdown 围栏、礼貌用语、尾随逗号、截断会导致响亮的解析崩溃,而结构错误的静默垃圾最危险——`json.loads` 能通过,但下游拿到的是错误字段。作者的方案是建立一条完整的质检链:剥离围栏与规范化、解析、schema 验证、用确切错误重新提示的修复循环,以及防止捶死 API 的熔断器。生产数据包括 49 个注入点、60+ schema 契约和 12 个“脏”输出测试,并强调 `finish_reason=length` 必须计为失败。核心结论是:LLM 是机器中的一个零件,需要像工厂质检一样检查、修复或报废,而不是祈祷它听话。
在生产环境中,LLM 不是聊天玩具,而是机器中的一个零件。可它吐出的不是 JSON,而是“看起来像 JSON 的文本”。一个工程师分享了他如何修复管道中静默 JSON 失败的实战经历。

json.loads 的陷阱
代码看起来人畜无害:
data = json.loads(llm_response)
title = data["title"]
简洁,脆弱。模型返回的不是 JSON,而是文本。你让它填表格,它回你一句“嗯,您看是这样的……”——也许有用,也许完全跑偏。
生产环境中实际遇到的五种情况:
- Markdown 围栏:模型用三反引号把 JSON 包起来,
json.loads直接崩溃。至少这种失败是响亮的。 - 礼貌用语:
Sure! Here is the JSON you asked for: {...},再次崩溃。模型很热心,管道不领情。 - 尾随逗号、智能引号:
{"title": "Hello",},解析器不买账。 - 截断:
{"title": "Hello", "score":,JSON 不完整。 - 最糟的——静默垃圾:模型返回
{"ok": true}而不是{"title": "...", "score": 42}。json.loads欣然接受,JSON 合法,但结构错误。垃圾数据流入下游,一小时后才发现——或者永远发现不了。
两种失败模式,而非一种
| 类型 | 表现 | 何时发现 |
|---|---|---|
| 响亮失败 | 抛异常,任务终止 | 立即 |
| 静默失败 | 字段错误 / 类型错误 | 太晚 |
try/except 能处理第一种。第二种才是真正伤人的。
试过但没用的方法
- “告诉模型永远返回干净的 JSON。” 有时有用。但模型不是编译器。
- 用正则匹配
{和}之间的内容。 遇到嵌套对象和字符串内的花括号就崩。 - 堆一堆 if 语句。 每种新响应结构 = 更多 if。滚雪球。
作者停止乞求模型,转而检查它。
Schema 验证(schema validation) 指的是按照预先定义的结构契约检查数据:哪些字段必须存在、每个字段是什么类型、数值是否落在允许区间内。它和“能否被解析”是两件独立的事——json.loads 只保证文本是合法 JSON,不保证内容符合你的预期。常见的实现方式包括 JSON Schema、Pydantic、Zod、dataclass 校验等。在 LLM 管道中,schema 是拦截“静默垃圾”的关键闸门:{"ok": true} 语法完全合法,但缺少 title 和 score,只有 schema 才能识破。设计 schema 时建议显式声明必填字段、类型和边界,并在验证失败时返回具体到字段的错误信息,而非笼统的“校验失败”。
修复方案
LLM 文本
|
+-- 剥离 Markdown 围栏和“Sure! Here's...”
+-- 规范化引号 / 尾随逗号
+-- 解析
+-- 验证 SCHEMA(类型、字段、边界)
|
+-- 若失败 -> 修复循环
| 用确切的错误重新提示
| (“score: 期望 int,得到 str”)
|
+-- 若 API 濒死 -> 熔断器
停止捶打死尸
这和工厂车间的质检思路一致:制造 -> 检查 -> 修复或报废。不抱侥幸。 作者并非软件出身,在真实生产环境中待过多年。零件按图纸出去,接受检验,废品不流入下一道工序。LLM 也是机器,只是它撒谎时更自信。
熔断器(circuit breaker) 源自分布式系统的容错模式,最早由 Michael Nygard 在《Release It!》中系统化描述。它监控对某个下游依赖(这里是 LLM API)的连续失败次数,达到阈值后主动“断开”调用,直接快速失败或走降级路径,而不是继续把请求打过去。经过一段冷却期后进入“半开”状态,放少量请求试探,成功则恢复,失败则继续断开。在 LLM 修复循环里,熔断器的意义在于:修复重试本身会放大请求量,如果模型或 API 已经处于故障状态,无限重试只会加重雪崩。典型参数是:连续 5 次失败触发断开,冷却 30-60 秒,半开阶段限流探测。
代码(没有魔法)
from llm_output_guard import Schema, Field, RepairLoop
schema = Schema(
fields={
"title": Field(str),
"score": Field(int, min_value=0, max_value=100),
}
)
def call_model(err=None):
# err = 上次尝试的确切验证错误
return generate(title=..., hint=err)
result = RepairLoop(schema=schema, max_attempts=3).run(call_model)
修复不是“再掷一次骰子”,而是把错误喂回给模型。在实际运行中,第二次尝试能修正大多数类型错误。另外还需要熔断器:5 次失败 -> 冷却 -> 重置。否则聪明的修复循环会变成锤子,把濒死的 API 彻底敲死。
生产系统的数据
- 一条生产管道中有 49 个 Instructor 注入点,全部通过同一条 JSON 路径
- 60+ schema 契约
- 12 个行为测试,专门针对“脏”的 LLM 输出
finish_reason=length计为失败,而非成功
当你有几十个调用时,“总能解析出来”不是策略。
finish_reason 是多数 LLM API(如 OpenAI、Anthropic 等)在响应中返回的字段,用来说明生成为什么停止。常见取值包括 stop(正常结束)、length(达到 max_tokens 上限被截断)、content_filter(被安全策略中断)、tool_calls(触发了工具调用)。其中 length 最关键:它意味着返回的文本可能在句子或 JSON 中途被砍断,看似有内容实则是残缺的。把 length 当作成功是许多管道事故的根源——下游会拿到不完整的 JSON,或在重试逻辑中反复消耗额度。工程上的做法是把 length 显式标记为失败,触发重试、缩短输入、或提高 max_tokens 后重新请求,而不是把它当作正常完成继续往下走。
拿走这份检查清单
如果只从这篇文章带走一件事——带走这个:
- 永远不要裸调
json.loads(llm_text)。先剥离围栏。 - 解析后总是验证结构(类型 + 必填字段)。
- 把
finish_reason=length当作失败,不是成功。 - schema 错误时:用确切错误重新提示。不要掷骰子。
- 在 API 周围放熔断器。5 次失败 -> 冷却。
- 记录验证失败的原因。未来的你会需要。
- 用丑陋输出测试:围栏、散文、尾随逗号、错误类型。
- 所有调用点共享一个契约。不要 49 个特例。
发布前的快速检查
问三个问题:
- 如果模型返回 Markdown,会发生什么?
- 如果某个字段类型错误,会发生什么?
- 如果 API 在运行中挂掉,会发生什么?
如果答案是“崩溃”或“发出垃圾”——那就还没完成。
我们不做什么
- 不把密钥发给随机的“JSON 修复”服务
- 不把
json.loads(strip(text))当作足够 - 不隐藏“几乎通过”的结果
核心要点
- LLM 不是 JSON API。 它是文本 API。JSON 是你的活。
- 静默失败比崩溃更伤人。 Schema 能抓住 try/except 漏掉的问题。
- 用确切错误修复胜过从头重新生成。
- 熔断器保护你免于自己的固执。
- 一个契约服务所有调用胜过 49 个雪花特例。
作者把这些发布为小型开源库,避免其他人重新造轮子:
- structured-llm-output-guard —— 安全的 JSON 路径
- guards-and-laws —— 质量门 + 领域法则,带硬否决
- checkpointed-task-pipeline —— 长任务抗崩溃
如果你在生产中运行 LLM,检查你的 json.loads。如果它没有 schema 和修复机制——你可能已经在为此付出代价,只是还没看到账单。
相关推荐

iPhone Duo续航实测:一天一充跑不掉,大屏是耗电元凶
iPhone Duo续航实测:重度使用一天需至少充电一次,6.5小时亮屏约剩30%电量。大屏展开频率是耗电关键,MagSafe电池背夹可救场,长期衰减同样值得关注。

Kimi K3.1 Pro开源发布:2.8万亿参数逼近Opus
Moonshot发布开源MoE模型Kimi K3.1 Pro,2.8万亿总参数、1040亿激活参数,Terminal Bench 4.0仅落后Opus 5.5不到1分,单任务成本约三分之一,API价格减半,开源权重11月4日开放。

Costco科技产品价格竞猜:显示器、键鼠、Bose音响值不值?
YouTube博主在Costco竞猜科技产品价格:2K 180Hz显示器、罗技键鼠、Bose音响和CyberPower UPS实测估价,揭示仓储会员店的选品定价逻辑与品牌溢价真相。