Skip to content

server: GLM tool-call parser stringifies all argument values (numbers/arrays become strings), breaking schema-validating clients #569

Description

@solodddd

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions