🎁 新用戶 免費註冊,送 10 次呼叫,最高 $1,免綁卡。
Agent Loop 中的 GLM 5.2 工具呼叫:「OpenAI 相容」藏了什麼

Agent Loop 中的 GLM 5.2 工具呼叫:「OpenAI 相容」藏了什麼

目錄
  1. 同一回合,三種樣貌
  2. 文字跟著工具呼叫一起來
  3. 它會把思考過程說出來
  4. GLM 5.2 的一次 tool-call 回合要花多少
  5. 什麼時候該用 GLM 5.2,以及怎麼把它跑好
  6. 免責聲明
  7. 來源

把現成的 OpenAI 風格 agent loop 接到 GLM 5.2 上,大部分都能直接跑:你送出 tools,拿回 tool_calls,執行它們,再把結果送回去。接著它會做一件 SDK 範例從來沒演示過的事。助理在送出工具呼叫的同一輪裡,順手回了一行文字:

{
  "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 那套:你送出 function 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.content 會是 null。很多 agent loop 就靠這點:它們用「有 content 還是有 tool call」來分流,把 content 當成最終答案記下來,或是直接斷言它是空的。GLM 兩個一起給你,這個假設就是第一個會壞掉的地方。

這裡的行為來自對 glm-5.2 的實際工具呼叫請求,並用 gpt-5.5claude-opus-4-8 跑同一個任務當對照組。簡單講:GLM 5.2 用的是 OpenAI 的 API 介面,但在某幾個面向上,它的行為比較像 Claude 而不是 GPT,而會踩雷的正是按 OpenAI 訓練出來的那種 loop。

同一回合,三種樣貌

相同的 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 block
該回合的 reasoning直接給出: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_…

會讓迴圈出問題的是其中兩列:工具呼叫回合中的文字,以及該回合冒出來的 reasoning。其餘的部分都平淡無奇,反而讓人安心。

文字跟著工具呼叫一起來

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 寫的迴圈反而會被打個措手不及。修正很小,但你得刻意去做。別再把工具呼叫回合當成沒有 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 是推理模型,呼叫工具時不會暫停推理。每一次 tool-call 回合都帶著推理過程,而 GLM 5.2 會把它以文字形式直接呈現出來。在非串流的回應裡,從 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 區塊裡顯示,而且只有開啟 extended thinking 時才有。三者之中,GLM 5.2 預設曝露得最多。

這帶來兩個後果。第一是成本:tool-call 回合裡的這些推理 token 都得付費,而一個 agent loop 會跑很多回合。reasoning effort 就是調整這個數字的旋鈕,我們在 GLM 5.2:Reasoning Effort 是成本槓桿 裡談過。每一回合都要計算推理 token,不能只算最後的答案。

第二是串流順序。串流請求時,GLM 會先送推理過程,接著是前言文字,最後才是 tool calls:

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

照標準 OpenAI chat completions 寫的 parser 不認得 reasoning_content 欄位,會默默忽略開頭那一段。通常沒問題。但如果你的 UI 用第一個 content delta 來觸發「thinking…」狀態,問題就來了:因為通訊線上最先出現的是推理,不是 content,指示燈永遠不會切換。

GLM 5.2 的一次 tool-call 回合要花多少

行為只說了一半,帳單是另一半,而 agent loop 會把同一個回合反覆跑很多次。固定一段前綴(约 2,000 個 token 的系統提示加上工具定義),每次呼叫只變動使用者訊息,量測十個熱回合的結果如下:

每個熱 tool-call 回合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 就是調整它的旋鈕。

快取是讓這些在 loop 裡都還負擔得起的關鍵。系統提示和工具定義佔了每次 prompt 的絕大部分,而且每回合都一樣,所以前綴一旦進了快取,每回合就便宜 2.8 到 4.9 倍。能不能享受到這個好處,由兩件事決定。GLM 和 OpenAI 會自動快取前綴;Anthropic 只快取你用 cache_control 標記的部分。而且 GLM 的快取會慢一拍才暖起來,所以三步的任務可能整段都付全價,三十步的任務則一路跑在快取上。機制細節在 開放權重 LLM 的快取

什麼時候該用 GLM 5.2,以及怎麼把它跑好

把前面的線索拼起來。在那張表裡,GLM 5.2 是最便宜的模型,也是最慢的,而且它每一輪都會推理。這個特性本身就指出了它該出現在哪裡。

它適合的場景:成本是主要考量、每輪多花幾秒可以接受的長鏈、多步驟 agent loop。背景執行的 coding agent、CI 與批次自動化,以及無人值守的工作。讓它變慢的那套推理,也正是它在真實的寫程式和規劃任務上撐得住的原因,而不只是做些瑣碎的路由。快取在暖機之後會讓優勢進一步放大:一個三十步的任務能攤平前綴成本,跑起來很便宜;而一個三步的任務可能得付全額,還白白吃下延遲。所以長任務就交給 GLM 5.2,互動式、單次呼叫、每輪六秒會被使用者感受到的場景,則留給更快的模型。

怎麼把 GLM 5.2 跑好。下面五個習慣能讓你的 loop 準備好接 GLM,而且完全不用離開 OpenAI API 的介面:

  • 把帶有 tool call 的那一輪當成「可能也帶 content」來處理,別斷定它是空的。
  • 預期 wire 上會出現 reasoning_contentusage 裡會有 reasoning_tokens;兩者都要預留額度,並用 reasoning-effort 這個旋鈕在品質和成本之間做取捨。
  • 在 streaming 中,別把 UI 狀態綁在第一個 content delta 上,因為 reasoning 會先到。
  • 原封不動地回傳 tool_call_id;把它當成不透明的字串,永遠不要去解析或重新產生它。
  • index 把 streaming 的 arguments 累積起來,直到該次呼叫結束;不要假設 chunk 的數量。

有兩件事你不需要特別防範:GLM 發出 parallel tool call 時,會像其他模型一樣帶 index,而且整個 round-trip 會正常收尾。把 assistant 那一輪附加上去,每個呼叫各附加一則帶結果的 tool 訊息,它就會以 finish_reason: "stop" 結束。順手把可快取的前綴在每一輪之間維持 byte 層級的穩定;system prompt 和 tool 定義占了每個 prompt 的絕大部分,而穩定的前綴正是讓 GLM 的快取在暖起來之後能持續攤平成本的關鍵。

這些都不是什麼奇技。它就是「請求成功」和「agent loop 正確」之間的那道縫,而在 GLM 上,這道縫差不多就是兩個假設的寬度:以為帶 tool call 的那一輪是沉默的,以及以為它沒在思考。拿掉這兩個假設,把前綴維持穩定,同一個 loop 就能同時承載 GLM、GPT 和 Claude,而且只要不是在優化延遲的地方,GLM 都能用零頭的成本把活幹完。

免責聲明

上面的成本、延遲與快取數字,是在 2026-06-30 量測的,每個模型跑十次暖過的 tool-call 輪次,使用 glm-5.2gpt-5.5claude-opus-4-8。成本取自回報的 usage;延遲是 wall-clock 中位數,會隨負載和 reasoning effort 而變動。模型行為和價格都會漂移,所以把這些數字當作參考即可,在依賴它們之前,請用你自己的流量重新量測。

來源

← 返回部落格