结论先说:想让智能体稳定吐出能被程序直接消费的数据,靠的不是”请返回 JSON”这句提示,而是三道关——先用 schema 做硬性约束,再在服务端做校验与拒答识别,最后才是带错误回传的有限重试。三道关缺任何一道,线上都会出现偶发的解析失败。
第一关:把约束交给解码层,别交给提示词
提示词里写”只输出 JSON”,模型仍有小概率输出多余说明或漏字段。可靠的做法是在请求里开启严格模式:开启后,模型输出会被约束到给定的 JSON Schema 上。相应地,schema 必须满足几条硬性要求:
- 每个对象都要显式声明
"additionalProperties": false; properties里的字段必须全部列入required;- 可选字段用联合类型表示,例如
"type": ["string", "null"],而不是直接省略。
{
"type": "object",
"properties": {
"city": { "type": "string" },
"unit": { "type": ["string", "null"], "enum": ["celsius", "fahrenheit"] }
},
"required": ["city", "unit"],
"additionalProperties": false
}
schema 不满足约束时请求会被直接拒绝并给出原因,这反而是好事——问题暴露在编码阶段,而不是凌晨的线上日志里。
推荐做法:始终开启严格模式
官方的建议是只要能用就开启。它的实现方式是约束解码:在生成过程中就屏蔽掉不符合 schema 的候选,因此与”提示词里反复强调格式”有本质区别。可用但无法兼容时,接口会退回尽力而为的模式,并在响应里标明并未生效——生产环境应把这一项当作监控指标,发现降级立即报警。
第二关:服务端校验与拒答识别
约束解决的是”格式一定对”,解决不了”语义对不对”和”模型拒绝回答”。服务端要检查三件事:
- 结构校验:类型、必填、枚举值、数值区间,用成熟的校验库跑一遍,不要手写 if 。
- 拒答识别:模型出于安全策略可能返回拒绝而不是数据,响应里有专门的拒答字段可以被程序读到,必须显式判断,否则会被当成空数据处理。
- 截断识别:输出被长度限制截断时,JSON 一定不完整。检查结束原因,截断的请求要么提高上限重发,要么拆分任务,不要试图修补半截 JSON 。
minimum/maximum,字符串长度用 minLength,可选集合用 enum。能被 schema 表达的规则,就不要指望模型自觉遵守。第三关:重试要有信息量,还要有上限
无脑重试是最常见的反模式。正确的重试是把校验失败的具体原因回传给模型:哪个字段不合法、期望什么、实际给了什么,让它带着错误信息重来一次。这样第二次的成功率远高于原样重发。
上限建议设为两次,第三次就该走降级:返回结构化错误、转人工,或改用确定性解析兜底。无限重试只会把延迟和成本放大,最终仍可能失败。重试与降级的整体分层见智能体工具调用失败的四层设计。
四个高发坑
- 嵌套过深:三层以上的嵌套对象会明显降低遵循率。把深层结构拆成多个平级字段,或拆成两次调用。
- 滥用枚举:几十个取值的枚举既占上下文又容易选错,超过十几个就考虑改成自由字符串加服务端映射。
- 流式输出直接解析:流式传输过程中数据是不完整的,必须等结束事件再解析,或者用 SDK 提供的增量聚合能力。
- 把推理过程和结果混在一个字段:给思考留一个独立字段,最终答案字段保持纯净,下游解析会简单很多。
和输出侧其他防线的关系
结构化约束只是输出侧的第一道防线,完整的输出治理还包括内容审核与权限校验,整体框架见智能体护栏实现;工具参数本身该怎么设计、描述写到什么程度,见智能体工具设计实践。本文的严格模式要求与字段写法依据 OpenAI 官方结构化输出与函数调用文档。
开启严格模式后,schema 报不兼容怎么办?
additionalProperties: false、把所有字段放进 required、可选字段改成联合类型加 null 。这三条覆盖了绝大多数不兼容场景,改完即可开启。









评论 (0)