🎁 Novo Cadastre-se grátis, 10 chamadas por nossa conta. Até US$ 1, sem cartão.
Tool Calls do GLM 5.2 em Agent Loops: o que 'compatível com OpenAI' esconde

Tool Calls do GLM 5.2 em Agent Loops: o que 'compatível com OpenAI' esconde

Conteúdo
  1. O mesmo turno, de três formas
  2. Texto vem junto com a tool call
  3. Ele pensa em voz alta
  4. Quanto custa um turno de tool call no GLM 5.2
  5. Quando usar o GLM 5.2 e como rodá-lo bem
  6. Aviso
  7. Fontes

Aponte um agent loop existente no estilo OpenAI para o GLM 5.2 e quase tudo funciona: você manda tools, recebe tool_calls, executa, manda os resultados. Aí ele faz algo que os exemplos do SDK nunca mostram. O assistant devolve uma linha de texto no mesmo turno das tool calls:

{
  "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\"}"}}
      ]
    }
  }]
}

Duas convenções dominam, e vale manter as duas em mente. Na da OpenAI, você manda os schemas das funções, recebe tool_calls de volta e responde com uma mensagem tool por call, identificada pelo 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"})

A da Anthropic tem outro formato: as tools carregam 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 agent loops dependem disso: fazem branch em “content ou tool calls”, logam content como resposta final, ou assumem que está vazio. O GLM te entrega os dois ao mesmo tempo, e essa suposição é a primeira coisa que quebra.

Esse comportamento foi capturado em requests reais de tool calling para o glm-5.2, com gpt-5.5 e claude-opus-4-8 rodando a mesma tarefa como referência. Em resumo: o GLM 5.2 usa a superfície da API da OpenAI, mas em alguns aspectos se comporta mais como o Claude do que como o GPT, e é o loop treinado para a OpenAI que tropeça.

O mesmo turno, de três formas

Mesmo prompt, mesmas duas ferramentas, três modelos:

GLM (glm-5.2)OpenAI (gpt-5.5)Anthropic (claude-opus-4-8)
Superfície da APIOpenAI chat-completionsOpenAI chat-completionsAnthropic messages
Texto no turno da tool-callpreâmbulo em content (não nulo)content é nullum bloco text antes do tool_use
Reasoning nesse turnoexposto: reasoning_content + reasoning_tokensoculto; só reasoning_tokens no usagesó como bloco thinking, se você habilitar
Tool calls paralelassim, com indexsimsim, múltiplos blocos tool_use
Sinal de conclusãofinish_reason: "tool_calls"finish_reason: "tool_calls"stop_reason: "tool_use"
Prefixo do id da tool-callcall_…call_…toolu_…

São duas linhas que quebram os loops: o texto no turno da tool-call e o reasoning aparecendo nesse turno. O resto é tranquilamente entediante.

Texto vem junto com a tool call

O GLM 5.2 emite com frequência um preâmbulo curto no content do assistant junto com tool_calls, com finish_reason: "tool_calls". Não é erro e não é eventual.

Aqui está o mesmo turno nos três, reduzido à parte que difere:

// 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 o content nulo; o GLM o preenche; a Anthropic sempre colocou um bloco text ali. Ou seja, o GLM pega o formato de fio da OpenAI com o hábito da Anthropic de narrar antes de agir, e quem é pego de surpresa é o loop escrito para a OpenAI. O ajuste é pequeno, mas você precisa fazê-lo de propósito. Pare de tratar um turno de tool-call como se 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 renderiza o content para o usuário como a resposta do assistant, agora você vai mostrar uma linha de “deixa eu verificar isso” antes de cada tool call. Decida se você quer isso. A questão é que a decisão é sua, e não algo que o silêncio do modelo faz por você.

Ele pensa em voz alta

O GLM 5.2 é um modelo de reasoning, e isso não para para usar tools. Um turno de tool call carrega o reasoning junto, e o GLM 5.2 expõe esse reasoning como texto. Numa resposta não-streaming a contabilidade 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 reasoning, numa requisição cuja saída visível são duas chamadas de função curtas. É nesta linha que os três modelos divergem. O GLM 5.2 te entrega o reasoning como reasoning_content mais a contagem de tokens. O OpenAI cobra reasoning_tokens no usage mas nunca mostra o texto. O Anthropic só mostra como blocos thinking, e só quando você liga o extended thinking. Dos três, o GLM 5.2 é o mais exposto por padrão.

Duas consequências. Primeiro, custo: você paga por esses tokens de reasoning nos turnos de tool call, e um loop de agente tem muitos turnos. O reasoning effort é o botão que mexe nesse número, como vimos em GLM 5.2: Reasoning Effort Is the Cost Lever. Conte os tokens de reasoning em cada turno, não só na resposta final.

Segundo, a ordem do streaming. Ao fazer o streaming da requisição, o GLM manda primeiro o reasoning, depois o texto do preâmbulo, e só então as tool calls:

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

