Agent 循环中的 GLM 5.2 工具调用:“兼容 OpenAI”没有告诉你的事
把现有的 OpenAI 风格 Agent 循环接到 GLM 5.2 上,大部分逻辑都能直接工作:发送 tools,收到 tool_calls,执行工具,再发回结果。但随后会出现一个 SDK 示例从未展示过的情况。Assistant 在返回工具调用的同一轮里,还会返回一行文本:
{
"choices": [{
"finish_reason": "tool_calls",
"message": {
"role": "assistant",
"content": "I'll look up both pieces of information for you at the same time!",
"tool_calls": [
{"id": "call_…", "type": "function",
"function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}},
{"id": "call_…", "type": "function",
"function": {"name": "get_time", "arguments": "{\"city\":\"Tokyo\"}"}}
]
}
}]
}
TL;DR
- GLM 5.2 单次热缓存工具调用的成本为 $0.0009,gpt-5.5 为 $0.0042,claude-opus-4-8 为 $0.0051(测量日期:2026-06-30)。
- GLM 5.2 热缓存轮次的延迟中位数为 6.6s,gpt-5.5 为 1.9s,opus-4-8 为 3.1s:成本低,但速度也慢。
- GLM 5.2 会在返回
tool_calls和finish_reason: "tool_calls"的同一轮中返回可见文本;按 OpenAI 的约定,此时content为 null。 - GLM 5.2 每个热缓存轮次约有 27 个推理 token;相同任务中,gpt-5.5 和 claude-opus-4-8 均为 0。
目前主要有两套工具调用约定,理解差异很重要。OpenAI 的方式是:发送函数 schema,收到 tool_calls 后,针对每个调用返回一条 tool 消息,并通过 tool_call_id 关联:
resp = openai.chat.completions.create(model="…", tools=tools, tool_choice="auto", messages=messages)
# assistant.tool_calls → [{"id": "call_…", "function": {"name": "get_weather", "arguments": "{\"city\":\"Paris\"}"}}]
messages.append(resp.choices[0].message)
messages.append({"role": "tool", "tool_call_id": "call_…", "content": "18C, clear"})
Anthropic 的结构不同:工具使用 input_schema,模型输出 tool_use block,调用方再用 tool_result block 返回结果:
resp = anthropic.messages.create(model="…", tools=tools, messages=messages)
# resp.content → [{"type": "tool_use", "id": "toolu_…", "name": "get_weather", "input": {"city": "Paris"}}]
messages.append({"role": "assistant", "content": resp.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": "toolu_…", "content": "18C, clear"}]})
GLM 5.2 使用的是 OpenAI 这套协议。
按 OpenAI 的约定,当 finish_reason 为 tool_calls 时,message.content 应为 null。很多 Agent 循环都依赖这个前提:用“内容还是工具调用”来做分支、把 content 记为最终答案,或者直接断言它为空。GLM 会同时返回两者,这个假设首先会失效。
本文行为来自对 glm-5.2 的真实工具调用请求,并使用 gpt-5.5 和 claude-opus-4-8 执行相同任务作为对照。简而言之,GLM 5.2 使用 OpenAI 的 API 形式,但在几个方面更像 Claude,而不是 GPT。基于 OpenAI 行为编写的循环反而最容易出问题。
同一轮,三种返回方式
相同的 prompt、相同的两个工具、三个模型:
GLM (glm-5.2) | OpenAI (gpt-5.5) | Anthropic (claude-opus-4-8) | |
|---|---|---|---|
| API 形式 | OpenAI chat-completions | OpenAI chat-completions | Anthropic messages |
| 工具调用轮次中的文本 | content 前置文本(非 null) | content 为 null | tool_use 前有一个 text block |
| 当前轮次的推理 | 暴露:reasoning_content + reasoning_tokens | 隐藏;仅在 usage 中提供 reasoning_tokens | 仅在启用后以 thinking block 返回 |
| 并行工具调用 | 支持,带 index | 支持 | 支持,包含多个 tool_use block |
| 结束信号 | finish_reason: "tool_calls" | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| 工具调用 ID 前缀 | call_… | call_… | toolu_… |
真正会让循环出错的是两行:工具调用轮次中带有文本,以及当前轮次会暴露推理。其他方面都没有意外。
文本与工具调用一起返回
GLM 5.2 经常会在 tool_calls 旁边返回一段简短的 Assistant content 前置文本,同时将 finish_reason 设为 tool_calls。这不是错误,也不是偶发现象。
下面是三个模型在同一轮中的响应,仅保留差异部分:
// OpenAI gpt-5.5: content is null on a tool-call turn
"message": { "content": null,
"tool_calls": [ {/* get_weather */}, {/* get_time */} ] }
// GLM glm-5.2: content carries a preamble
"message": { "content": "I'll look up both pieces of information for you at the same time!",
"tool_calls": [ {/* get_weather */}, {/* get_time */} ] }
// Anthropic claude-opus-4-8: a text block sits before the tool_use blocks
"content": [ { "type": "text", "text": "I'll get both pieces of information for you." },
{ "type": "tool_use", /* get_weather */ },
{ "type": "tool_use", /* get_time */ } ]
OpenAI 将 content 留为 null;GLM 会填入内容;Anthropic 一直会在这里放一个 text block。也就是说,GLM 使用 OpenAI 的传输格式,却保留了 Anthropic 在执行工具前先描述动作的习惯。基于 OpenAI 行为编写的循环因此容易措手不及。修复并不复杂,但需要明确处理:不要再假设工具调用轮次一定没有内容。
resp = client.chat.completions.create(model="glm-5.2", messages=msgs, tools=tools)
msg = resp.choices[0].message
# GLM may return assistant text in the same turn as the tool calls.
if msg.content:
log.debug("preamble: %s", msg.content) # keep or drop, but don't assume it's empty
msgs.append(msg)
for call in msg.tool_calls:
result = dispatch(call.function.name, json.loads(call.function.arguments))
msgs.append({"role": "tool", "tool_call_id": call.id, "content": result})
如果循环会把 content 作为 Assistant 回复展示给用户,那么现在每次工具调用前都会出现一句“我来查一下”。是否展示由你决定。关键在于,这应该是显式决策,不能再依赖模型保持沉默。
它会把推理过程直接暴露出来
GLM 5.2 是推理模型,工具调用期间也不会暂停推理。工具调用轮次会附带推理内容,而且 GLM 5.2 会以文本形式将其暴露出来。在非 streaming 响应中,token 统计清楚地体现了这一点:
"usage": {
"prompt_tokens": 224,
"completion_tokens": 68,
"completion_tokens_details": { "reasoning_tokens": 30 },
"total_tokens": 292
}
这次请求的可见输出只有两个简短的函数调用,但推理占了 completion 的近一半。三个模型在这里的行为完全不同。GLM 5.2 会通过 reasoning_content 返回推理文本,并提供 token 数量。OpenAI 会在 usage 中对 reasoning_tokens 计费,但不会返回文本。Anthropic 只会通过 thinking block 展示,而且必须先启用 extended thinking。默认情况下,GLM 5.2 暴露的内容最多。
这会带来两个影响。首先是成本:工具调用轮次中的推理 token 也要付费,而 Agent 循环通常包含很多轮。reasoning effort 是控制这项成本的主要参数,详见 GLM 5.2:Reasoning Effort 才是成本开关。每一轮都要统计推理 token,而不只是最终回答。
其次是 streaming 顺序。使用 streaming 请求时,GLM 会先发送推理,再发送前置文本,最后发送工具调用:
reasoning_content (many deltas)
content (a few deltas)
tool_calls (id + name, then arguments)
按标准 OpenAI chat completions 编写的解析器不认识 reasoning_content 字段,通常会静默忽略开头这一段。大多数情况下没问题。但如果 UI 的“思考中……”状态依赖第一个 content delta 来切换,就会出错。网络流中最先到达的是推理,而不是 content,状态指示器将无法切换。
GLM 5.2 单轮工具调用的成本
行为只是一半,账单则是另一半,而 Agent 循环会多次执行相同的轮次。使用固定前缀(约 2,000 个 token 的 system prompt 加工具定义),每次调用只更改用户消息,并测量十个热缓存轮次后,结果如下:
| 每个热缓存工具调用轮次 | GLM glm-5.2 | OpenAI gpt-5.5 | Anthropic claude-opus-4-8 |
|---|---|---|---|
| 成本 | $0.0009 | $0.0042 | $0.0051 |
| 延迟(中位数) | 6.6s | 1.9s | 3.1s |
| Prompt 缓存率 | ≈96% | ≈81% | ≈97% |
| 推理 token | ≈27 | 0 | 0 |
| 冷缓存到热缓存的成本差 | 3.4× | 2.8× | 4.9× |
GLM 5.2 成本最低:每个热缓存轮次约比 GPT-5.5 便宜 4.5×,比 Opus 便宜 5.4×。但它的速度也最慢,延迟是另外两者的 2 到 3.5 倍,因为 GLM 每轮都会消耗推理 token,而另外两个模型在此任务中都没有消耗。取舍很明确:GLM 用延迟换成本,reasoning effort 则用于调节两者。
缓存让循环中的这些模型变得可负担。system prompt 和工具定义占据了每次 prompt 的大部分,而且每轮完全相同。前缀进入缓存后,单轮成本会降低 2.8× 到 4.9×。能否获得这种收益取决于两点。GLM 和 OpenAI 会自动缓存前缀;Anthropic 只缓存使用 cache_control 标记的内容。此外,GLM 的缓存会晚一拍生效,因此三步任务可能全程按原价计费,而三十步任务则能在缓存命中后降低成本。具体机制见 开放权重 LLM 的缓存机制。
什么时候该用 GLM 5.2,以及如何用好它
综合这些特点,GLM 5.2 在表中成本最低、速度最慢,而且每轮都会推理。它适合的场景也由此变得清晰。
它适合长时间、多步骤的 Agent 循环:成本优先,并且每轮多等几秒可以接受。例如后台编程 Agent、CI、批处理自动化,以及无人值守任务。持续推理虽然让它变慢,却也让它能处理真正的编程和规划任务,而不只适合简单路由。缓存生效后,任务越长,优势越明显:三十步任务可以摊薄前缀成本,并以较低成本持续运行;三步任务则可能全程按原价付费,还要白白承受延迟。因此,长任务适合 GLM 5.2;交互式、单次调用则应保留速度更快的模型,因为每轮六秒的延迟会很明显。
要用好 GLM 5.2,只需养成五个习惯,无须离开 OpenAI API 形式:
- 工具调用轮次可能同时携带
content,不要断言它为空。 - 预期传输中出现
reasoning_content,并在usage中出现reasoning_tokens;两者都要纳入预算,再通过 reasoning-effort 参数权衡质量和成本。 - 在 streaming 模式下,不要用第一个 content delta 驱动 UI 状态,因为推理会先到达。
- 原样回传
tool_call_id;将其视为不透明值,不要解析或重新生成。 - 按
index累积 streaming 返回的arguments,直到调用结束;不要假设 chunk 数量。
有两件事不需要额外防御:GLM 和其他模型一样,会通过 index 输出并行工具调用;整个调用往返也能正常结束。追加 Assistant 轮次,再为每个调用追加一条包含结果的 tool 消息,最终就会以 finish_reason: "stop" 结束。同时要确保各轮之间可缓存前缀的字节完全一致。system prompt 和工具定义占据了每次 prompt 的大部分,只有稳定的前缀才能让 GLM 缓存预热后持续节省成本。
这些都不复杂。区别只在于“请求成功”和“Agent 循环正确”并不是一回事。对 GLM 来说,两者之间主要隔着两个错误假设:工具调用轮次没有文本,以及工具调用期间不会推理。去掉这两个假设,并保持前缀稳定,同一套循环就能同时支持 GLM、GPT 和 Claude。在延迟不是首要优化目标的场景下,GLM 还能把成本降到另外两者的一小部分。
免责声明
以上成本、延迟和缓存数据测量于 2026-06-30。每个模型均执行了十个热缓存工具调用轮次,使用的模型分别为 glm-5.2、gpt-5.5 和 claude-opus-4-8。成本来自上报的 usage;延迟为端到端耗时的中位数,会随负载和 reasoning effort 变化。模型行为和价格都会变化,这些数据仅供参考。在用于实际决策前,请根据自己的流量重新测量。