GLM 结构化输出怎么保证格式不跑偏

2026-08-25

数据截至 2026-08,价格与限额以各官网为准。本文只讲计费与接入机制,不列具体价格数字。

结论先说:GLM 的结构化输出不是一个开关,而是四段配合。response_format 只负责把输出切到 JSON 模式,真正决定「格式跑不跑偏」的是——你有没有把期望结构写进 system 消息、有没有在客户端做 Schema 校验、有没有在解析前先看 finish_reason。官方文档里那个反复出现的 try / except 结构不是凑字数,它同时兜住了两类完全不同的失败:json.JSONDecodeError(模型输出压根不是合法 JSON,或者被截断了)和 jsonschema.exceptions.ValidationError(是合法 JSON,但字段对不上)。把这两类分开处理,你才知道该重试还是该改提示词。

先把 response_format 这个字段本身看清楚

在对话补全接口里,response_format 是一个对象,不是字符串。它内部只有一个必填属性 type。官方接口文档给出的说明是:指定模型的响应输出格式,默认为 text仅文本模型支持此字段

这里有个值得留意的细节:官方描述文字写的是「type 取值收敛为三种」,但紧跟着的 enum 只列了 text(普通文本输出)和 json_object(JSON 格式输出)两个值,default 标的是 text。描述与枚举在措辞上并不一致,同步接口和异步接口两处都是这么写的。对接的时候以 enum 实际列出的取值为准,并以官方文档当前版本为准——不要因为「说是三种」就去猜第三种是什么,更不要凭着别家平台的印象往里填一个 Schema 类型的取值。

另一个容易混的点:response_format 这个名字在 GLM 平台不止一处出现。语音合成接口里也有一个 response_format,但那是一个字符串,枚举取值是音频格式,跟对话补全里的 JSON 模式完全不是一回事。跨接口复制参数模板的时候,这是个很容易踩空的地方。

response_format={
    "type": "json_object"
}

只开 JSON 模式还不够,结构得自己写进消息里

这是最多人误解的一层。开了 json_object,你拿到的是一段 JSON,但具体有哪些字段、字段是什么类型,模型并不知道。官方文档在核心参数说明里把这件事写得很直白:messages 的作用是「在系统消息中定义期望的 JSON 结构和字段要求」。

也就是说,结构声明的位置是 system 消息,不是 response_format。官方的完整示例里,system 消息的内容就是一整段期望的 JSON 样例,把字段名、取值范围(比如情感字段写成 positive/negative/neutral 这种斜杠并列的形式)、数组字段的示例元素都摆出来。模型照着这个样子生成。

进阶一点的写法官方也给了:先在 Python 里把 JSON Schema 定义成一个 dict,然后用 json.dumps(schema, indent=2, ensure_ascii=False) 序列化后拼进 system 提示里。这个写法的好处是同一份 Schema 既当提示词、又当校验依据,两边不会走样。ensure_ascii=False 这个参数别漏,中文字段描述如果被转义成 Unicode 码点塞进提示词,可读性会掉一大截。

官方在实践建议里给 Schema 设计提了三条:字段名称和类型要清晰明确、包含所有必要的验证规则、考虑未来的扩展需求。另外还有一句提醒挺实在——建议从简单结构开始逐步增加复杂性,并为关键字段提供详细的描述和示例。

客户端校验才是真正的保底

官方示例里,拿到结果之后并没有直接信任它,而是走了这么一条链路:json.loads 解析字符串,再用 jsonschemavalidate(instance=result, schema=schema) 做结构校验,然后分别捕获 jsonschema.exceptions.ValidationErrorjson.JSONDecodeError

这两个异常一定要分开 except,因为它们对应的处置动作完全不同:

  • 抓到 JSONDecodeError:说明返回内容不是合法 JSON。可能是模型带了额外说明文字,也可能是输出被截断了(下一节专门讲这个)。
  • 抓到 ValidationError:JSON 本身没问题,是字段缺了、类型错了或者枚举值不在范围内。这时候重试同一份提示词大概率还是同样的结果,该改的是 system 里的结构描述。

Schema 里的 required 数组是这条链路的核心。官方示例只把真正非它不可的字段放进 required,其余字段留作可选,并在提示词里明确写「如果某个字段没有信息,不要包含该字段」。这个组合比「所有字段都必填、缺了就让模型编」要稳得多——强迫模型填一个它不知道的字段,等于主动制造脏数据。

官方还给了一条降级思路:准备简化的备用 Schema。主 Schema 校验不过时,退到一个字段更少的版本再试,而不是整条请求直接失败。配合详细的错误日志记录,线上真出问题时才有得查。

格式跑偏最常见的原因其实是被截断

很多人报「JSON 解析失败」,实际根因既不是模型不听话,也不是 Schema 写错了,而是输出到一半就停了——一个缺右花括号的字符串,json.loads 当然过不去。

判断依据在响应里现成摆着:finish_reason。官方文档列出的取值含义分别是——stop 表示自然结束或触发 stop 词,tool_calls 表示模型命中函数,length 表示达到 token 长度限制,sensitive 表示内容被安全审核接口拦截,network_error 表示模型推理异常,model_context_window_exceeded 表示超出模型上下文窗口。

对结构化输出来说,这条排查顺序应该固定下来:先读 finish_reason,再决定要不要 json.loads

  • lengthmax_tokens 给小了。官方对这个参数的说明是,它限制的是生成内容的长度、不包括输入;如果模型在达到上限前完成回答会自然结束,达到上限则输出可能被截断。嵌套层级深、数组元素多的 Schema 特别吃这一刀。具体该给多少、各模型支持的上限是多少,以官方参数文档当前版本为准。
  • model_context_window_exceeded:这是上下文窗口的问题,不是输出长度的问题。把 Schema 反复 json.dumps 进 system、又带着长历史对话,很容易同时把两头都撑满。
  • sensitive:内容被安全审核拦了,官方特别提示用户应判断并决定是否撤回已公开的内容。这种情况下重试没有意义。
  • network_error:推理异常,属于可重试类型,重试时记得走指数退避,别原地密集重发。

顺带说一句,把截断当成解析失败去反复重试,是一种很贵的错误——每次重试都要重新吃一遍输入 token。成本侧的监控口径可以看 API 成本监控怎么做

采样参数要不要动

官方参数文档里,do_sample 是布尔值,决定是否对输出进行采样;temperature 控制输出的随机性,值越高越随机;top_p 走核采样。官方明确写了两条建议:temperaturetop_p 只使用其中一个,不建议同时修改;在需要严谨、事实准确的场景建议使用较低的 temperature

结构化输出显然属于「需要严谨」的那一类。想让字段稳定,就别在这里追求多样性。具体取值官方给的是区间建议而非硬性规定,以官方文档为准。

开了思考模式,还能拿到干净的 JSON 吗

能,因为思维链走的是另一个字段。官方接口文档里,message 对象上除了 content 还有 reasoning_content,描述写的是「思维链内容,仅 glm-4.5 系列支持」。思考模式本身通过 thinking 对象开启,官方示例里写成 {"type": "enabled"}

所以解析结构化结果时,取的始终是 response.choices[0].message.content。这一点在流式场景下更要注意:官方流式示例里是用 hasattr(delta, 'reasoning_content') 判断再累加的,思维链增量和正文增量分属两个属性,拼错了就会把推理过程混进 JSON 字符串。

另外官方接口里还有一个控制项,用于决定是否清除历史对话轮次中的 reasoning_content——开启时系统会忽略历史轮的思维链,只把可见文本、工具调用与结果等作为上下文输入,官方说明这适用于普通对话与轻量任务,可降低上下文长度与成本。这个开关的默认行为以官方文档当前版本为准。多轮结构化提取的场景里,它和上一节说的 model_context_window_exceeded 是直接相关的。

另一条路:用工具调用拿结构

如果你的目标本来就是「让模型填一组参数」,工具调用是比 JSON 模式更贴合的形态。官方工具调用文档里,tools 用来定义可调用的函数列表,「包含函数名、描述和参数规范」——参数规范本身就是一份 Schema,等于把结构声明从提示词挪到了参数定义里。

返回侧的关键字段是 tool_calls,其中 function.name 是被调用的函数名称,function.arguments函数调用参数的 JSON 格式字符串。注意它是字符串不是对象,官方示例里统一要 json.loads(tool_call.function.arguments) 之后才能用。这一步漏了,拿到的就是一段没解析的文本。

还有一个限制要记住:官方写明 tool_choice 控制函数调用策略,默认且仅支持 auto。也就是说你没法强制模型必须调某个函数,模型也可能选择直接回文本。要判断它到底走了哪条路,还是回到 finish_reason——命中函数时它是 tool_calls

具体怎么把工具定义写对、流式下怎么拼装分片,可以接着看 GLM API 接入配置GLM API 报错排查

最容易栽的两个坑

第一个:把 JSON 模式当成 Schema 模式response_format 的枚举里只有 textjson_object,没有一个「传 Schema 进去由服务端强制约束」的取值。字段约束这件事,官方给的路径是「写进 system 提示 + 客户端 jsonschema 校验」这一套组合拳,不是服务端替你做。指望不写提示词、只开个开关就拿到指定结构,方向从一开始就错了。

第二个:解析前不看 finish_reason。一个被 length 截断的响应和一个格式跑偏的响应,在 json.loads 眼里都是同一个异常,但处置动作一个是调 max_tokens、一个是改提示词。日志里如果只记了「JSON 解析失败」而没记 finish_reasonusage,这两类问题就永远分不开。

顺手把上下文缓存也考虑进来:结构化提取的 system 消息通常是固定的一大段 Schema,前缀高度重复,这正是缓存最容易发挥作用的形态。命中条件和写法上的讲究见 GLM 上下文缓存怎么才能命中。把 Schema 放在 system 消息最前面、把变动的输入放到末尾,是同时利好格式稳定性和成本的一个小习惯。

官方文档在结构化输出这一页最后留了一句提醒也值得抄下来:JSON 模式要求模型严格按照指定格式输出,但在某些复杂场景下可能影响回答的自然性。如果你的产品是要把这段 JSON 直接渲染给用户看的,记得在功能性和阅读体验之间找个平衡——不是所有场景都值得强上 JSON。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。