Skip to content

[Bugfix] Fix DeepSeekV32 tool parser incorrect type conversion for array/object parameters - #30797

Closed
fangtaosong wants to merge 3 commits into
vllm-project:mainfrom
fangtaosong:dpsk-v32-tool-fix
Closed

fangtaosong wants to merge 3 commits into
vllm-project:mainfrom
fangtaosong:dpsk-v32-tool-fix

Conversation

@fangtaosong

@fangtaosong fangtaosong commented Dec 16, 2025 •

Copy link
Copy Markdown

The DeepSeekV32ToolParser was incorrectly serializing array and object type parameters as strings instead of their proper JSON types. This caused tool calls with complex parameters (e.g., todos: [{...}]) to be serialized as escaped strings (e.g., "todos": "[{...}]") rather than actual JSON arrays/objects.

This issue breaks integration with downstream applications (such as Claude Code) that perform strict type validation on tool arguments.

Root cause:

  • The parser defaulted to extracting XML content as raw strings via Regex.
  • There was no mechanism to look up the expected parameter type from the tool definition.
  • Parameters were double-encoded when json.dumps was called on the already stringified values during the final tool call construction.

Fix:

  • Added _get_param_type() helper to look up parameter types from the ChatCompletionRequest tools schema.
  • Updated _parse_invoke_params() to accept tool_name and request for type lookup.
  • Enhanced _convert_param_value() and the parsing logic (both streaming and non-streaming) to attempt json.loads when the expected type is array/object, or as a fallback when the type is unknown.

Test Plan

Verified the fix by integrating DeepSeek-V3.2 with Claude Code (Anthropic's CLI tool), which heavily relies on TodoWrite tool calls containing array parameters (todos).

Steps:

  1. Serve DeepSeek-V3.2 using vLLM with the modified parser.
  2. Connect Claude Code to the vLLM endpoint.
  3. Prompt the model to perform a task that triggers the TodoWrite tool (requires a list of objects).
  4. Verify that the tool arguments are correctly deserialized into JSON arrays and accepted by the client.

Test Result

Before Fix:
The client receives the todos parameter as a string. Validation fails.

{
  "type": "assistant",
  "message": {
    "id": "chatcmpl-9068c14695bca837",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "tool_use",
        "id": "chatcmpl-tool-ad2bf0b87531d72b",
        "name": "TodoWrite",
        "input": {
          "todos": "[{\"content\": \"Explore Django codebase to understand i18n_patterns and locale handling\", \"status\": \"in_progress\", \"activeForm\": \"Exploring Django codebase for i18n_patterns locale handling\"}, {\"content\": \"Find the specific code that handles language prefix matching\", \"status\": \"pending\", \"activeForm\": \"Finding language prefix matching code\"}, {\"content\": \"Reproduce the issue to confirm the bug\", \"status\": \"pending\", \"activeForm\": \"Reproducing the i18n_patterns issue\"}, {\"content\": \"Analyze the root cause of the bug\", \"status\": \"pending\", \"activeForm\": \"Analyzing root cause of locale parsing\"}, {\"content\": \"Implement fix for language locales with script and region\", \"status\": \"pending\", \"activeForm\": \"Implementing fix for language locales with script and region\"}, {\"content\": \"Test the fix with the problematic locales\", \"status\": \"pending\", \"activeForm\": \"Testing fix with problematic locales\"}]"
        }
      }
    ],
    "model": "deepseek_v3.2",
    "stop_reason": "tool_use",
    "usage": {
      "input_tokens": 17052,
      "output_tokens": 254
    },
    "context_management": null
  },
  "parent_tool_use_id": null,
  "session_id": "a8f618b7-d1d1-4306-9a6a-083f837a001e",
  "uuid": "5f4e31ae-801a-44af-bcff-921caa6b94f0"
}
{
  "type": "user",
  "message": {
    "role": "user",
    "content": [
      {
        "type": "tool_result",
        "content": "<tool_use_error>InputValidationError: TodoWrite failed due to the following issue:\nThe parameter `todos` type is expected as `array` but provided as `string`</tool_use_error>",
        "is_error": true,
        "tool_use_id": "chatcmpl-tool-ad2bf0b87531d72b"
      }
    ]
  },
  "parent_tool_use_id": null,
  "session_id": "a8f618b7-d1d1-4306-9a6a-083f837a001e",
  "uuid": "d79441e5-0476-4a68-9698-c208ff98e58d",
  "tool_use_result": "InputValidationError: [\n  {\n    \"code\": \"invalid_type\",\n    \"expected\": \"array\",\n    \"received\": \"string\",\n    \"path\": [\n      \"todos\"\n    ],\n    \"message\": \"Expected array, received string\"\n  }\n]"
}

After Fix:
The client receives the todos parameter as a JSON Array. Validation succeeds.

{
  "type": "assistant",
  "message": {
    "id": "chatcmpl-adb360f4bba4c60d",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "text",
        "text": "I'll help you investigate and fix the internationalization issue where `i18n_patterns` doesn't support locale containing both script and region (like `en-latn-us`). Let me start by exploring the Django codebase to understand how i18n patterns handle language codes.\n\n"
      }
    ],
    "model": "deepseek_v3.2",
    "stop_reason": "tool_use",
    "usage": {
      "input_tokens": 16689,
      "output_tokens": 306
    },
    "context_management": null
  },
  "parent_tool_use_id": null,
  "session_id": "2aa341be-b82c-4098-ad5e-8f81bc9a4ef2",
  "uuid": "bf244b3a-7287-4221-bd09-b8aeab9eaf85"
}
{
  "type": "assistant",
  "message": {
    "id": "chatcmpl-adb360f4bba4c60d",
    "type": "message",
    "role": "assistant",
    "content": [
      {
        "type": "tool_use",
        "id": "chatcmpl-tool-b58ebfb23bb032e6",
        "name": "TodoWrite",
        "input": {
          "todos": [
            {
              "content": "Explore Django codebase to understand i18n patterns and language code handling",
              "status": "in_progress",
              "activeForm": "Exploring Django codebase to understand i18n patterns and language code handling"
            },
            {
              "content": "Reproduce the issue locally to confirm the problem",
              "status": "pending",
              "activeForm": "Reproducing the issue locally to confirm the problem"
            },
            {
              "content": "Identify the root cause of the bug",
              "status": "pending",
              "activeForm": "Identifying the root cause of the bug"
            },
            {
              "content": "Implement a fix for the issue",
              "status": "pending",
              "activeForm": "Implementing a fix for the issue"
            },
            {
              "content": "Test the fix with various language code formats",
              "status": "pending",
              "activeForm": "Testing the fix with various language code formats"
            },
            {
              "content": "Update tests if necessary",
              "status": "pending",
              "activeForm": "Updating tests if necessary"
            }
          ]
        }
      }
    ],
    "model": "deepseek_v3.2",
    "stop_reason": "tool_use",
    "usage": {
      "input_tokens": 16689,
      "output_tokens": 306
    },
    "context_management": null
  },
  "parent_tool_use_id": null,
  "session_id": "2aa341be-b82c-4098-ad5e-8f81bc9a4ef2",
  "uuid": "860260f5-5dfc-4b12-9c57-6592c3adcec0"
}

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.
  • (Optional) Release notes update. If your change is user facing, please update the release notes draft in the Google Doc.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.

@mergify mergify Bot added the deepseek Related to DeepSeek models label Dec 16, 2025

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a bug in the DeepSeekV32ToolParser that caused incorrect serialization of array and object parameters. The fix, which involves looking up parameter types from the tool schema and using json.loads for complex types, is well-implemented. I particularly appreciate the refactoring that introduced the _get_param_type helper function, which centralizes type lookup logic and removes code duplication between streaming and non-streaming parsing. I've identified one high-severity issue regarding robustness against malformed tool definitions in the new helper function and provided a suggestion to prevent potential TypeError exceptions.

and hasattr(tool.function, "parameters")
):
params = tool.function.parameters
if isinstance(params, dict) and "properties" in params:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The code doesn't validate that params['properties'] is a dictionary before it's used. If a user provides a malformed tool definition where properties is not a dictionary (e.g., a list), the subsequent code that assumes it's a dictionary could raise a TypeError. To make this more robust against such malformed inputs, you should ensure params['properties'] is a dictionary.

Suggested change
if isinstance(params, dict) and "properties" in params:
if isinstance(params, dict) and isinstance(params.get("properties"), dict):

…ray/object parameters

The DeepSeekV32 tool parser was incorrectly serializing array and object
type parameters as strings instead of their proper JSON types. This caused
tool calls with array parameters (e.g., todos: [{...}]) to be serialized
as escaped strings rather than actual JSON arrays.

Root cause:
- The parser defaulted to "string" type when parameter type lookup failed
- Type conversion logic processed string type before object/array types
- Parameters were double-encoded when json.dumps was called on string values

Fix:
- Add _get_param_type() helper to look up parameter types from tool schema
- Update _parse_invoke_params() to accept tool_name and request for type lookup
- Reorder _convert_param_value() to handle object/array types first
- For unknown types, attempt JSON parsing before falling back to string

Co-authored-by: Xinyu Lu <luxinyu221@mails.ucas.ac.cn>
Signed-off-by: Taosong Fang <constfrost@foxmail.com>
Signed-off-by: 10053 <1005394403@qq.com>
@fangtaosong

Copy link
Copy Markdown
Author

@fangtaosong

Copy link
Copy Markdown
Author

