上周三帮团队把一个客服 Agent 从 GLM-5 升级到 GLM-5.2(z-ai/glm-5.2),升完之后函数调用死活返回null——明明 tools 数组传了、function 定义没变、prompt 也没动,就是不触发 tool_calls。折腾了大半天才定位到原因:GLM-5.2 对tool_choice字段的枚举值做了变更,老版本能跑的"auto"在某些接入路径下会被静默降级为"none",导致模型压根不尝试调用函数。这篇把坑的根因、修复方案、不同接入路径的配置差异全部讲清楚,踩过同样坑的直接翻到对应章节复制代码就行。
这篇适合谁
- 正在用 GLM-5.2 做 Function Calling / Tool Use,发现
tool_calls字段返回null或空数组 - 从 GLM-4.7 / GLM-5 升级到 GLM-5.2 后函数调用行为异常
- 用 Cline、Claude Code、Cherry Studio 等工具接入 GLM-5.2 想配置 tool_choice
- 对 OpenAI 兼容协议下各家模型 tool_choice 实现差异感兴趣
整体流程
- 理解 GLM-5.2 的
tool_choice枚举值与 OpenAI 规范的差异 - 根据你的接入方式(官方 SDK / OpenAI 兼容 / 聚合网关)修改请求参数
- 验证修复:确认
tool_calls正常返回 - 在 Cline / Claude Code / Cherry Studio 中配置正确的 tool_choice
- 建立防御性代码,避免后续升级再踩坑
先说结论
| 接入方式 | tool_choice 正确写法 | 常见错误写法 | 后果 |
|---|---|---|---|
| 智谱官方 SDK | "required"或{"type":"function","function":{"name":"xxx"}} | "auto" | 静默降级为不调用 |
| OpenAI 兼容协议(直连智谱) | "required" | "auto"(部分版本可用) | 返回 null |
| 聚合网关(ofox.io / OpenRouter) | "auto"或"required"均可 | — | 网关做了枚举映射 |
| Cline 配置 | 需在 settings 里指定toolChoice: "required" | 默认"auto" | 函数不触发 |
graph TD A[你的代码发送 tool_choice] --> B{接入路径} B -->|智谱官方 SDK| C[必须用 required] B -->|OpenAI 兼容直连| D[建议用 required] B -->|聚合网关 ofox/OpenRouter| E[auto 和 required 均可] C --> F[tool_calls 正常返回] D --> F E --> F B -->|传了 auto| G[GLM-5.2 静默降级为 none] G --> H[tool_calls: null 💀]第一步:理解根因——GLM-5.2 的枚举值变了
智谱在 GLM-5.2(2026 年 7 月更新)里调整了tool_choice的行为逻辑。OpenAI 规范里"auto"的含义是"模型自行决定是否调用工具",但 GLM-5.2 在官方 SDK 通道下把"auto"的行为改成了"仅在高置信度时才调用"——实际效果就是大部分场景下不触发。
我调试时抓到的实际返回:
{"choices":[{"message":{"role":"assistant","content":"好的,我来帮您查询。","tool_calls":null}}]}注意tool_calls直接是null,不是空数组[]。说明模型压根没进入函数调用的决策分支。
第二步:官方 SDK 修复
如果你用的是智谱官方 Python SDK(zhipuai),把tool_choice从"auto"改成"required":
response = client.chat.completions.create( model="glm-5.2", messages=messages, tools=tools, tool_choice="required" )"required"的语义是"模型必须调用至少一个工具"——在你明确知道当前轮次需要函数调用时这是正确的。
如果你需要"有时调用有时不调用"的行为,用指定函数名的写法:
tool_choice={ "type": "function", "function": {"name": "get_weather"} }这样模型会强制调用你指定的那个函数,不会返回 null。
第三步:OpenAI 兼容协议接入修复
很多人(包括我)是通过 OpenAI SDK 的base_url切到智谱的 OpenAI 兼容端点。这条路径下的坑更隐蔽——智谱的兼容层对"auto"的处理在 7 月 22 号前后有变化。
7 月 22 号之前:"auto"正常工作(等价于 OpenAI 的行为)
7 月 22 号之后:"auto"被映射到 GLM-5.2 新的"高置信度"逻辑
修复方式一样,改成"required":
from openai import OpenAI client = OpenAI( api_key="your-zhipu-key", base_url="https://open.bigmodel.cn/api/paas/v4" )resp = client.chat.completions.create( model="glm-5.2", messages=messages, tools=tools, tool_choice="required" )第四步:通过聚合网关接入(推荐,省心)
如果你用 ofox.io 或 OpenRouter 这类聚合 API 网关,好消息是它们在协议转换层做了枚举映射——你传"auto"过去,网关会根据目标模型自动转成正确的值。
from openai import OpenAI client = OpenAI( api_key="your-ofox-key", base_url="https://api.ofox.io/v1" )resp = client.chat.completions.create( model="z-ai/glm-5.2", messages=messages, tools=tools, tool_choice="auto" # 网关自动映射,不用改 )我后来把所有模型调用都走聚合网关了,省得每家模型的 tool_choice 枚举差异都要单独处理。ofox.io 是 0% 加价对齐官方价格,OpenRouter 收 5.5% 手续费。
第五步:在 Cline / Claude Code / Cherry Studio 中配置
Cline 配置
Cline 默认发送tool_choice: "auto",接 GLM-5.2 时需要在.cline/settings.json里覆盖:
{ "apiProvider": "openai-compatible", "toolChoice": "required" }如果你的 Cline 是通过 ofox.io 网关接入的,可以不改这个配置——网关会处理映射。base_url 填https://api.ofox.io/v1就行。
Claude Code 配置
Claude Code 本身主要调 Claude 系模型,但如果你通过--model参数指定 GLM-5.2,需要确保你的 API 端点支持正确的枚举映射。直连智谱端点时 Claude Code 的默认 tool_choice 行为会踩坑。
Cherry Studio 配置
Cherry Studio 的模型配置面板里有Tool Choice下拉框,直接选required即可。路径:设置 → 模型管理 → GLM-5.2 → 高级参数 → Tool Choice。
不同场景怎么选
| 你的场景 | 建议方案 | 原因 |
|---|---|---|
| 每轮都必须调工具(如 Agent 执行器) | tool_choice: "required" | 语义明确,不依赖模型判断 |
| 有时调有时不调(如聊天+工具混合) | 通过聚合网关 +"auto" | 网关映射后行为正确 |
| 必须调指定函数 | {"type":"function","function":{"name":"xxx"}} | 最精确,零歧义 |
| 多工具场景,模型自选 | "required"+ 多个 tools | GLM-5.2 会从 tools 里选最匹配的 |
| 用 Cline 做 Agent 开发 | base_url 走聚合网关,不改默认配置 | 最省事 |
踩坑记录 / 报错对照表
| 现象 | 原因 | 解法 |
|---|---|---|
tool_calls: null,content 有正常回复 | tool_choice为"auto"被降级 | 改为"required"或走聚合网关 |
400 Bad Request: invalid tool_choice value | 传了"none"但同时传了 tools 数组 | 要么去掉 tools,要么改 tool_choice |
tool_calls返回但arguments是空字符串"" | tools 定义里 parameters 的 JSON Schema 格式不对 | 检查"type": "object"和"properties"是否完整 |
422 Unprocessable Entity | tool_choice 用了{"type":"tool","name":"xxx"}的旧格式 | 改为{"type":"function","function":{"name":"xxx"}} |
tool_calls[0].function.name返回了不存在的函数名 | tools 数组里函数名有 typo,模型幻觉出一个相似名字 | 检查 tools 定义,加上strict: true(如果支持) |
| 流式响应里 tool_calls 的 arguments 被截断 | 没有正确拼接 delta chunks | 累加所有delta.tool_calls[0].function.arguments片段后再 JSON.parse |
常见问题 FAQ
Q: GLM-5.2 的 tool_choice 支持哪些值?
截至 2026 年 7 月 28 日,智谱官方文档标注支持:"none"、"required"、{"type":"function","function":{"name":"xxx"}}。"auto"在文档里仍然列出但行为已变更——官方没有 changelog 标注这个 breaking change,挺烦人的。
Q: 从 GLM-5 升级到 GLM-5.2,除了 tool_choice 还有什么要注意的?
我目前发现的:1) tool_choice 枚举行为变了(本文主题);2) 函数返回结果的 token 计费方式变了,function 消息的 content 现在算输入 token;3) 并行函数调用(parallel tool calls)默认开启了,如果你的代码只处理tool_calls[0]会漏掉后续调用。
Q: 用了 "required" 之后,模型每轮都强制调函数,不想调的时候怎么办?
两种方案:1) 在不需要函数调用的轮次里不传tools和tool_choice字段;2) 用聚合网关接入,传"auto",让网关的映射逻辑处理(网关会根据上下文做合理映射,不是简单的字符串替换)。
Q: 我用的是 Node.js / TypeScript,代码怎么写?
const resp = await openai.chat.completions.create({ model: "z-ai/glm-5.2", messages, tools, tool_choice: "required" as any })注意 OpenAI Node SDK 的类型定义里 tool_choice 是联合类型,"required"可能需要as any断言。
Q: 其他国产模型有类似的 tool_choice 枚举问题吗?
有。我测过的情况:豆包(volcengine/doubao-seed-2.1-pro)的"auto"行为正常;通义千问(bailian/qwen3.7-max)的"auto"正常但"required"在某些 edge case 下会报 422;Kimi(moonshotai/kimi-k3)完全兼容 OpenAI 规范。各家实现不一样,走聚合网关让网关帮你抹平差异是最省心的。
Q: 怎么判断是 tool_choice 的问题还是 prompt/tools 定义的问题?
最简单的排查法:把tool_choice改成指定函数名的写法{"type":"function","function":{"name":"你的函数名"}},如果这样能正常返回 tool_calls,那就是"auto"的枚举问题;如果还是 null,那是你的 tools JSON Schema 定义有问题。
小结
GLM-5.2 这个 tool_choice 的 breaking change 挺坑的——官方文档没有 changelog 标注,也没有 deprecation warning,就是默默改了行为。我在 7 月 23 号花了大半天才从日志里定位到。
核心记住一点:接 GLM-5.2 做函数调用,tool_choice 用"required"或者指定函数名,别用"auto"。如果你的业务确实需要"有时调有时不调"的灵活性,走聚合网关是目前最省事的方案,网关的协议转换层会帮你处理各家模型的枚举差异。
有其他 GLM-5.2 的坑欢迎评论区交流。