🎁 신규 무료 가입, 10회 호출 제공. 최대 $1, 카드 불필요.
Agent 루프에서 GLM 5.2 Tool Call 쓰기: 'OpenAI 호환'이 감추는 차이

Agent 루프에서 GLM 5.2 Tool Call 쓰기: 'OpenAI 호환'이 감추는 차이

목차
  1. 같은 turn을 처리하는 세 가지 방식
  2. tool call과 함께 오는 텍스트
  3. reasoning을 그대로 드러낸다
  4. GLM 5.2 tool-call turn의 비용
  5. GLM 5.2가 적합한 작업과 운영 방법
  6. 면책 조항
  7. 출처

기존 OpenAI 방식의 agent 루프에 GLM 5.2를 연결하면 대부분 그대로 동작합니다. tools를 보내고, tool_calls를 받아 실행한 뒤 결과를 다시 보내면 됩니다. 그런데 SDK 예제에는 나오지 않는 동작이 하나 있습니다. Assistant가 tool call과 같은 turn에 텍스트 한 줄도 함께 반환합니다.

{
  "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 turn 비용은 $0.0009였습니다. gpt-5.5는 $0.0042, claude-opus-4-8은 $0.0051이었습니다. 측정일은 2026-06-30입니다.
  • GLM 5.2의 warm turn 지연 시간 중앙값은 6.6s였습니다. gpt-5.5는 1.9s, opus-4-8은 3.1s였습니다. 저렴한 대신 느립니다.
  • GLM 5.2는 finish_reason: "tool_calls"인 turn에도 tool_calls와 함께 사용자에게 보이는 텍스트를 반환합니다. OpenAI 계약에서는 이때 content가 null입니다.
  • GLM 5.2의 모든 warm turn에는 reasoning token이 약 27개 포함됐습니다. 같은 작업에서 gpt-5.5와 claude-opus-4-8은 0개였습니다.

주로 쓰이는 규약은 두 가지이며, 둘 다 알아두면 좋습니다. OpenAI 방식에서는 function schema를 보내고 tool_calls를 받은 다음, 각 call에 대해 tool_call_id를 지정한 tool 메시지로 응답합니다.

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 방식은 구조가 다릅니다. tool에는 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.contentnull입니다. 많은 agent 루프가 이 동작을 전제로 합니다. “content 또는 tool call”으로 분기하거나, content를 최종 답변으로 로깅하거나, 값이 비어 있다고 단정합니다. GLM은 둘을 한 번에 반환하므로 이 가정부터 깨집니다.

여기서 설명하는 동작은 glm-5.2에 실제 tool-calling 요청을 보내 확인했습니다. 같은 작업을 gpt-5.5claude-opus-4-8에도 실행해 비교했습니다. 요약하면 GLM 5.2는 OpenAI API 인터페이스를 쓰지만, 몇 가지 동작은 GPT보다 Claude에 가깝습니다. 그래서 OpenAI 동작을 전제로 만든 루프에서 문제가 발생합니다.

같은 turn을 처리하는 세 가지 방식

같은 prompt와 같은 tool 두 개를 세 모델에 보냈습니다.

GLM (glm-5.2)OpenAI (gpt-5.5)Anthropic (claude-opus-4-8)
API 인터페이스OpenAI chat-completionsOpenAI chat-completionsAnthropic messages
tool-call turn의 텍스트content에 안내 문구 포함, null 아님contentnulltool_use 앞에 text 블록
해당 turn의 reasoningreasoning_content + reasoning_tokens로 노출숨김, usagereasoning_tokens만 제공활성화한 경우에만 thinking 블록으로 제공
병렬 tool call지원, index 포함지원지원, 여러 tool_use 블록
완료 신호finish_reason: "tool_calls"finish_reason: "tool_calls"stop_reason: "tool_use"
tool-call ID 접두사call_…call_…toolu_…

루프가 깨지는 부분은 두 행입니다. tool-call turn에 텍스트가 들어오고, 같은 turn에 reasoning도 노출됩니다. 나머지는 예상한 대로 동작합니다.

tool call과 함께 오는 텍스트

GLM 5.2는 finish_reason: "tool_calls"인 경우에도 tool_calls와 함께 짧은 안내 문구를 assistant content에 자주 담아 보냅니다. 오류도 아니고 드물게 발생하는 동작도 아닙니다.

세 모델이 같은 turn을 반환한 결과에서 차이가 나는 부분만 추렸습니다.

// 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 turn에는 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 모델이며 tool을 사용할 때도 reasoning을 멈추지 않습니다. tool-call turn에도 reasoning이 포함되고, GLM 5.2는 그 내용을 텍스트로 노출합니다. non-streaming 응답에서는 token 사용량에 명확히 표시됩니다.

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

사용자에게 보이는 출력은 짧은 function call 두 개뿐인데 completion의 거의 절반이 reasoning이었습니다. 이 부분에서는 세 모델이 모두 다르게 동작합니다. GLM 5.2는 reasoning 내용을 reasoning_content로, token 수를 별도 항목으로 제공합니다. OpenAI는 usagereasoning_tokens에 비용을 청구하지만 텍스트는 공개하지 않습니다. Anthropic은 extended thinking을 활성화했을 때만 thinking 블록으로 보여줍니다. 기본 설정 기준으로 GLM 5.2가 세 모델 중 reasoning을 가장 많이 노출합니다.

여기에는 두 가지 영향이 있습니다. 첫째는 비용입니다. tool-call turn의 reasoning token에도 비용이 들고, agent 루프는 여러 turn으로 구성됩니다. 이 수치를 조절하는 설정은 reasoning effort이며, GLM 5.2: Reasoning Effort가 비용을 좌우한다에서 다뤘습니다. 최종 답변뿐 아니라 모든 turn에서 reasoning token을 집계해야 합니다.

둘째는 streaming 순서입니다. 요청을 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 필드를 모르기 때문에 처음 들어오는 데이터를 조용히 무시합니다. 대개는 문제가 없습니다. 하지만 UI에서 첫 content delta를 기준으로 “생각하는 중…” 상태를 표시하면 문제가 됩니다. wire에서 처음 도착하는 값은 content가 아니라 reasoning이므로 상태 표시가 전환되지 않습니다.

GLM 5.2 tool-call turn의 비용

동작 방식만큼 비용도 중요합니다. agent 루프에서는 같은 turn을 여러 차례 실행하기 때문입니다. 고정 prefix로 약 2,000-token 분량의 system prompt와 tool 정의를 사용하고, 매 요청마다 user 메시지만 바꿨습니다. warm turn 10회를 측정한 결과는 다음과 같습니다.

warm tool-call turn당GLM glm-5.2OpenAI gpt-5.5Anthropic claude-opus-4-8
비용$0.0009$0.0042$0.0051
지연 시간 중앙값6.6s1.9s3.1s
cache된 prompt≈96%≈81%≈97%
reasoning token≈2700
cold → warm 비용 차이3.4×2.8×4.9×

GLM 5.2가 가장 저렴합니다. warm turn당 비용은 GPT-5.5보다 약 4.5×, Opus보다 약 5.4× 낮습니다. 대신 가장 느립니다. 이 작업에서 다른 두 모델은 reasoning token을 쓰지 않았지만 GLM은 매 turn마다 사용했기 때문에 지연 시간이 2배에서 3.5배에 달했습니다. GLM은 지연 시간을 감수해 비용을 낮추며, reasoning effort로 이를 조절할 수 있습니다.

루프 비용을 낮추려면 caching이 필수입니다. system prompt와 tool 정의는 prompt의 대부분을 차지하며 turn마다 동일합니다. prefix가 cache되면 turn 비용은 2.8×에서 4.9×까지 낮아집니다. 실제로 cache가 적용되는지는 두 가지가 좌우합니다. GLM과 OpenAI는 prefix를 자동으로 cache하지만, Anthropic은 cache_control로 지정한 부분만 cache합니다. GLM은 cache가 한 박자 늦게 warm 상태에 들어갑니다. 따라서 3단계 작업은 끝까지 전체 비용을 낼 수 있지만, 30단계 작업은 cache된 상태로 실행됩니다. 자세한 동작은 오픈 웨이트 LLM의 prompt caching에서 설명했습니다.

GLM 5.2가 적합한 작업과 운영 방법

지금까지의 결과를 종합하면 GLM 5.2는 표에서 가장 저렴하면서도 가장 느리고, 매 turn마다 reasoning을 수행합니다. 이런 특성을 보면 어떤 작업에 적합한지 분명합니다.

turn마다 몇 초가 더 걸려도 괜찮고 비용이 중요한 장시간 다단계 agent 루프에 적합합니다. 백그라운드 coding agent, CI 및 batch 자동화, 사람 없이 실행되는 작업이 여기에 해당합니다. 느리게 만드는 reasoning 덕분에 단순 routing을 넘어 실제 coding과 planning 작업에서도 성능을 유지합니다. warm-up 이후에는 caching의 이점도 누적됩니다. 30단계 작업은 prefix 비용을 분산해 저렴하게 실행할 수 있지만, 3단계 작업은 전체 비용과 지연 시간을 그대로 감수하고 끝날 수 있습니다. GLM 5.2는 긴 작업에 사용하고, turn당 6초가 체감되는 대화형 single-shot 요청에는 더 빠른 모델을 쓰는 편이 좋습니다.

OpenAI API 인터페이스를 유지하면서도 다음 다섯 가지 원칙을 지키면 GLM 5.2에 맞는 루프를 만들 수 있습니다.

  • tool-call turn에도 content가 포함될 수 있다고 처리하세요. 비어 있다고 단정하면 안 됩니다.
  • wire의 reasoning_contentusagereasoning_tokens를 예상하고 둘 다 비용에 반영하세요. reasoning-effort 설정으로 품질과 비용을 조절할 수 있습니다.
  • streaming에서는 첫 content delta를 기준으로 UI 상태를 전환하지 마세요. reasoning이 먼저 도착합니다.
  • tool_call_id는 받은 그대로 다시 보내세요. 불투명한 값으로 취급하고 parsing하거나 재생성하면 안 됩니다.
  • streaming arguments는 call이 끝날 때까지 index별로 누적하세요. chunk 개수가 정해져 있다고 가정하면 안 됩니다.

두 가지는 별도로 방어할 필요가 없습니다. GLM도 다른 모델과 마찬가지로 index가 포함된 병렬 tool call을 반환하고, round trip도 정상적으로 끝납니다. assistant turn을 추가한 뒤 각 call의 결과를 담은 tool 메시지를 하나씩 추가하면 finish_reason: "stop"으로 종료됩니다. turn 간에 cache 가능한 prefix도 byte 단위로 동일하게 유지하세요. system prompt와 tool 정의가 각 prompt의 대부분을 차지하며, prefix가 안정적이어야 GLM cache가 warm 상태에 들어간 뒤 비용을 줄일 수 있습니다.

특별히 복잡한 문제는 아닙니다. “요청이 성공한다”와 “agent 루프가 올바르게 동작한다” 사이의 차이는 GLM에서 주로 두 가지 잘못된 가정에서 생깁니다. tool-call turn에는 텍스트가 없고 reasoning도 하지 않는다는 가정입니다. 이 두 가정을 버리고 prefix를 안정적으로 유지하면 하나의 루프로 GLM, GPT, Claude를 모두 처리할 수 있습니다. 지연 시간이 최우선이 아닌 작업에서는 GLM을 훨씬 낮은 비용으로 운영할 수 있습니다.

면책 조항

위 비용, 지연 시간, cache 수치는 2026-06-30에 glm-5.2, gpt-5.5, claude-opus-4-8을 대상으로 모델별 warm tool-call turn 10회를 측정한 결과입니다. 비용은 보고된 사용량을 기준으로 계산했습니다. 지연 시간은 실제 경과 시간의 중앙값이며 부하와 reasoning effort에 따라 달라집니다. 모델 동작과 가격은 바뀔 수 있으므로 이 수치는 참고용으로만 사용하고, 실제 적용 전에 자체 트래픽으로 다시 측정하세요.

출처

← 블로그로 돌아가기