[Bugfix] Align deepseek v4 parser thinking default with tokenizer (#51296)

Signed-off-by: sfeng33 <4florafeng@gmail.com>
This commit is contained in:
Flora Feng
2026-08-10 19:30:30 +00:00
committed by GitHub
parent d40c3e3c00
commit 3f142bd85e
3 changed files with 57 additions and 12 deletions
+51 -8
View File
@@ -243,15 +243,29 @@ class TestThinkingModeConfig:
cfg = deepseek_v4_config(thinking=False)
assert cfg.initial_state.name == "CONTENT"
def test_enable_thinking_kwarg(self, mock_tokenizer):
p = DeepSeekV4Parser(
mock_tokenizer, chat_template_kwargs={"enable_thinking": True}
@pytest.mark.parametrize(
("chat_template_kwargs", "expected_state"),
[
({}, "REASONING"),
({"thinking": True}, "REASONING"),
({"enable_thinking": True}, "REASONING"),
({"reasoning_effort": "high"}, "REASONING"),
({"thinking": False}, "CONTENT"),
({"enable_thinking": False}, "CONTENT"),
(
{"enable_thinking": True, "reasoning_effort": "none"},
"CONTENT",
),
],
)
def test_parser_thinking_mode_matches_tokenizer_default(
self, mock_tokenizer, chat_template_kwargs, expected_state
):
parser = DeepSeekV4Parser(
mock_tokenizer,
chat_template_kwargs=chat_template_kwargs,
)
assert p.parser_engine_config.initial_state.name == "REASONING"
def test_no_thinking_kwarg_defaults_to_content(self, mock_tokenizer):
p = DeepSeekV4Parser(mock_tokenizer)
assert p.parser_engine_config.initial_state.name == "CONTENT"
assert parser.parser_engine_config.initial_state.name == expected_state
def test_thinking_mode_reasoning_without_tags(self, mock_tokenizer):
parser = DeepSeekV4Parser(
@@ -838,6 +852,35 @@ class TestDelegatingParserLargeDelta:
args = json.loads(output.tool_calls[0]["arguments"])
assert args == {"location": "Berlin", "units": "celsius"}
def test_default_thinking_extracts_tool_call_without_think_end(self, dsv4_tokens):
tokens = [
token
for token in dsv4_tokens
if token[0] != _DSV4_FULL_VOCAB[DSML_THINK_END]
]
tokenizer = MockTokenizer(
vocab=dict(_DSV4_FULL_VOCAB),
tokens=tokens,
)
parser = _DeepSeekV4Delegating(tokenizer)
deltas = replay_streaming(
parser,
tokens,
chunk_size=1,
finished_on_last=True,
tools=DUMMY_TOOLS,
prompt_token_ids=[_DSV4_FULL_VOCAB[DSML_THINK_START]],
)
output = collect_output(deltas)
assert "The user wants" in output.reasoning
assert output.content == ""
assert len(output.tool_calls) == 1
assert output.tool_calls[0]["name"] == "get_weather"
args = json.loads(output.tool_calls[0]["arguments"])
assert args == {"location": "Berlin", "units": "celsius"}
def test_eos_drop_token_does_not_swallow_tool_calls(self):
"""Tool calls must survive when an EOS DROP token's ID is in
delta_token_ids but its text is absent from delta_text.
+1 -1
View File
@@ -722,7 +722,7 @@ def _dsv4_segments(scenario: Scenario, thinking: bool) -> list[tuple[str, bool]]
def _build_deepseek_v4(scenario: Scenario, validate: bool = True) -> Sample:
thinking = scenario.reasoning is not None
chat_kwargs = {"thinking": True} if thinking else None
chat_kwargs = {"thinking": thinking}
if thinking:
expected_reasoning: str | None = scenario.reasoning or ""
+5 -3
View File
@@ -217,10 +217,12 @@ class DeepSeekV4Parser(ParserEngine):
**kwargs,
) -> None:
chat_kwargs = kwargs.pop("chat_template_kwargs", None) or {}
thinking = (
bool(chat_kwargs.get("thinking") or chat_kwargs.get("enable_thinking"))
and chat_kwargs.get("reasoning_effort") != "none"
thinking = bool(
chat_kwargs.get("thinking") or chat_kwargs.get("enable_thinking")
)
if "thinking" not in chat_kwargs and "enable_thinking" not in chat_kwargs:
thinking = True
thinking = thinking and chat_kwargs.get("reasoning_effort") != "none"
super().__init__(
tokenizer,
tools,