Um parser escrito para o chat completions padrão do OpenAI não conhece o campo reasoning_content e vai ignorar essa rajada inicial sem reclamar. Em geral, sem problema. Vira problema se a sua UI mostra um estado de “thinking…” acionado pelo primeiro delta de content, porque o que chega primeiro na conexão é reasoning, não content, e o indicador nunca dispara.

Quanto custa um turno de tool call no GLM 5.2

O comportamento é metade da história; a conta é a outra metade, e um loop de agente roda o mesmo turno muitas vezes. Com um prefixo fixo (um system prompt de cerca de 2.000 tokens mais as definições de tools) e a mensagem do usuário variando a cada chamada, medido em dez turnos quentes:

por turno de tool call quenteGLM glm-5.2OpenAI gpt-5.5Anthropic claude-opus-4-8
Custo$0.0009$0.0042$0.0051
Latência (mediana)6.6s1.9s3.1s
Prompt em cache≈96%≈81%≈97%
Tokens de reasoning≈2700
Custo frio → quente3.4×2.8×4.9×

O GLM 5.2 é o barato: cerca de 4,5× mais barato que o GPT-5.5 e 5,4× mais barato que o Opus por turno quente. Também é o lento, com latência de duas a três vezes e meia a dos outros, porque gasta tokens de reasoning em cada turno enquanto os outros dois não gastaram nenhum nesta tarefa. É o trade: o GLM troca custo por latência, e o reasoning effort é o botão que ajusta isso.

O cache é o que torna qualquer um deles viável num loop. O system prompt e as definições de tools são a maior parte de cada prompt e são idênticos a cada turno, então, uma vez que o prefixo está em cache, o turno fica 2,8× a 4,9× mais barato. Duas coisas determinam se você vê esse ganho. GLM e OpenAI cacheiam o prefixo automaticamente; o Anthropic só cacheia o que você marca com cache_control. E o cache do GLM aquece um instante depois, então uma tarefa de três passos pode pagar preço cheio enquanto uma de trinta passos roda em cache. A mecânica está em Open-Weight LLM Caching.

Quando usar o GLM 5.2 e como rodá-lo bem

Juntando as peças: na tabela, o GLM 5.2 é o modelo barato e também o lento, e ele raciocina a cada turno. Esse perfil indica onde ele vale a pena.

Onde ele encaixa: loops de agente longos e com vários passos, onde o custo pesa mais e dá pra aceitar alguns segundos por turno. Agentes de coding em background, CI e automação em batch, jobs que rodam sem supervisão. O raciocínio que o deixa lento é também o motivo de ele se sair bem em coding e planejamento de verdade, em vez de roteamento trivial. Depois do aquecimento, o cache reforça o argumento: uma tarefa de trinta passos amortiza o prefixo e fica barata, enquanto uma de três passos pode pagar o preço cheio e engolir a latência sem retorno. Então use o GLM 5.2 nos jobs longos, e mantenha um modelo mais rápido para as chamadas interativas e de tiro único, onde os seis segundos por turno se notam.

Como rodar o GLM 5.2 bem. Cinco hábitos deixam um loop pronto pro GLM sem sair da superfície da API da OpenAI:

  • Trate um turno com tool call como podendo trazer content. Não presuma que está vazio.
  • Espere reasoning_content no fio e reasoning_tokens no usage; reserve orçamento para os dois e use o controle de reasoning-effort para trocar qualidade por custo.
  • No streaming, não amarre o estado da UI no primeiro delta de content, porque o reasoning chega primeiro.
  • Repita o tool_call_id exatamente como veio; trate-o como opaco, nunca o parseie nem o regere.
  • Acumule os arguments do streaming por index até a chamada fechar; não assuma uma contagem de chunks.

Duas coisas contra as quais você não precisa se defender: o GLM emite tool calls paralelas com um index como os outros, e o round-trip fecha normalmente. Acrescente o turno do assistant, acrescente uma mensagem tool por chamada com o resultado dela, e termina com finish_reason: "stop". De quebra, mantenha o prefixo cacheável estável byte a byte entre os turnos; o system prompt e as definições de tools são a maior parte de qualquer prompt, e é um prefixo estável que permite ao cache do GLM carregar o custo depois que ele aquece.

Nada disso é exótico. É a diferença entre “a request teve sucesso” e “o loop do agente está correto”, e no GLM essa diferença mora basicamente em duas suposições: que um turno com tool call é silencioso, e que ele não está pensando. Largue essas duas, mantenha o prefixo estável, e um único loop atende GLM, GPT e Claude do mesmo jeito, com o GLM fazendo isso por uma fração do custo onde latência não é o que você está otimizando.

Aviso

Os números de custo, latência e cache acima foram medidos em 2026-06-30 ao longo de dez turnos quentes de tool call por modelo, com glm-5.2, gpt-5.5 e claude-opus-4-8. O custo vem do usage reportado; a latência é a mediana de wall-clock e varia com carga e reasoning effort. O comportamento dos modelos e os preços mudam, então trate os números como indicativos e meça de novo contra o seu próprio tráfego antes de depender deles.

Fontes

← Voltar ao blog