エージェントループでの GLM 5.2 Tool Call:「OpenAI 互換」に隠れた違い
目次
既存の OpenAI 形式のエージェントループを GLM 5.2 に向けても、ほとんどはそのまま動きます。tools を送り、tool_calls を受け取り、ツールを実行して結果を返すだけです。ただし、SDK のサンプルにはない動作が 1 つあります。Tool Call と同じターンで、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
- warm 状態での GLM 5.2 の Tool Call 1 ターンあたりのコストは $0.0009 でした。gpt-5.5 は $0.0042、claude-opus-4-8 は $0.0051 です(2026-06-30 計測)。
- GLM 5.2 の warm ターンにおけるレイテンシ中央値は 6.6s でした。gpt-5.5 は 1.9s、opus-4-8 は 3.1s です。安い代わりに遅いモデルです。
- GLM 5.2 は
finish_reason: "tool_calls"のターンで、tool_callsと同時にユーザーから見えるテキストも返します。OpenAI の仕様では、このときcontentは null です。 - GLM 5.2 の warm ターンでは、毎回約 27 reasoning token が発生しました。同じタスクで gpt-5.5 と claude-opus-4-8 は 0 でした。
主流の仕様は 2 つあり、両方を把握しておく必要があります。OpenAI 方式では、関数スキーマを送り、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_reason が tool_calls の場合、message.content は null になります。多くのエージェントループはこの前提で実装されています。「content または Tool Call」で分岐したり、content を最終回答としてログに記録したり、空であることを assert したりします。GLM は両方を同時に返すため、まずこの前提が崩れます。
ここで扱う挙動は、glm-5.2 に実際の Tool Calling リクエストを送って確認したものです。比較対象として、同じタスクを gpt-5.5 と claude-opus-4-8 でも実行しました。要点をまとめると、GLM 5.2 の API インターフェースは OpenAI 互換ですが、いくつかの点では GPT より Claude に近い動きをします。そのため、OpenAI 前提のループで問題が起きます。
同じターンを 3 モデルで比較
同じプロンプトと同じ 2 つのツールを 3 モデルに与えました。
GLM (glm-5.2) | OpenAI (gpt-5.5) | Anthropic (claude-opus-4-8) | |
|---|---|---|---|
| API インターフェース | OpenAI chat-completions | OpenAI chat-completions | Anthropic messages |
| Tool Call ターンのテキスト | content に前置き文(null ではない) | content は null | tool_use の前に text ブロック |
| そのターンの reasoning | 公開:reasoning_content + reasoning_tokens | 非公開。usage に reasoning_tokens のみ | 有効化した場合のみ thinking ブロック |
| 並列 Tool Call | 対応。index 付き | 対応 | 対応。複数の tool_use ブロック |
| 完了を示す値 | finish_reason: "tool_calls" | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| Tool Call ID の prefix | call_… | call_… | toolu_… |
ループが壊れるのは主に 2 行です。Tool Call ターンにもテキストがあることと、そのターンにも reasoning が現れることです。それ以外は想定どおりです。
Tool Call と同時にテキストも返る
GLM 5.2 は、finish_reason: "tool_calls" のターンで、tool_calls と一緒に短い前置き文を assistant の content に入れます。これはエラーでも、まれな挙動でもありません。
3 モデルの同じターンについて、異なる箇所だけを抜粋します。
// 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 の wire format を使いながら、ツール実行前に説明を入れる Anthropic に近い挙動をします。そのため、OpenAI 向けに書かれたループでは想定外になります。修正自体は小さいものですが、明示的な対応が必要です。Tool Call ターンには 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 の返答としてユーザーに表示する実装なら、Tool Call のたびに「確認します」のような文が表示されます。それを表示するかどうかは、アプリケーション側で決めてください。モデルが何も返さないことを前提に、暗黙に決めてはいけません。
reasoning がそのまま見える
GLM 5.2 は reasoning model であり、ツール使用中も reasoning を止めません。Tool Call ターンにも reasoning が含まれ、GLM 5.2 はそのテキストを公開します。非 streaming レスポンスでは、token の内訳からも明確に確認できます。
"usage": {
"prompt_tokens": 224,
"completion_tokens": 68,
"completion_tokens_details": { "reasoning_tokens": 30 },
"total_tokens": 292
}
ユーザーから見える出力は短い関数呼び出しが 2 つだけですが、completion のほぼ半分が reasoning です。この点は 3 モデルで異なります。GLM 5.2 は reasoning を reasoning_content として返し、token 数も示します。OpenAI は usage の reasoning_tokens を課金対象にしますが、テキストは公開しません。Anthropic で表示されるのは thinking ブロックのみで、extended thinking を有効にした場合に限られます。デフォルトで最も多く公開するのは GLM 5.2 です。
影響は 2 つあります。1 つ目はコストです。Tool Call ターンの reasoning token も課金対象となり、エージェントループではターンが何度も繰り返されます。この値を動かすのが reasoning effort です。詳しくは GLM 5.2:コストを左右する Reasoning Effort で扱っています。最終回答だけでなく、すべてのターンで reasoning token を集計してください。
2 つ目は streaming 時の順序です。GLM は最初に reasoning、次に前置き文、最後に Tool Call を送ります。
reasoning_content (many deltas)
content (a few deltas)
tool_calls (id + name, then arguments)
通常の OpenAI chat completions 向けに書かれた parser は reasoning_content を認識せず、冒頭のデータを黙って無視します。多くの場合は問題ありません。ただし、最初の content delta を受け取った時点で UI の「thinking…」表示を切り替える実装では問題になります。wire 上で最初に届くのは content ではなく reasoning なので、インジケーターが切り替わりません。
GLM 5.2 の Tool Call 1 ターンにかかるコスト
挙動だけでなく、コストも重要です。エージェントループでは同じターンを何度も実行します。固定 prefix として、約 2,000 token の system prompt とツール定義を使い、リクエストごとに user message を変更しました。warm 状態で 10 ターン計測した結果です。
| warm 状態の Tool Call 1 ターンあたり | 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% |
| Reasoning token | ≈27 | 0 | 0 |
| cold → warm のコスト差 | 3.4× | 2.8× | 4.9× |
最も安いのは GLM 5.2 です。warm ターンあたりのコストは GPT-5.5 の約 4.5× 分の 1、Opus の約 5.4× 分の 1 です。一方、最も遅いモデルでもあります。今回のタスクでは他の 2 モデルが reasoning token を使わなかったのに対し、GLM は毎ターン使用したため、レイテンシは 2 倍から 3.5 倍になりました。GLM はレイテンシと引き換えにコストを下げるモデルであり、そのバランスを調整するのが reasoning effort です。
ループのコストを抑えるうえで、キャッシュは不可欠です。system prompt とツール定義は prompt の大半を占め、各ターンで同一です。prefix がキャッシュされれば、1 ターンあたりのコストは 2.8× から 4.9× 安くなります。実際にキャッシュが効くかどうかは、2 つの要素で決まります。GLM と OpenAI は prefix を自動的にキャッシュしますが、Anthropic では cache_control を指定した部分だけが対象です。また、GLM のキャッシュは効き始めるまで少し時間がかかります。そのため、3 ステップのタスクでは最後まで通常料金になる可能性がありますが、30 ステップならキャッシュが効いた状態で実行できます。仕組みの詳細は オープンウェイト LLM のキャッシュ で説明しています。
GLM 5.2 が向く用途と実装上のポイント
ここまでの結果を整理すると、GLM 5.2 はこの表で最も安く、最も遅く、毎ターン reasoning を実行するモデルです。この特性から、適した用途が見えてきます。
向いているのは、数秒の追加レイテンシを許容でき、コストを重視する長いマルチステップのエージェントループです。バックグラウンドで動く coding agent、CI や batch automation、無人で実行するジョブなどが該当します。reasoning によって遅くなりますが、その reasoning があるからこそ、単純な routing ではなく実際の coding や planning でも性能を維持できます。warm-up 後はキャッシュの効果も積み上がります。30 ステップのタスクなら prefix のコストを償却して安く実行できます。一方、3 ステップでは通常料金のまま、レイテンシだけ負担する可能性があります。GLM 5.2 は長時間のジョブに使い、1 ターン 6 秒が気になる対話的な単発リクエストには、より高速なモデルを使うのが適切です。
GLM 5.2 を安定して運用するためのポイントは 5 つです。いずれも OpenAI API のインターフェースを維持したまま対応できます。
- Tool Call ターンにも
contentが含まれる可能性を考慮します。空であることを assert してはいけません。 - wire 上の
reasoning_contentとusage内のreasoning_tokensを前提にします。両方をコストに含め、reasoning-effort の設定で品質とコストを調整します。 - streaming では、最初の content delta を基準に UI の状態を切り替えないでください。先に reasoning が届きます。
tool_call_idはそのまま返してください。不透明な値として扱い、解析や再生成をしてはいけません。- streaming の
argumentsは、呼び出しが閉じるまでindexごとに結合してください。chunk 数を決め打ちしてはいけません。
対応不要な点も 2 つあります。GLM は他のモデルと同様に、index 付きの並列 Tool Call を返します。また、往復処理も通常どおり完了します。assistant ターンを追加し、呼び出しごとに結果を含む tool メッセージを 1 つずつ追加すれば、最後は finish_reason: "stop" で終了します。あわせて、ターン間でキャッシュ対象の prefix を byte 単位で同一に保ってください。system prompt とツール定義は prompt の大半を占めます。GLM のキャッシュが warm になった後でコストを抑えられるかどうかは、prefix の安定性で決まります。
特殊な対応ではありません。「リクエストが成功する」状態と「エージェントループが正しく動く」状態には差があり、GLM では主に 2 つの前提がその差を生みます。Tool Call ターンは無言であるという前提と、そのターンでは reasoning しないという前提です。この 2 つをなくし、prefix を安定させれば、同じループで GLM、GPT、Claude を扱えます。レイテンシを最優先しない用途なら、GLM は大幅に低いコストで実行できます。
免責事項
上記のコスト、レイテンシ、キャッシュの値は、2026-06-30 に glm-5.2、gpt-5.5、claude-opus-4-8 を使用し、各モデルで warm 状態の Tool Call を 10 ターン実行して計測しました。コストは報告された usage に基づきます。レイテンシは実時間の中央値で、負荷や reasoning effort によって変動します。モデルの挙動や価格は変わるため、これらの値は目安として扱い、実際に利用する前に自社のトラフィックで再計測してください。