Branch: glm5.2 (bd89932, also present at the current branch tip) Model: GLM 5.2 Component: ds4_server.c, OpenAI-compatible /v1/chat/completions tool calling
Summary
On the glm5.2 branch, the GLM tool-call parser emits every tool argument as a JSON string, regardless of its actual type. Numbers become "10", arrays become "[]", booleans become "true". Any client that validates tool-call arguments against the tool's JSON schema (Chatwise, and generally anything using zod/ajv-style validation) rejects the call.
Reproduction
curl -s http://localhost:18004/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "glm-5.2-chat",
"messages": [{"role":"user","content":"Search the web for ByteDance inference chips, return 10 results"}],
"tools": [{
"type": "function",
"function": {
"name": "WebSearch",
"description": "Search the web",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "number"},
"exclude_domains": {"type": "array", "items": {"type": "string"}}
},
"required": ["query"]
}
}
}]
}'
Actual (note max_results is a string):
"function": {
"name": "WebSearch",
"arguments": "{"query":"ByteDance inference chips","max_results":"10"}"
}
Expected:
"arguments": "{"query":"ByteDance inference chips","max_results":10}"
Client-side symptom (Chatwise, zod validation):
Invalid input for tool WebSearch: Type validation failed:
Value: {"query":"...","exclude_domains":"[]","max_results":"10"}
[{ "expected": "number", "code": "invalid_type", "path": ["max_results"] }]
Root cause
In parse_glm_generated_message_ex (ds4_server.c:5049), every <arg_value> is added with is_string hardcoded to "true":
tool_call_json_args_add(&args, key, value, "true");
GLM's wire format (<arg_key>name</arg_key><arg_value>10</arg_value>) carries no type information — unlike DeepSeek's DSML, which has the string="true|false" attribute that the DSML path already honors. So the GLM path JSON-escapes everything into a string.
Possible fixes
Heuristic (self-contained): try to parse the raw <arg_value> text as a JSON scalar/array/object (number, true/false/null, [...], {...}); if it parses, emit it raw (the existing json_minify_raw_value path, i.e. is_string = "false"), otherwise emit a string. This mirrors what most GLM tool parsers do.
Schema-aware (precise): look up the parameter's declared type in the request's tools[].function.parameters and coerce accordingly (what vLLM's GLM tool parser does). More work, since the server currently doesn't consult client tool schemas at parse time.
Happy to test a patch — I can reproduce this reliably.
Branch: glm5.2 (bd89932, also present at the current branch tip) Model: GLM 5.2 Component: ds4_server.c, OpenAI-compatible /v1/chat/completions tool calling
Summary
On the glm5.2 branch, the GLM tool-call parser emits every tool argument as a JSON string, regardless of its actual type. Numbers become "10", arrays become "[]", booleans become "true". Any client that validates tool-call arguments against the tool's JSON schema (Chatwise, and generally anything using zod/ajv-style validation) rejects the call.
Reproduction
curl -s http://localhost:18004/v1/chat/completions -H "Content-Type: application/json" -d '{
"model": "glm-5.2-chat",
"messages": [{"role":"user","content":"Search the web for ByteDance inference chips, return 10 results"}],
"tools": [{
"type": "function",
"function": {
"name": "WebSearch",
"description": "Search the web",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string"},
"max_results": {"type": "number"},
"exclude_domains": {"type": "array", "items": {"type": "string"}}
},
"required": ["query"]
}
}
}]
}'
Actual (note max_results is a string):
"function": {
"name": "WebSearch",
"arguments": "{"query":"ByteDance inference chips","max_results":"10"}"
}
Expected:
"arguments": "{"query":"ByteDance inference chips","max_results":10}"
Client-side symptom (Chatwise, zod validation):
Invalid input for tool WebSearch: Type validation failed:
Value: {"query":"...","exclude_domains":"[]","max_results":"10"}
[{ "expected": "number", "code": "invalid_type", "path": ["max_results"] }]
Root cause
In parse_glm_generated_message_ex (ds4_server.c:5049), every <arg_value> is added with is_string hardcoded to "true":
tool_call_json_args_add(&args, key, value, "true");
GLM's wire format (<arg_key>name</arg_key><arg_value>10</arg_value>) carries no type information — unlike DeepSeek's DSML, which has the string="true|false" attribute that the DSML path already honors. So the GLM path JSON-escapes everything into a string.
Possible fixes
Heuristic (self-contained): try to parse the raw <arg_value> text as a JSON scalar/array/object (number, true/false/null, [...], {...}); if it parses, emit it raw (the existing json_minify_raw_value path, i.e. is_string = "false"), otherwise emit a string. This mirrors what most GLM tool parsers do.
Schema-aware (precise): look up the parameter's declared type in the request's tools[].function.parameters and coerce accordingly (what vLLM's GLM tool parser does). More work, since the server currently doesn't consult client tool schemas at parse time.
Happy to test a patch — I can reproduce this reliably.