[控场AI]
· 8 分钟阅读· 4,153 字

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

别让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 失败的实战经历。

rss source

json.loads 的陷阱

代码看起来人畜无害:

data = json.loads(llm_response)
title = data["title"]

简洁,脆弱。模型返回的不是 JSON,而是文本。你让它填表格,它回你一句“嗯,您看是这样的……”——也许有用,也许完全跑偏。

生产环境中实际遇到的五种情况:

  1. Markdown 围栏:模型用三反引号把 JSON 包起来,json.loads 直接崩溃。至少这种失败是响亮的。
  2. 礼貌用语:Sure! Here is the JSON you asked for: {...},再次崩溃。模型很热心,管道不领情。
  3. 尾随逗号、智能引号:{"title": "Hello",},解析器不买账。
  4. 截断:{"title": "Hello", "score":,JSON 不完整。
  5. 最糟的——静默垃圾:模型返回 {"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 个特例。

发布前的快速检查

问三个问题:

  1. 如果模型返回 Markdown,会发生什么?
  2. 如果某个字段类型错误,会发生什么?
  3. 如果 API 在运行中挂掉,会发生什么?

如果答案是“崩溃”或“发出垃圾”——那就还没完成。

我们不做什么

  • 不把密钥发给随机的“JSON 修复”服务
  • 不把 json.loads(strip(text)) 当作足够
  • 不隐藏“几乎通过”的结果

核心要点

  1. LLM 不是 JSON API。 它是文本 API。JSON 是你的活。
  2. 静默失败比崩溃更伤人。 Schema 能抓住 try/except 漏掉的问题。
  3. 用确切错误修复胜过从头重新生成。
  4. 熔断器保护你免于自己的固执。
  5. 一个契约服务所有调用胜过 49 个雪花特例。

作者把这些发布为小型开源库,避免其他人重新造轮子:

如果你在生产中运行 LLM,检查你的 json.loads。如果它没有 schema 和修复机制——你可能已经在为此付出代价,只是还没看到账单。

#LLM JSON 解析#json.loads 陷阱#LLM 管道静默失败#schema 验证#修复循环#熔断器#structured-llm-output-guard
分享:

相关推荐