JSON Schema 不是格式化:给大模型立“输出契约”的一次复盘

zhichao Lv3

我一直觉得“让模型按格式输出”是一件很小的事。

直到我们把一个看似简单的需求(把用户需求解析成结构化任务)接进前端流程:按钮点下去要立刻渲染卡片、再驱动后续工具调用。

那一周我们见识了人类语言的任性,也见识了模型的任性:

  • 同一个字段名,有时叫 title,有时叫 task_title
  • 需要数组,它给你一段自然语言列表
  • 少一个逗号,前端就白屏

最后我们发现:这不是“格式化”问题,这是“契约”问题。


1. 为什么今年这件事突然变重要

今年大家都在谈 Agent、工具、编排。只要你开始做编排,就一定会遇到一个现实:

你真正依赖的不是模型的文字,而是模型输出里的结构。

结构才是系统之间的通用语言。

前端要渲染卡片、后端要触发任务、数据库要落字段、风控要审计——这些都不想读一段散文。


2. 我们踩的坑:把“解析”交给了运气

最开始我们走的是常见路线:

  • Prompt 里强调“请输出 JSON”
  • 后端用 json.loads() 解析
  • 失败就让模型重试一次

它在 Demo 阶段非常顺。

但一旦接入真实用户,问题就像潮水:

  • 用户会粘贴半段表格
  • 用户会夹带一段代码
  • 用户会用“顺便”提出第二个需求

模型当然也会“顺便”输出多一些解释。

那一刻我才意识到:

不要把生产系统建立在“模型今天心情好”的概率上。


3. 解决方案:把输出当作 API 来设计

我们后来把模型输出当作一个 API Response 来设计:

  • 有 schema(字段、类型、枚举、默认值)
  • 有错误语义(缺字段算什么错)
  • 有版本(schema v1/v2)
  • 有兼容策略(新增字段不破坏旧客户端)

下面是一个简化的 Schema(示例):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"type": "object",
"required": ["intent", "tasks"],
"properties": {
"intent": {"type": "string", "enum": ["plan", "summarize", "extract"]},
"tasks": {
"type": "array",
"items": {
"type": "object",
"required": ["title", "priority"],
"properties": {
"title": {"type": "string", "minLength": 1},
"priority": {"type": "string", "enum": ["P0", "P1", "P2"]},
"due": {"type": ["string", "null"]}
}
}
}
}
}

在 Prompt 里我们不再说“输出 JSON”,而是说:

  • 你在实现一个响应体
  • 必须完全符合 schema
  • 不允许额外文字
  • 如果信息不足,填 null 并把 need_clarification=true

4. 后端实现:验证永远比相信更便宜

我们在服务端做了三件事:

  1. 严格校验:不合 schema 就判失败
  2. 自动修复:仅做“安全的修复”(比如去掉 Markdown 代码块包裹)
  3. 可观测:记录失败原因,回灌到评测集

伪代码(Python):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import json
from jsonschema import validate, ValidationError

SCHEMA = {...}

def parse_or_retry(text: str) -> dict:
cleaned = text.strip()
if cleaned.startswith("```"):
cleaned = cleaned.strip("`")
try:
obj = json.loads(cleaned)
validate(instance=obj, schema=SCHEMA)
return obj
except (json.JSONDecodeError, ValidationError) as e:
raise ValueError(f"invalid_structured_output: {e}")

为什么不做“自动修一切”?

因为修复本质上是在帮模型做隐式决策,越修越会引入不可控分支。

我们宁愿失败得更明确一点,然后把失败样本加入回归。


5. 前端侧的经验:不要让 UI 等模型“写完作文”

当你把结构化输出做稳后,前端体验会明显变好:

  • 先渲染骨架(intent、tasks 数量)
  • 再填充细节
  • 如果 need_clarification=true,直接渲染澄清卡片

用户会感觉它在“做事”,而不是“思考”。


6. 什么时候不该用 Schema

我不是 Schema 传教士。下面这些场景我会放弃强结构:

  • 纯创作(文案、故事、风格化写作)
  • 极强开放的问题(讨论、脑暴)
  • 输出只给人看、不进系统

只要输出要进入系统,就尽量立契约。


结语

今年 AI 应用最大的变化之一,是我们越来越少把模型当作“聊天对象”,越来越多把它当作“系统组件”。

组件之间靠契约协作。

当你开始用 Schema 思维设计输出,你会发现很多问题突然变简单:

  • 失败可解释
  • 兼容可演进
  • 体验可优化

它不浪漫,但很工程。

  • Title: JSON Schema 不是格式化:给大模型立“输出契约”的一次复盘
  • Author: zhichao
  • Created at : 2025-04-19 20:40:00
  • Updated at : 2025-12-22 17:15:29
  • Link: https://chozzc.me/2025/04/19/2025-04-tech-structured-output-contract/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments