Skip to content

[Bugfix][Frontend] Parse DeepSeek-V3 tool calls with multi-line JSON args - #60361

Closed
shreyanshj10 wants to merge 1 commit into
vllm-project:mainfrom
shreyanshj10:fix/issue-60342-deepseekv3-multiline-json
Closed

shreyanshj10 wants to merge 1 commit into
vllm-project:mainfrom
shreyanshj10:fix/issue-60342-deepseekv3-multiline-json

Conversation

@shreyanshj10

Copy link
Copy Markdown
Contributor

Overview

DeepSeekV3ToolParser loses a tool call when the model pretty-prints the JSON
arguments over more than one line. Fixes #60342.

Claims

  • Non-streaming: extract_tool_calls returns the call, instead of
    tools_called=True with an empty tool_calls list.
  • Streaming: the argument delta accumulates the whole object, instead of stopping
    at the first newline.
  • Parallel tool calls stay separate calls, single-line or indented.

Validation

Both regexes live in __init__, so I drove the reproducer from #60342 through the
patterns themselves against a get_weather block whose body is
json.dumps({"city": "Tokyo", "unit": "celsius"}, indent=2):

before after
tool_call_regex.findall 0 matches 1 match, args {"city": "Tokyo", "unit": "celsius"}
stream_tool_call_portion_regex arguments group '{' '{\n "city": "Tokyo",\n "unit": "celsius"\n}'

Single-line arguments are unchanged before and after. For the two-call output, the
non-streaming regex yields two matches with their own arguments both indented and
not, which is the case a plain re.DOTALL would break.

That case is now pinned by test_pretty_printed_arguments in
tests/tool_parsers/test_deepseekv3_tool_parser.py. It goes through the shared
ToolParserTests harness, so the existing streaming fixture runs it on both the
streaming and the non-streaming path:

pytest tests/tool_parsers/test_deepseekv3_tool_parser.py

Details

Root cause: both regexes capture the fenced JSON body with . and neither is
compiled with re.DOTALL, so the argument group cannot cross a newline. The
non-streaming pattern then fails to match at all, and extract_tool_calls still
reports tools_called=True because it only checked for the start token. The
streaming pattern does match, but its argument group ends at the first line break,
so only { is ever streamed.

I scoped [\s\S] to the two argument groups rather than compiling the patterns
with re.DOTALL, because type and function_name are greedy .*: under
DOTALL they run past the end of one tool call, and a response containing two
calls collapses into one match with the first call's markup swallowed into the
name. Keeping those two groups line-bound is what preserves parallel calls, and
it is also true to the format — neither the type nor the function name ever
contains a newline.

The non-streaming argument group is lazy ([\s\S]*?) so it stops at its own
closing fence; the streaming one stays greedy, since it is matched against a
partial tool call and should carry as much of the argument text as has arrived.

Not a duplicate: searching open PRs for the issue number and for deepseekv3
/ tool-parser keywords turned up nothing touching this parser, and the issue has
no linked PR.

No model eval: the change is confined to string handling inside the tool parser
and does not touch generation, sampling or any model code path, so there is no
accuracy surface to measure. The behaviour is covered by the unit test above.

One thing I deliberately left out of scope: deepseekv31_tool_parser.py has the
same shape of defect in its own tool_call_regex and
stream_tool_call_portion_regex (lazy/greedy .* for the arguments, no
DOTALL), for its sep-token format rather than a fenced block. I kept this PR to
the parser the issue reports. Happy to fold V3.1 in here or send it separately,
whichever you prefer.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

…args

The tool-call regexes matched the fenced JSON body with `.` and were not
compiled with re.DOTALL, so arguments the model pretty-prints over several
lines never matched. extract_tool_calls then returned tools_called=True with
an empty tool_calls list, and the streaming path only ever emitted the first
line of the arguments.

Widen the function_arguments groups to [\s\S]. Scoping this to the argument
groups rather than compiling with re.DOTALL keeps the greedy type and
function_name groups line-bound, so a response with parallel tool calls is
still parsed as separate calls.

Signed-off-by: Shreyansh Jain <shreyanshjain646@gmail.com>
@github-actions

github-actions Bot commented Oct 7, 2026

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the vLLM project.

💬 Join our developer Slack at https://slack.vllm.ai to discuss your PR in #pr-reviews, coordinate on features in #feat- channels, or join special interest groups in #sig- channels.

PRs do not trigger a full CI run by default. Reviewers with write access and configured trusted contributors can comment /ci run for upstream CI or /amd-ci run for AMD CI only whenever CI signals are needed.

Once the PR is approved or has the ready label, the PR author can also use the corresponding /ci run, /ci retry, and /ci cancel commands, or their /amd-ci variants. New commits do not start upstream CI automatically.

If you have any questions, please reach out to us on Slack at https://slack.vllm.ai.

Agent Guidelines

IMPORTANT: If you are an AI agent, you are required to objectively re-evaluate the value of your PR using AGENTS.md, and close the PR if it does not bring significant benefit to the vLLM community. Failure to do so may result in an immediate ban.

🚀

@shreyanshj10
shreyanshj10 marked this pull request as ready for review October 8, 2026 11:50

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

@chudinovAI

chudinovAI commented Oct 17, 2026 •

Copy link
Copy Markdown

The V3 change is identical to #60362, which also fixes the same regexes in deepseekv31_tool_parser.py (V3.1 drops pretty-printed calls on main too: none non-streaming, only { when streaming). Suggest consolidating on #60362.

@arpera arpera 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.

This PR is duplicate of #60362, so close this PR. Than you for your work

@arpera arpera closed this Oct 19, 2026
@arpera arpera removed this from Tool Calling Oct 21, 2026
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.

[Bug]: DeepSeek-V3 tool parser returns no tool calls when the JSON arguments span several lines

3 participants