🎁 新人 免费注册,送 10 次调用,最高 $1,免绑卡。
Agent 循环里的 GLM 5.2 工具调用:「兼容 OpenAI」背后藏了什么

Agent 循环里的 GLM 5.2 工具调用:「兼容 OpenAI」背后藏了什么

目录
  1. 同一轮对话,三种写法
  2. 文本和工具调用一起返回
  3. 它会”想出声”
  4. 一个 GLM 5.2 工具调用回合的成本
  5. 什么时候该用 GLM 5.2,以及怎么用好它
  6. 免责声明
  7. 来源

把一个现成的 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\"}"}}
      ]
    }
  }]
}

主流的约定有两种,两边都得记在心里。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 块,你用 tool_result 块来回应:

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_reasontool_calls 时,message.contentnull。很多 agent 循环都靠这一点:它们在「content 还是 tool calls」上分支,把 content 当成最终答案记下来,或者断言它是空的。GLM 把两个一起塞给你,于是这个假设第一个崩。

这里的行为是从对 glm-5.2 的真实工具调用请求里抓出来的,同一个任务也跑了 gpt-5.5claude-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-completionsOpenAI chat-completionsAnthropic messages
工具调用轮里的文本content 前导文本(非 null)contentnulltool_use 前面的一个 text
该轮的推理内容暴露出来:reasoning_content + reasoning_tokens隐藏;只在 usage 里给 reasoning_tokens只在你启用后以 thinking 块出现
并行工具调用支持,带 index支持支持,多个 tool_use
完成信号finish_reason: "tool_calls"finish_reason: "tool_calls"stop_reason: "tool_use"
工具调用 id 前缀call_…call_…toolu_…

会让循环出问题的是两行:工具调用轮里出现文本,以及推理内容出现在该轮。其余部分都老老实实,没什么花样。

文本和工具调用一起返回

GLM 5.2 经常在 tool_calls 旁边带上一段简短的 assistant content 前导文本,finish_reasontool_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 块。所以 GLM 用的是 OpenAI 的报文格式,却带着 Anthropic 那种”动手前先说一句”的习惯,被打个措手不及的,正是照着 OpenAI 写的循环。修复很简单,但你得有意识地去做。别再把工具调用轮当成没有 content 的:

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 会把它以文本形式暴露出来。在非流式响应里,token 统计把这一点说得很清楚:

"usage": {
  "prompt_tokens": 224,
  "completion_tokens": 68,
  "completion_tokens_details": { "reasoning_tokens": 30 },
  "total_tokens": 292
}

这次请求的可见输出只是两个简短的函数调用,但补全里差不多一半都是推理。三个模型正是在这一行上分道扬镳。GLM 5.2 把推理内容作为 reasoning_content 返回,并附上 token 数。OpenAI 在 usage 里对 reasoning_tokens 计费,但从不展示文本。Anthropic 只在你打开 extended thinking 时,以 thinking 块的形式展示。三者之中,GLM 5.2 默认暴露得最多。

由此带来两个后果。第一是成本:工具调用回合里这些推理 token 都要付费,而一个 agent 循环往往要跑很多回合。控制这个数字的旋钮是 reasoning effort,我们在 GLM 5.2:Reasoning Effort 是成本杠杆 里讲过。每个回合都要统计推理 token,而不是只看最终答案。

第二是流式输出的顺序。流式请求时,GLM 先发推理,再发前导文本,最后才是工具调用:

reasoning_content  (many deltas)
content            (a few deltas)
tool_calls         (id + name, then arguments)

按原版 OpenAI chat completions 写的解析器不认识 reasoning_content 字段,会悄悄忽略开头这一串。通常没问题。但如果你的 UI 用第一个 content delta 来触发”thinking…”状态,问题就来了:线上最先到的是推理而不是 content,指示器永远不会切换。

一个 GLM 5.2 工具调用回合的成本

行为只是一半,账单是另一半,而 agent 循环会把同样的回合跑上许多遍。固定前缀(约 2000 token 的 system prompt 加上工具定义),每次调用只改 user message,在十个热回合上测量:

每个热工具调用回合GLM glm-5.2OpenAI gpt-5.5Anthropic claude-opus-4-8
成本$0.0009$0.0042$0.0051
延迟(中位数)6.6s1.9s3.1s
Prompt 缓存命中≈96%≈81%≈97%
推理 token≈2700
冷 → 热成本3.4×2.8×4.9×

GLM 5.2 是便宜的那个:每个热回合比 GPT-5.5 便宜约 4.5 倍,比 Opus 便宜约 5.4 倍。它也是慢的那个,延迟是另外两者的两到三倍半,因为它每个回合都要花推理 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 接口的前提下适配 GLM:

  • 把一个带 tool-call 的轮次当成可能也带 content,别断定它一定是空的。
  • 在传输流里预期会有 reasoning_content,在 usage 里预期会有 reasoning_tokens;两者都要留出预算,并用 reasoning-effort 这个旋钮在质量和成本之间做取舍。
  • 在 streaming 中,不要用第一个内容 delta 来触发 UI 状态,因为推理内容会先到。
  • 原样回传 tool_call_id,把它当成不透明的字符串,不要解析、也不要重新生成。
  • index 累积 streaming 的 arguments,直到这次调用结束;不要假定分片的数量。

有两件事你不用专门去防:GLM 发出的并行 tool call 跟其他模型一样带 index,而且整个往返会正常收尾。追加 assistant 轮次,每个调用追加一条带结果的 tool 消息,它就会以 finish_reason: "stop" 结束。顺便在每一轮都保持可缓存前缀的字节稳定;system prompt 和工具定义占了每个 prompt 的大头,正是稳定的前缀让 GLM 的缓存在预热后把成本扛下来。

这些都不复杂。它只是「请求成功」和「agent 循环正确」之间的差距,而在 GLM 上这个差距主要就是两个假设:以为带 tool-call 的轮次是沉默的,以及以为它没在思考。去掉这两个假设,保持前缀稳定,同一套循环就能同时跑 GLM、GPT 和 Claude,而在延迟不是优化目标的场景里,GLM 能用零头的成本完成同样的事。

免责声明

上文的成本、延迟和缓存数据是在 2026-06-30 测的,用 glm-5.2gpt-5.5claude-opus-4-8,每个模型跑十轮预热后的 tool-call 轮次。成本取自上报的 usage;延迟是 wall-clock 中位数,会随负载和 reasoning effort 变化。模型行为和价格都会变,所以把这些数字当作参考,在依赖它们之前先用你自己的流量重新测一遍。

来源

← 返回博客