你 prompt 里写 “respond in JSON matching this schema: {name: string, age: number, tags: string[]}”。95% 的调用返回合法 JSON。3% 返回 “Sure, here’s the JSON you requested: json\n{...}\n”。1% 漏 tags 字段。1% 返回 age: "thirty"——因为 schema 没强制类型。production 里这意味着你的 JSON parser 5% 失败、下游代码崩、到处加 try-catch。模型没违抗——它跟的是 schema 的英文描述,不是一份被强制的契约。
现代模型 API 都支持真正的 schema 强制(OpenAI structured outputs、Anthropic tool use、Gemini response schema)。还把 schema 写在 prompt 里然后祈祷的话,你在白白浪费免费的可靠性。
常见原因
1. schema 是英文描述、不是声明
Return JSON: {name, age, tags}.
模型把这当软引导。有时加 wrapper 文字、有时漏字段。没有 API 层强制就是不可靠。
怎么判断:prompt 里 schema 是自然语言描述、API 调用没传 response_format= 或 tools=。
2. markdown code-block 包裹
模型返回:
Here is the JSON:
```json
\{"name": "Alice"\}
```
parser 读整个字符串,JSON.parse() 失败。即使设了 response_format={"type":"json_object"},跟其他 instruction 冲突时模型仍可能加 prose。
怎么判断:输出里有反引号、或前缀 “Here is” / “Sure” / “Of course”。
3. schema 指定了字段没指定类型
prompt 写 {age: number}、模型返 "age": "30"。描述本身允许歧义,模型以为 “30” 是个 number-shaped 字符串。
怎么判断:用严格 JSON Schema validator 校验。类型错就是 schema 没强制。
4. 可选字段被当必填
schema 是 {name, email, phone}。用户输入只有 name。模型返 {"name": "Alice", "email": null, "phone": null} 或干脆省略。下游期望字符串拿到 null 就崩。
怎么判断:访问 null 字段崩、或漏字段时 KeyError。
5. 嵌套对象被拍平或展开
schema 是 {user: {name, age}}。模型有时直接返 {name, age}、有时展开成 {user_name, user_age}。嵌套丢了。
怎么判断:顶层 keys 跟你声明的不一致。
6. 对象数组塌成逗号分隔字符串
schema 是 tags: string[]。模型返 "tags": "blue, red, fast"。字符串、不是数组。输入里有逗号分隔值时常发。
怎么判断:按 schema 类型校验每个字段,发现是 string 不是 array。
7. enum 字段不被遵守
schema 是 sentiment: "positive" | "neutral" | "negative"。模型返 "sentiment": "very positive" 或 "sentiment": "neg"。enum 是 hint、不是约束。
怎么判断:sentiment 值不在允许集合里。
最短修复路径
第 1 步:用真正的 structured output、不用 prompt 描述 schema
OpenAI (Python):
from pydantic import BaseModel
class User(BaseModel):
name: str
age: int
tags: list[str]
resp = client.beta.chat.completions.parse(
model="gpt-5.5",
messages=[...],
response_format=User,
)
user = resp.choices[0].message.parsed # 已经是 User 实例
API 在 token-sampling 层强制 schema。非法 token 在结构上被禁。
第 2 步:Anthropic——用 tool definition 当 schema
tools = [{
"name": "extract_user",
"input_schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"age": {"type": "integer"},
"tags": {"type": "array", "items": {"type": "string"}}
},
"required": ["name", "age", "tags"]
}
}]
msg = client.messages.create(
model="claude-opus-4-7",
tools=tools,
tool_choice={"type": "tool", "name": "extract_user"},
messages=[...],
)
data = msg.content[0].input # 已按 schema 校验
强制 tool use 让模型输出严格匹配 schema 的 JSON。
第 3 步:Gemini——传 response_schema
import google.generativeai as genai
resp = model.generate_content(
prompt,
generation_config={
"response_mime_type": "application/json",
"response_schema": user_schema,
},
)
第 4 步:卡在不支持 structured output 的模型上,就校验加重试
def get_json(prompt, max_retries=3):
for i in range(max_retries):
out = call_llm(prompt)
try:
data = json.loads(out)
User.model_validate(data)
return data
except (json.JSONDecodeError, ValidationError) as e:
prompt += f"\n\nPrevious response failed validation: {e}. Return valid JSON only."
raise RuntimeError("Failed after retries")
把校验错误回灌给模型——第 2 次通常就自己改对了。
第 5 步:把 json_object 模式当 fallback、不当保证
response_format={"type": "json_object"}
它防 prose 包裹,但不强制 schema。仍要对 parsed 结果做校验。
第 6 步:模型死活要包裹时,先 pre-extract
import re
def extract_json(text):
# 找第一个 { ... } 或 [ ... ] 块
match = re.search(r'(\{.*\}|\[.*\])', text, re.DOTALL)
if match: return match.group(1)
raise ValueError("No JSON found")
针对死活要加 “Here is your JSON:” 的模型的便宜防御。
第 7 步:log schema 违规、按字段调优
metrics.increment("schema_violation", tags={"field": field_name, "type": "missing"})
某个字段 5% 失败率,那个字段的 schema 描述需要改——澄清或加示例值。
哪些情况可能不是你操作错了
有些小模型再怎么 prompt 都跟不上 JSON schema。一定要用小模型的话,生成 JSON-like 后下游修复——接受一定丢弃率。
容易误判的情况
“prompt 写得不好”。更长的 schema 描述只能边际改善。真正修复是 API 层强制。答案是 “切到 structured outputs” 时就别再调 prompt 了。
预防建议
- 默认用 structured-output API(OpenAI parse、Anthropic tools、Gemini response_schema)。
- 把 schema 写成代码(Pydantic、Zod)——client 和 validator 共享一个真源。
- 即使用了 structured outputs 也要再跑 schema 校验当 defense-in-depth。
- log 校验失败、把错误反馈给模型重试。
- 不支持 structured-output 的模型加一层
extract_jsonregex 做防御。
FAQ
- structured output 贵吗? 边际延迟、不加 token。几乎免费的可靠性。
- 嵌套 schema 深度 5 层呢? structured outputs 支持嵌套到 provider 上限(通常 5-10 层)。更深就拍平。
相关阅读
- 没指定输出格式
- 模型自己补足缺失细节
- 回答被截断在半句话,因为 max_tokens 设太低
- 互相冲突的指令削弱输出
- Style 和 format 互相打架
- 输出很漂亮但没法执行
- 没给例子导致输出漂移
- AI 编造事实
- Prompt 要 10 条,模型给 3 条就停
- 最后一句覆盖前面的指令
标签: #Prompt 工程 #排查 #llm-output #json #structured-output #schema-validation