From 1d350aaad3511920616a5cd16fbe413166b34a88 Mon Sep 17 00:00:00 2001 From: Shangming Cai Date: Mon, 27 Jul 2026 18:15:47 +0800 Subject: [PATCH] fix(reasoning): let --enable-strict-thinking works for DeepSeek-V4 (#32400) Co-authored-by: Claude Fable 5 Co-authored-by: Xinyuan Tong --- python/sglang/srt/parser/reasoning_parser.py | 33 ++++++++++++++++++- .../unit/parser/test_reasoning_parser.py | 16 +++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/python/sglang/srt/parser/reasoning_parser.py b/python/sglang/srt/parser/reasoning_parser.py index 838f40005..3013e371a 100644 --- a/python/sglang/srt/parser/reasoning_parser.py +++ b/python/sglang/srt/parser/reasoning_parser.py @@ -2,6 +2,14 @@ import inspect import re from typing import Dict, List, Optional, Tuple, Type +from sglang.srt.entrypoints.openai.encoding_dsv4 import dsml_token as dsv4_dsml_token +from sglang.srt.entrypoints.openai.encoding_dsv4 import eos_token as dsv4_eos_token +from sglang.srt.entrypoints.openai.encoding_dsv4 import ( + thinking_end_token as dsv4_thinking_end_token, +) +from sglang.srt.entrypoints.openai.encoding_dsv4 import ( + thinking_start_token as dsv4_thinking_start_token, +) from sglang.srt.entrypoints.openai.protocol import ChatCompletionRequest from sglang.srt.function_call.hunyuan_detector import resolve_hunyuan_tokens from sglang.srt.parser.harmony_parser import HarmonyParser @@ -897,6 +905,29 @@ class _DeepSeekV3Detector(Qwen3Detector): self.reasoning_default = "explicit_thinking" +class DeepSeekV4Detector(BaseReasoningFormatDetector): + def __init__( + self, + stream_reasoning: bool = True, + force_reasoning: bool = False, + continue_final_message: bool = False, + previous_content: str = "", + force_nonempty_content: bool = False, + ): + super().__init__( + dsv4_thinking_start_token, + dsv4_thinking_end_token, + think_excluded_tokens=[dsv4_eos_token, dsv4_dsml_token], + force_reasoning=force_reasoning, + stream_reasoning=stream_reasoning, + continue_final_message=continue_final_message, + previous_content=previous_content, + thinks_internally=True, + reasoning_default="explicit_thinking", + force_nonempty_content=force_nonempty_content, + ) + + class _MimoDetector(Qwen3Detector): """MIMO reuses Qwen3 tokens but requires explicit enable_thinking=True to enable.""" @@ -1385,7 +1416,7 @@ class ReasoningParser: "apertus2509": Apertus2509Detector, "deepseek-r1": DeepSeekR1Detector, "deepseek-v3": _DeepSeekV3Detector, - "deepseek-v4": _DeepSeekV3Detector, + "deepseek-v4": DeepSeekV4Detector, "glm45": Glm45Detector, "hunyuan": HunyuanDetector, "gpt-oss": GptOssDetector, diff --git a/test/registered/unit/parser/test_reasoning_parser.py b/test/registered/unit/parser/test_reasoning_parser.py index e5616b27f..96117ec06 100644 --- a/test/registered/unit/parser/test_reasoning_parser.py +++ b/test/registered/unit/parser/test_reasoning_parser.py @@ -6,6 +6,7 @@ from sglang.srt.parser.reasoning_parser import ( Apertus2509Detector, BaseReasoningFormatDetector, DeepSeekR1Detector, + DeepSeekV4Detector, Gemma4Detector, Glm45Detector, HunyuanDetector, @@ -167,6 +168,21 @@ class TestQwen3Detector(CustomTestCase): self.assertEqual(result.reasoning_text, "") +class TestDeepSeekV4Detector(CustomTestCase): + def test_strict_thinking_excludes_deepseek_control_tokens(self): + detector = ReasoningParser(model_type="deepseek-v4").detector + self.assertIsInstance(detector, DeepSeekV4Detector) + self.assertEqual( + detector.think_excluded_tokens, + ["<|end▁of▁sentence|>", "|DSML|"], + ) + + def test_thinking_stays_explicit_opt_in(self): + detector = ReasoningParser(model_type="deepseek-v4").detector + self.assertEqual(detector.reasoning_default, "explicit_thinking") + self.assertTrue(detector.thinks_internally) + + class TestInklingDetector(CustomTestCase): def test_streaming_routes_blocks_across_all_string_boundaries(self): detector = InklingDetector()