[Bugfix] Align deepseek v4 parser thinking default with tokenizer (#51296)
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Flora Feng committed
Aug 10, 2026 at 15:30 UTC
3f142bd85e962a795c6c484f0249f9c92aed37b2
3 files changed
+57
-12
tests/parser/engine/test_deepseek_v4.py
+51
-8
@@ -243,15 +243,29 @@ class TestThinkingModeConfig:
243
cfg = deepseek_v4_config(thinking=False)
244
assert cfg.initial_state.name == "CONTENT"
245
246
- def test_enable_thinking_kwarg(self, mock_tokenizer):
247
- p = DeepSeekV4Parser(
248
- mock_tokenizer, chat_template_kwargs={"enable_thinking": True}
246
+ @pytest.mark.parametrize(
247
+ ("chat_template_kwargs", "expected_state"),
248
+ [
249
+ ({}, "REASONING"),
250
+ ({"thinking": True}, "REASONING"),
251
+ ({"enable_thinking": True}, "REASONING"),
252
+ ({"reasoning_effort": "high"}, "REASONING"),
253
+ ({"thinking": False}, "CONTENT"),
254
+ ({"enable_thinking": False}, "CONTENT"),
255
+ (
256
+ {"enable_thinking": True, "reasoning_effort": "none"},
257
+ "CONTENT",
258
+ ),
259
+ ],
260
+ )
261
+ def test_parser_thinking_mode_matches_tokenizer_default(
262
+ self, mock_tokenizer, chat_template_kwargs, expected_state
263
+ ):
264
+ parser = DeepSeekV4Parser(
265
+ mock_tokenizer,
266
+ chat_template_kwargs=chat_template_kwargs,
267
)
250
- assert p.parser_engine_config.initial_state.name == "REASONING"
251
-
252
- def test_no_thinking_kwarg_defaults_to_content(self, mock_tokenizer):
253
- p = DeepSeekV4Parser(mock_tokenizer)
254
- assert p.parser_engine_config.initial_state.name == "CONTENT"
268
+ assert parser.parser_engine_config.initial_state.name == expected_state
269
270
def test_thinking_mode_reasoning_without_tags(self, mock_tokenizer):
271
parser = DeepSeekV4Parser(
@@ -838,6 +852,35 @@ class TestDelegatingParserLargeDelta:
852
args = json.loads(output.tool_calls[0]["arguments"])
853
assert args == {"location": "Berlin", "units": "celsius"}
854
855
+ def test_default_thinking_extracts_tool_call_without_think_end(self, dsv4_tokens):
856
+ tokens = [
857
+ token
858
+ for token in dsv4_tokens
859
+ if token[0] != _DSV4_FULL_VOCAB[DSML_THINK_END]
860
+ ]
861
+ tokenizer = MockTokenizer(
862
+ vocab=dict(_DSV4_FULL_VOCAB),
863
+ tokens=tokens,
864
+ )
865
+ parser = _DeepSeekV4Delegating(tokenizer)
866
+
867
+ deltas = replay_streaming(
868
+ parser,
869
+ tokens,
870
+ chunk_size=1,
871
+ finished_on_last=True,
872
+ tools=DUMMY_TOOLS,
873
+ prompt_token_ids=[_DSV4_FULL_VOCAB[DSML_THINK_START]],
874
+ )
875
+ output = collect_output(deltas)
876
+
877
+ assert "The user wants" in output.reasoning
878
+ assert output.content == ""
879
+ assert len(output.tool_calls) == 1
880
+ assert output.tool_calls[0]["name"] == "get_weather"
881
+ args = json.loads(output.tool_calls[0]["arguments"])
882
+ assert args == {"location": "Berlin", "units": "celsius"}
883
+
884
def test_eos_drop_token_does_not_swallow_tool_calls(self):
885
"""Tool calls must survive when an EOS DROP token's ID is in
886
delta_token_ids but its text is absent from delta_text.
tests/parser/engine/trace_builder.py
+1
-1
@@ -722,7 +722,7 @@ def _dsv4_segments(scenario: Scenario, thinking: bool) -> list[tuple[str, bool]]
722
723
def _build_deepseek_v4(scenario: Scenario, validate: bool = True) -> Sample:
724
thinking = scenario.reasoning is not None
725
- chat_kwargs = {"thinking": True} if thinking else None
725
+ chat_kwargs = {"thinking": thinking}
726
727
if thinking:
728
expected_reasoning: str | None = scenario.reasoning or ""
vllm/parser/deepseek_v4.py
+5
-3
@@ -217,10 +217,12 @@ class DeepSeekV4Parser(ParserEngine):
217
**kwargs,
218
) -> None:
219
chat_kwargs = kwargs.pop("chat_template_kwargs", None) or {}
220
- thinking = (
221
- bool(chat_kwargs.get("thinking") or chat_kwargs.get("enable_thinking"))
222
- and chat_kwargs.get("reasoning_effort") != "none"
220
+ thinking = bool(
221
+ chat_kwargs.get("thinking") or chat_kwargs.get("enable_thinking")
222
)
223
+ if "thinking" not in chat_kwargs and "enable_thinking" not in chat_kwargs:
224
+ thinking = True
225
+ thinking = thinking and chat_kwargs.get("reasoning_effort") != "none"
226
super().__init__(
227
tokenizer,
228
tools,