With this fix, it is now possible to successfully complete complex, multi-turn code editing tasks (spanning dozens of rounds) in Claude Code using DeepSeek V3.2 deployed via vLLM.

if not tool_name or not request or not request.tools:
return "unknown"

for tool in request.tools:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change doesn’t seem to be compatible with the Responses API.
Could you help test it with the Responses API?

Signed-off-by: Taosong Fang <constfrost@foxmail.com>
@chaunceyjiang chaunceyjiang self-assigned this Dec 18, 2025
@chaunceyjiang

Copy link
Copy Markdown
Contributor

Hi @fangtaosong, sorry about this — I have a few other things to take care of over the next few days, and I’ll be able to review this PR after that.

@mergify mergify Bot added the bug Something isn't working label Jan 14, 2026
@fangtaosong

Copy link
Copy Markdown
Author

Hi @chaunceyjiang, checking in on this PR. It's been a few weeks. Could you please take a look at this bugfix for DeepSeekV3.2 tool parsing when you have a moment? Please let me know if you need any updates from my side.

@chichidd

Copy link
Copy Markdown

I'm encountering the same issue in array tool parse. Any updates on this PR?

@fangtaosong

Copy link
Copy Markdown
Author

I'm encountering the same issue in array tool parse. Any updates on this PR?

It doesn't seem so. Can the above changes resolve your issue?

@fangtaosong

Copy link
Copy Markdown
Author

Gentle bump @chaunceyjiang, any chance to review this soon?

@chaunceyjiang

Copy link
Copy Markdown
Contributor

I’m currently addressing another high-priority issue with dsv32 tool calls. After that’s done, I’ll come back and take a look at your PR. @fangtaosong

@sfeng33 sfeng33 left a comment •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the work, the PR does fix the bug where the array/object parameters were double encoded as strings.

However, it introduces a regression - _get_param_type assumes schema["type"] is always a string, but JSON Schema allows type arrays (e.g., {"type": ["integer", "null"]}). In that case it returns a list, and _convert_param_value calls .lower() on it → AttributeError crash. The old code wouldn't hit this because it never called _get_param_type.

I updated the PR back to your queue for changes.

@sfeng33

sfeng33 commented Mar 11, 2026

Copy link
Copy Markdown
Member

A note - the minimax m2 parser has more comprehensive schema handling implemented, while it goes beyond the scope of bug listed in the PR, it's worth aligning with that approach, which will include coverage on anyOf/oneOf/allOf handling, enum handling, etc.

@sfeng33

sfeng33 commented May 20, 2026

Copy link
Copy Markdown
Member

Closing as fixed in #43019

@sfeng33 sfeng33 closed this May 20, 2026
@mergify mergify Bot added the tool-calling label May 20, 2026
alexeldeib added a commit to alexeldeib/vllm that referenced this pull request May 20, 2026
Add parser-level regression coverage for DeepSeek V3.2/V4 DSML tool arguments whose JSON Schema types are expressed through composed schemas such as anyOf and allOf. These cases cover the customer-observed wait object and JSON Patch array shapes that previously surfaced as JSON strings when the parser only read direct parameter type fields.

Current vLLM main already contains the source behavior through vllm-project#43019, which replaced the DeepSeek parser's private conversion path with shared schema helpers. This branch is therefore intentionally scoped to coverage that locks the composed-schema behavior on the V3.2 superclass path and the V4 parser alias path.

Why this is not duplicating existing upstream work: vllm-project#43019 fixed the parser behavior and added broader coercion tests, but did not include the customer-shaped composed-schema parser regression cases covered here. Earlier related work (vllm-project#30797, vllm-project#41531, vllm-project#42879) was adjacent but did not fully cover this semantic case.

Tests: PYTHONPATH=/Users/aeldeib/code/inference/vllm-dsv4-composed-tool-args-ace /Users/aeldeib/code/inference/vllm/.venv/bin/python -m pytest tests/tool_parsers/test_deepseekv32_tool_parser.py tests/tool_parsers/test_deepseekv4_tool_parser.py -q -k composed_schema -> 4 passed, 50 deselected. uvx ruff check tests/tool_parsers/test_deepseekv32_tool_parser.py tests/tool_parsers/test_deepseekv4_tool_parser.py vllm/tool_parsers/deepseekv32_tool_parser.py -> passed. uvx ruff format --check tests/tool_parsers/test_deepseekv32_tool_parser.py tests/tool_parsers/test_deepseekv4_tool_parser.py vllm/tool_parsers/deepseekv32_tool_parser.py -> passed.

AI assistance was used to investigate production traces, search related vLLM work, and prepare this coverage. A human submitter must review and defend every changed line before opening an upstream PR.

Co-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Ace Eldeib <aeldeib@coreweave.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working deepseek Related to DeepSeek models tool-calling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants