Novità Registrati gratis, 10 chiamate le offriamo noi. Fino a $1, senza carta.
Tool call di GLM 5.2 negli agent loop: cosa nasconde la compatibilità con OpenAI

Tool call di GLM 5.2 negli agent loop: cosa nasconde la compatibilità con OpenAI

Indice
  1. Lo stesso turno in tre formati
  2. Il testo arriva insieme al tool call
  3. Ragiona ad alta voce
  4. Quanto costa un turno con tool call su GLM 5.2
  5. Quando scegliere GLM 5.2 e come usarlo al meglio
  6. Nota metodologica
  7. Fonti

Se si collega un agent loop esistente, basato sull’API OpenAI, a GLM 5.2, quasi tutto funziona senza modifiche: si inviano i tools, si ricevono i tool_calls, si eseguono e si rimandano i risultati. Poi succede qualcosa che gli esempi degli SDK non mostrano mai. Nello stesso turno dei tool call, l’assistente restituisce anche una riga di testo:

{
  "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

  • Un turno warm con tool call è costato $0.0009 su GLM 5.2, contro $0.0042 su gpt-5.5 e $0.0051 su claude-opus-4-8 (misurazione del 2026-06-30).
  • La latenza mediana di un turno warm su GLM 5.2 è stata di 6.6s, contro 1.9s su gpt-5.5 e 3.1s su opus-4-8: il turno meno costoso è anche il più lento.
  • GLM 5.2 restituisce testo visibile nello stesso turno dei tool_calls, con finish_reason: "tool_calls"; nel contratto OpenAI, in quel caso content è null.
  • Ogni turno warm di GLM 5.2 ha usato circa 27 token di reasoning; sullo stesso task, gpt-5.5 e claude-opus-4-8 ne hanno usati 0.

Esistono due convenzioni principali ed è utile tenerle entrambe presenti. Con quella di OpenAI si inviano gli schema delle funzioni, si ricevono i tool_calls e si risponde con un messaggio tool per ogni chiamata, associato tramite 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"})

La struttura di Anthropic è diversa: i tool contengono un input_schema, il modello emette blocchi tool_use e la risposta arriva in un blocco 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 usa il formato OpenAI.

Nel contratto OpenAI, message.content è null quando finish_reason è tool_calls. Molti agent loop si basano su questo comportamento: distinguono tra “contenuto o tool call”, registrano content come risposta finale oppure verificano che sia vuoto. GLM restituisce entrambi nello stesso momento, facendo saltare subito questa assunzione.

Il comportamento descritto è stato rilevato inviando richieste reali con tool call a glm-5.2 ed eseguendo lo stesso task con gpt-5.5 e claude-opus-4-8 come riferimenti. In sintesi, GLM 5.2 espone l’API OpenAI, ma sotto alcuni aspetti si comporta più come Claude che come GPT. A incontrare problemi è quindi un loop progettato sul comportamento di OpenAI.

Lo stesso turno in tre formati

Stesso prompt, stessi due tool, tre modelli:

GLM (glm-5.2)OpenAI (gpt-5.5)Anthropic (claude-opus-4-8)
APIChat Completions di OpenAIChat Completions di OpenAIMessages di Anthropic
Testo nel turno con tool callpreambolo in content (non null)content è nullun blocco text prima di tool_use
Reasoning in quel turnoesposto: reasoning_content + reasoning_tokensnascosto; solo reasoning_tokens in usagesolo come blocco thinking, se abilitato
Tool call parallelisì, con indexsì, più blocchi tool_use
Segnale di completamentofinish_reason: "tool_calls"finish_reason: "tool_calls"stop_reason: "tool_use"
Prefisso dell’ID del tool callcall_…call_…toolu_…

I loop si rompono su due righe: il testo nel turno con tool call e il reasoning presente nello stesso turno. Per il resto non ci sono sorprese.

Il testo arriva insieme al tool call

GLM 5.2 emette regolarmente un breve preambolo in content insieme ai tool_calls, con finish_reason: "tool_calls". Non è un errore né un comportamento sporadico.

Ecco lo stesso turno sui tre modelli, limitato alla parte che cambia:

// 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 lascia content a null, GLM lo valorizza e Anthropic inserisce da sempre un blocco text in quella posizione. GLM usa quindi il formato di OpenAI, ma ha l’abitudine di Anthropic di descrivere cosa sta per fare. È un comportamento inatteso per un loop scritto sulla base di OpenAI. La correzione è semplice, ma va applicata esplicitamente: un turno con tool call non va più considerato privo di contenuto.

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 il loop mostra content all’utente come risposta dell’assistente, prima di ogni tool call comparirà una frase come “controllo subito”. Bisogna decidere se mantenerla o scartarla. La scelta deve essere esplicita, non una conseguenza del fatto che il modello resti o meno in silenzio.

Ragiona ad alta voce

GLM 5.2 è un reasoning model e non smette di ragionare quando usa i tool. Il reasoning è presente anche nei turni con tool call e GLM 5.2 ne espone il testo. In una risposta non in streaming, il conteggio dei token lo rende evidente:

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

Quasi metà del completamento è costituita dal reasoning, nonostante l’output visibile della richiesta consista in due brevi chiamate di funzione. Su questo aspetto i tre modelli si comportano tutti diversamente. GLM 5.2 restituisce il reasoning in reasoning_content insieme al relativo conteggio dei token. OpenAI addebita i reasoning_tokens indicati in usage, ma non ne mostra mai il testo. Anthropic lo espone solo nei blocchi thinking e soltanto quando si abilita l’extended thinking. Per impostazione predefinita, GLM 5.2 è il più trasparente dei tre.

Ci sono due conseguenze. La prima riguarda i costi: i token di reasoning vengono addebitati anche nei turni con tool call, che in un agent loop possono essere molti. Il parametro che incide sul consumo è il reasoning effort, come spiegato in GLM 5.2: il reasoning effort determina il costo. I token di reasoning vanno conteggiati in ogni turno, non solo nella risposta finale.

La seconda riguarda l’ordine degli eventi in streaming. GLM invia prima il reasoning, poi il testo introduttivo e infine i tool call:

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

Un parser scritto per le normali Chat Completions di OpenAI non conosce il campo reasoning_content e ignorerà senza errori questa prima sequenza di dati. Di solito non è un problema. Lo diventa se la UI attiva lo stato “elaborazione in corso…” al primo delta di contenuto: il primo elemento ricevuto è il reasoning, non il contenuto, quindi l’indicatore non cambia mai stato.

Quanto costa un turno con tool call su GLM 5.2

Il comportamento è solo metà del quadro; l’altra metà è il costo, soprattutto perché un agent loop ripete lo stesso tipo di turno molte volte. Il test usa un prefisso fisso, composto da un system prompt di circa 2,000 token più le definizioni dei tool, e varia il messaggio utente a ogni chiamata. Questi sono i risultati misurati su dieci turni warm:

per turno warm con tool callGLM glm-5.2OpenAI gpt-5.5Anthropic claude-opus-4-8
Costo$0.0009$0.0042$0.0051
Latenza (mediana)6.6s1.9s3.1s
Prompt in cache≈96%≈81%≈97%
Token di reasoning≈2700
Rapporto tra costo cold e warm3.4×2.8×4.9×

GLM 5.2 è il più economico: ogni turno warm costa circa 4.5× meno di GPT-5.5 e 5.4× meno di Opus. È anche il più lento, con una latenza da due a tre volte e mezzo superiore, perché usa token di reasoning in ogni turno, mentre gli altri due non ne hanno usati per questo task. Il compromesso è chiaro: GLM riduce i costi aumentando la latenza, e il reasoning effort è il parametro che regola questo equilibrio.

La cache rende sostenibile economicamente un loop su tutti questi modelli. Il system prompt e le definizioni dei tool costituiscono gran parte di ogni prompt e restano identici tra i turni. Quando il prefisso entra in cache, il costo del turno si riduce da 2.8× a 4.9×. Il risultato dipende da due fattori. GLM e OpenAI mettono automaticamente in cache il prefisso; Anthropic memorizza solo ciò che viene contrassegnato con cache_control. Inoltre, la cache di GLM impiega un po’ più di tempo a scaldarsi: un task di tre passaggi può essere eseguito interamente a prezzo pieno, mentre uno di trenta sfrutta la cache. I dettagli sono descritti in Cache dei prompt negli LLM open-weight.

Quando scegliere GLM 5.2 e come usarlo al meglio

Il quadro complessivo è questo: GLM 5.2 è il modello meno costoso della tabella, ma anche il più lento, e ragiona a ogni turno. Questo profilo ne definisce i casi d’uso ideali.

È adatto agli agent loop lunghi e articolati, nei quali il costo conta più della latenza e qualche secondo in più per turno è accettabile: coding agent in background, CI, automazioni batch e job non presidiati. Il reasoning che lo rallenta gli permette anche di affrontare attività reali di coding e pianificazione, invece del semplice routing. Dopo il warm-up, la cache rende il vantaggio ancora più netto: un task di trenta passaggi ammortizza il prefisso e costa poco, mentre uno di tre può pagare il prezzo pieno e subire la latenza senza ottenere benefici. GLM 5.2 conviene quindi per i job lunghi. Per le chiamate interattive e singole, nelle quali sei secondi per turno sono percepibili, è meglio mantenere un modello più veloce.

Per usare bene GLM 5.2 bastano cinque accorgimenti, senza abbandonare l’API OpenAI:

  • Un turno con tool call può contenere anche content. Non verificare che sia vuoto.
  • Prevedi reasoning_content nei dati ricevuti e reasoning_tokens in usage; includili entrambi nel budget e usa il reasoning effort per bilanciare qualità e costo.
  • In streaming, non basare lo stato della UI sul primo delta di contenuto, perché il reasoning arriva prima.
  • Reinvia tool_call_id senza modificarlo; trattalo come un valore opaco, senza analizzarlo né rigenerarlo.
  • Accumula gli arguments ricevuti in streaming in base a index finché la chiamata non termina; non presumere un numero fisso di chunk.

Non servono invece contromisure per due aspetti: GLM emette tool call paralleli con un index, come gli altri modelli, e il ciclo si chiude normalmente. Aggiungi il turno dell’assistente, poi un messaggio tool con il risultato per ogni chiamata; il modello terminerà con finish_reason: "stop". Mantieni inoltre invariato, byte per byte, il prefisso memorizzabile in cache tra un turno e l’altro. Il system prompt e le definizioni dei tool costituiscono gran parte di ogni prompt, e solo un prefisso stabile permette alla cache di GLM di ridurre i costi dopo il warm-up.

Non c’è nulla di particolarmente insolito. È la differenza tra “la richiesta va a buon fine” e “l’agent loop è corretto”. Con GLM, tutto ruota principalmente intorno a due assunzioni sbagliate: che un turno con tool call non contenga testo e che il modello non ragioni durante quel turno. Eliminandole e mantenendo stabile il prefisso, lo stesso loop può gestire GLM, GPT e Claude. Dove la latenza non è il parametro da ottimizzare, GLM lo fa a una frazione del costo.

Nota metodologica

I dati su costi, latenza e cache riportati sopra sono stati misurati il 2026-06-30 su dieci turni warm con tool call per ciascun modello, usando glm-5.2, gpt-5.5 e claude-opus-4-8. Il costo deriva dai dati di utilizzo restituiti dalle API; la latenza è la mediana wall-clock e varia in base al carico e al reasoning effort. Comportamento e prezzi dei modelli cambiano nel tempo: questi valori sono indicativi e vanno verificati sul proprio traffico prima di basarvi decisioni operative.

Fonti

← Torna al blog