Tool calls do GLM 5.2: o que a compatibilidade com OpenAI esconde
Conteúdo
Aponte um loop de agente existente, no estilo da OpenAI, para o GLM 5.2 e quase tudo simplesmente funciona: você envia tools, recebe tool_calls, executa as ferramentas e devolve os resultados. Então acontece algo que os exemplos dos SDKs nunca mostram. O assistente retorna uma linha de texto no mesmo turno das chamadas de ferramentas:
{
"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
- Um turno aquecido com chamada de ferramenta no GLM 5.2 custou $0.0009, contra $0.0042 no gpt-5.5 e $0.0051 no claude-opus-4-8 (medição de 2026-06-30).
- A latência mediana dos turnos aquecidos do GLM 5.2 foi de 6.6s, contra 1.9s no gpt-5.5 e 3.1s no opus-4-8: o turno mais barato também é o mais lento.
- O GLM 5.2 retorna texto visível no mesmo turno que
tool_calls, comfinish_reason: "tool_calls"; no contrato da OpenAI,contentfica nulo nesse caso. - Cada turno aquecido do GLM 5.2 usou cerca de 27 tokens de raciocínio; gpt-5.5 e claude-opus-4-8 usaram 0 na mesma tarefa.
Duas convenções predominam, e é útil ter ambas em mente. Na OpenAI, você envia os schemas das funções, recebe tool_calls e responde com uma mensagem tool para cada chamada, associada por 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"})
Na Anthropic, o formato é diferente: as ferramentas têm um input_schema, o modelo emite blocos tool_use e você responde com um bloco 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"}]})
O GLM 5.2 fala o dialeto da OpenAI.
No contrato da OpenAI, message.content é null quando finish_reason é tool_calls. Muitos loops de agentes dependem disso: separam os caminhos entre “conteúdo ou chamadas de ferramentas”, registram content como resposta final ou verificam que o campo está vazio. O GLM entrega os dois de uma vez, e essa é a primeira suposição que deixa de valer.
Esse comportamento foi observado em requisições reais com chamadas de ferramentas para glm-5.2, usando gpt-5.5 e claude-opus-4-8 na mesma tarefa como referências. Em resumo: o GLM 5.2 usa a interface da API da OpenAI, mas, em alguns aspectos, se comporta mais como o Claude do que como o GPT. Um loop projetado para a OpenAI é justamente o que tropeça.
O mesmo turno de três formas
O mesmo prompt, as mesmas duas ferramentas e três modelos:
GLM (glm-5.2) | OpenAI (gpt-5.5) | Anthropic (claude-opus-4-8) | |
|---|---|---|---|
| Interface da API | chat-completions da OpenAI | chat-completions da OpenAI | messages da Anthropic |
| Texto no turno da chamada de ferramenta | preâmbulo em content (não nulo) | content é null | um bloco text antes de tool_use |
| Raciocínio nesse turno | exposto: reasoning_content + reasoning_tokens | oculto; apenas reasoning_tokens em usage | somente como bloco thinking, se habilitado |
| Chamadas de ferramentas em paralelo | sim, com index | sim | sim, vários blocos tool_use |
| Sinal de conclusão | finish_reason: "tool_calls" | finish_reason: "tool_calls" | stop_reason: "tool_use" |
| Prefixo do ID da chamada | call_… | call_… | toolu_… |
Duas linhas são as que quebram os loops: texto no turno da chamada de ferramenta e raciocínio exposto nesse turno. O restante funciona como esperado.
O texto vem junto com a chamada de ferramenta
O GLM 5.2 normalmente emite um preâmbulo curto em content junto com tool_calls, usando finish_reason: "tool_calls". Não é um erro nem um comportamento ocasional.
Este é o mesmo turno nos três modelos, reduzido à parte que muda:
// 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 */ } ]
A OpenAI deixa content como nulo; o GLM preenche o campo; a Anthropic sempre colocou um bloco text nessa posição. Portanto, o GLM combina o formato de transporte da OpenAI com o hábito da Anthropic de narrar antes de agir. Um loop escrito para a OpenAI acaba pego de surpresa. A correção é pequena, mas precisa ser intencional. Pare de tratar um turno com chamada de ferramenta como se ele não tivesse conteúdo:
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})
Se o seu loop exibe content ao usuário como resposta do assistente, agora ele mostrará uma linha como “vou verificar” antes de cada chamada de ferramenta. Decida se isso faz sentido para o seu produto. Essa decisão deve ser sua, não uma consequência do silêncio do modelo.
Ele pensa em voz alta
O GLM 5.2 é um modelo de raciocínio, e esse raciocínio não é interrompido durante o uso de ferramentas. O turno com a chamada também contém raciocínio, e o GLM 5.2 o expõe como texto. Em uma resposta sem streaming, a contagem de tokens deixa isso explícito:
"usage": {
"prompt_tokens": 224,
"completion_tokens": 68,
"completion_tokens_details": { "reasoning_tokens": 30 },
"total_tokens": 292
}
Quase metade da completion foi raciocínio, embora a saída visível da requisição sejam apenas duas chamadas curtas de função. É nesse ponto que os três modelos divergem. O GLM 5.2 entrega o raciocínio em reasoning_content, além da contagem de tokens. A OpenAI cobra pelos reasoning_tokens registrados em usage, mas nunca mostra o texto. A Anthropic só o exibe em blocos thinking, e apenas quando o extended thinking está habilitado. Por padrão, o GLM 5.2 é o mais transparente dos três.
Isso tem duas consequências. A primeira é custo: você paga pelos tokens de raciocínio nos turnos com chamadas de ferramentas, e um loop de agente tem muitos turnos. O reasoning effort é o controle que altera esse valor, como explicamos em GLM 5.2: o reasoning effort é a alavanca de custo. Conte os tokens de raciocínio em todos os turnos, não apenas na resposta final.
A segunda é a ordem no streaming. Quando a requisição usa streaming, o GLM envia primeiro o raciocínio, depois o texto do preâmbulo e, por fim, as chamadas de ferramentas:
reasoning_content (many deltas)
content (a few deltas)
tool_calls (id + name, then arguments)
Um parser feito para chat completions convencionais da OpenAI não conhece o campo reasoning_content e ignora silenciosamente esse bloco inicial. Em geral, isso não causa problemas. Mas causa quando a UI exibe um estado “pensando…” a partir do primeiro delta de conteúdo: a primeira coisa que chega pela conexão é o raciocínio, não o conteúdo, e o indicador nunca muda de estado.
Quanto custa um turno com chamada de ferramenta no GLM 5.2
O comportamento é só metade da história; a outra metade é a conta, principalmente porque um loop de agente repete esse turno muitas vezes. Com um prefixo fixo — um system prompt de aproximadamente 2.000 tokens mais as definições das ferramentas — e uma mensagem do usuário diferente em cada chamada, estes foram os resultados de dez turnos aquecidos:
| por turno aquecido com chamada de ferramenta | GLM glm-5.2 | OpenAI gpt-5.5 | Anthropic claude-opus-4-8 |
|---|---|---|---|
| Custo | $0.0009 | $0.0042 | $0.0051 |
| Latência (mediana) | 6.6s | 1.9s | 3.1s |
| Prompt em cache | ≈96% | ≈81% | ≈97% |
| Tokens de raciocínio | ≈27 | 0 | 0 |
| Custo a frio → aquecido | 3.4× | 2.8× | 4.9× |
O GLM 5.2 é o mais barato: por turno aquecido, custa aproximadamente 4.5× menos que o GPT-5.5 e 5.4× menos que o Opus. Também é o mais lento, com latência de duas a três vezes e meia maior, porque usa tokens de raciocínio em todos os turnos, enquanto os outros dois não usaram nenhum nessa tarefa. Essa é a troca: o GLM reduz o custo em troca de latência, e o reasoning effort controla esse equilíbrio.
O cache é o que torna qualquer um desses modelos viável financeiramente em um loop. O system prompt e as definições das ferramentas representam a maior parte de cada prompt e são idênticos entre os turnos. Depois que o prefixo entra no cache, o turno fica entre 2.8× e 4.9× mais barato. Dois fatores determinam se você terá esse ganho. O GLM e a OpenAI armazenam o prefixo em cache automaticamente; na Anthropic, apenas o conteúdo marcado com cache_control é armazenado. Além disso, o cache do GLM leva um pouco mais de tempo para aquecer. Uma tarefa de três etapas pode pagar o preço integral, enquanto uma de trinta etapas roda com cache. Os detalhes estão em Cache de LLMs open-weight.
Quando usar o GLM 5.2 e como operá-lo bem
Juntando tudo, o GLM 5.2 é o modelo mais barato da tabela, também é o mais lento e raciocina em todos os turnos. Esse perfil deixa claro onde ele faz sentido.
Onde ele se encaixa: loops de agentes longos e com várias etapas, nos quais o custo é a prioridade e alguns segundos por turno são aceitáveis. Agentes de programação em segundo plano, CI, automações em lote e tarefas executadas sem supervisão. O raciocínio que o torna lento também explica por que ele funciona bem em programação e planejamento reais, em vez de apenas fazer roteamento simples. Depois do aquecimento, o cache reforça essa vantagem: uma tarefa de trinta etapas amortiza o prefixo e fica barata, enquanto uma de três etapas pode pagar o preço integral e ainda sofrer a latência sem benefício. Use o GLM 5.2 em tarefas longas e mantenha um modelo mais rápido para chamadas interativas e pontuais, nas quais seis segundos por turno fazem diferença.
Como operar bem o GLM 5.2. Cinco práticas deixam um loop preparado para o GLM sem sair da interface da API da OpenAI:
- Considere que um turno com chamada de ferramenta pode trazer
content. Não verifique que ele está vazio. - Espere
reasoning_contentna conexão ereasoning_tokensemusage; inclua ambos no orçamento e use o controle de reasoning effort para equilibrar qualidade e custo. - No streaming, não vincule o estado da UI ao primeiro delta de conteúdo, porque o raciocínio chega antes.
- Devolva
tool_call_idsem nenhuma alteração; trate-o como opaco, sem tentar interpretá-lo ou gerá-lo novamente. - Acumule os
argumentsdo streaming porindexaté a chamada terminar; não presuma uma quantidade fixa de chunks.
Você não precisa se proteger contra dois pontos: o GLM emite chamadas de ferramentas em paralelo com um index, como os demais, e o ciclo fecha normalmente. Adicione o turno do assistente, depois uma mensagem tool por chamada com o respectivo resultado, e o modelo concluirá com finish_reason: "stop". Mantenha também o prefixo armazenável em cache idêntico byte a byte entre os turnos. O system prompt e as definições das ferramentas representam a maior parte de cada prompt, e um prefixo estável permite que o cache do GLM reduza o custo depois de aquecido.
Nada disso é exótico. É a diferença entre “a requisição funcionou” e “o loop do agente está correto”. No GLM, essa diferença se resume principalmente a duas suposições: que um turno com chamada de ferramenta não produz texto e que o modelo não raciocina nesse turno. Elimine essas duas suposições, mantenha o prefixo estável e o mesmo loop funcionará com GLM, GPT e Claude. Onde latência não for a prioridade, o GLM fará isso por uma fração do custo.
Aviso
Os números de custo, latência e cache acima foram medidos em 2026-06-30, com dez turnos aquecidos de chamadas de ferramentas por modelo, usando glm-5.2, gpt-5.5 e claude-opus-4-8. O custo foi calculado a partir do uso informado; a latência é a mediana do tempo total e varia conforme a carga e o reasoning effort. O comportamento e os preços dos modelos mudam, portanto trate esses valores como indicativos e faça novas medições com o seu próprio tráfego antes de depender deles.