Skip to content

[Bug] Preserve DeepSeek-V3.2 tool-call markers in reasoning parsing - #21179

Open
robellliu-dev wants to merge 2 commits into
sgl-project:mainfrom
robellliu-dev:codex/deepseekv32-pd-tool-calls
Open

[Bug] Preserve DeepSeek-V3.2 tool-call markers in reasoning parsing#21179
robellliu-dev wants to merge 2 commits into
sgl-project:mainfrom
robellliu-dev:codex/deepseekv32-pd-tool-calls

Conversation

@robellliu-dev

Copy link
Copy Markdown
Contributor

Summary

  • preserve DeepSeek-V3.2 DSML tool-call markers when deepseek-v3 reasoning output switches into tool calls
  • add DeepSeek-V3-specific reasoning detector coverage for both non-streaming and streaming parsing
  • keep the downstream DeepSeek-V3.2 function-call parser on the existing raw marker path

Root Cause

deepseek-v3 currently reuses Qwen3Detector, which only recognizes <think> / </think> transitions. DeepSeek-V3.2 tool calls can start with the DSML <function_calls> marker before </think> is emitted. In that case the reasoning parser keeps treating the DSML marker as reasoning text instead of handing it off to the function-call parser, which can surface as malformed tool-call deltas or empty tool output in PD mode.

Fix

Introduce a DeepSeek-V3-specific reasoning detector that uses the DeepSeek-V3.2 DSML function-call start marker as a reasoning interruption token. This preserves the raw tool-call marker in normal text so the existing DeepSeek-V3.2 tool-call parser can consume it unchanged.

Testing

  • python -m py_compile python/sglang/srt/parser/reasoning_parser.py test/registered/unit/parser/test_reasoning_parser.py
  • DeepSeek-V3 parser smoke test covering non-stream and stream interruption paths
  • python test/registered/unit/parser/test_reasoning_parser.py (blocked locally because this environment is missing Pillow, which is imported by the repo test utilities)

Fixes #21176

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6302c18ae0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

"</think>",
force_reasoning=force_reasoning,
stream_reasoning=stream_reasoning,
tool_start_token=f"<{dsml_token}function_calls>",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle newline-prefixed DSML markers in streaming reasoning

DeepSeekV3Detector only registers tool_start_token as the bare <|DSML|function_calls> tag, but BaseReasoningFormatDetector.parse_streaming_increment() buffers partial matches only when the entire buffer is a prefix of that exact token. DeepSeek-V3.2’s own formatter/parser expects tool calls to start with \n\n<|DSML|function_calls (encoding_dsv32.py), so if streaming splits at a common boundary like "\n\n<" / "|DSML|function_calls>", the first chunk is emitted as reasoning_content and the raw marker never reaches the tool-call parser. In practice this leaves the PD-mode streaming fix incomplete for normal DSML-formatted tool calls.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. I updated the DeepSeek-V3 reasoning detector to handle both the bare DSML marker and the formatted \n\n<...function_calls> marker that DeepSeek-V3.2 emits.

I also tightened the streaming parser so that if a chunk ends with a partial tool-call prefix (for example reasoning\n\n<), that suffix stays buffered instead of being emitted as reasoning content. This preserves the full raw DSML marker for the downstream tool-call parser once the next chunk arrives.

I added regression coverage for both:

  • direct DeepSeek-V3.2 DSML interruption
  • the formatted streaming split case with "\n\n<" followed by the rest of the marker

@hnyls2002

Copy link
Copy Markdown
Collaborator

Heads-up: #34458 (5899674) landed and covers part of what this PR does — reasoning is now interrupted at tool_start_token, and a partial tool marker split across a chunk boundary is held back instead of being emitted.

What it does not cover is the part that is specific to this PR: tool_start_token is still typed Optional[str] on main, so a detector cannot declare more than one tool-start marker. Keeping this open for that.

Could you rebase on main and drop the parts that are now redundant, leaving the Union[str, Tuple[str, ...]] support? Sorry this sat for so long — it was the earliest report of the routing problem.

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.

[Bug] DeepSeek-V3.2 tool-call parsing exception or empty output in Disaggregated (PD) mode with SGLang 0.5.9

2 participants