Skip to content

Preserve schema-declared JSON types in GLM tool calls - #1016

Open
babyLegionite wants to merge 1 commit into
antirez:mainfrom
babyLegionite:feat/glm-tool-argument-types
Open

Preserve schema-declared JSON types in GLM tool calls#1016
babyLegionite wants to merge 1 commit into
antirez:mainfrom
babyLegionite:feat/glm-tool-argument-types

Conversation

@babyLegionite

Copy link
Copy Markdown

GLM tool calls currently quote every argument, so a tool declaring an integer receives {"result":"40"} even when the model emits <arg_value>40</arg_value>. This breaks clients that enforce tool parameter types.

Retain the request's parameter schemas and use explicit property types when parsing GLM output. Recover JSON integers, numbers, booleans, null, arrays, and objects while preserving strings such as "001" and "true". Buffered and streamed responses share the parser; original generated tool text remains intact for replay/cache bookkeeping.

Generated literals must be complete JSON before being emitted without quotes. Lexical checks reject strtod extensions such as NaN, Infinity, hexadecimal numbers, and leading zeros, including inside arrays/objects. Integer text is preserved without floating-point conversion.

Scope

This is conservative type recovery, not full JSON Schema validation. It supports explicit type declarations and type arrays. Missing/unknown types, unions allowing strings, and schemas expressed only through $ref or combinators retain the previous string behavior. Integer recovery requires integer notation; decimal/exponent notation is supported for number. Nested array/object values are preserved, not recursively coerced.

Validation

  • Added 35 argument fixtures for supported types, nullable types, string preservation, large integers, unknown schemas, and malformed literals; extended the OpenAI GLM streaming regression with an integer argument.
  • macOS: make -j8 ds4_test ds4-server ds4_agent_test, ./ds4_test --server, ./ds4_agent_test, and make -j8 cpu passed. CPU build only; no large CPU inference run.
  • M3 Ultra, 512 GB, Metal: make -j8, test-target build, ./ds4_test --server, and ./ds4_agent_test passed.
  • Live GLM-5.3-Flash-Q4_K at context 262144: seven requests passed through local/public gateways, covering streamed arithmetic, integer tool arguments, a tool-result continuation, and mixed argument types in buffered and streamed responses. Exact decoded types were checked, including "001" remaining a string. Decode throughput was about 24 tokens/second on the short mixed-type calls; this is a smoke measurement, not a controlled performance benchmark.

Based on 6289c516273979173abbc062209a81dd3706b804. No inference backend or model-weight changes. CUDA, SSD, distributed inference, and long-context quality benchmarks were not run; changes are limited to server-side argument parsing and its tests.

@celsopitta

Copy link
Copy Markdown

Tested this on a GLM 5.3 deployment (the full model, not Flash; ds4 loads it with the GLM 5.2 shape, so /v1/models lists it as glm-5.2), with opencode as the client.

Before: opencode rejected every tool call with numeric arguments, because they arrived as strings. For example, read received {"offset":"380","limit":"140"} and failed with Expected number | undefined, got "380". This is the same failure as #569 and anomalyco/opencode#26870.

After rebuilding with this PR (on 6289c51):

  • read, both buffered and streamed: {"filePath":"/etc/hosts","offset":380,"limit":140}, with the numbers as integers
  • todowrite: todos arrives as an array of objects instead of a JSON-encoded string
  • edit: "replaceAll":true is a boolean, while "oldString":"2024" and "newString":"2025" stay strings, as their schemas declare

The server unit tests (ds4_test --server, CPU-only build on Linux) pass. I also ran opencode's actual tool schemas, including their $schema, description and minimum/maximum keys, through parse_tools_value and the GLM parser, and all arguments came out with the expected types. opencode's built-in tools use only plain type declarations, so the conservative anyOf/oneOf fallback to strings doesn't affect them.

Thanks for the fix!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants