Prhub

#32400 fix(reasoning): let --enable-strict-thinking works for DeepSeek-V4

原始 PR 作者 ShangmingCai 合并时间 2026-07-27 18:15 文件变更 2 提交数 3 评论 3 代码增减 +48 / -1

执行摘要

修复 DeepSeek-V4 严格思考模式无法正确屏蔽 EOS

DeepSeek-V4 模型使用 DeepSeek 专用的 tokenizer,其 EOS token(id 1)与 Qwen3 的 <|im_end|> 等不同。现有 _DeepSeekV3Detector 继承自 Qwen3Detector,其 think_excluded_tokens 包含 Qwen3 的 control tokens,在 DeepSeek tokenizer 下编码为常见片段,无法有效屏蔽 EOS。导致启用 --enable-strict-thinking 时,模型约 10% 的请求在思考中途采样到 EOS,输出 empty content 且无 tool_call。PR body 中明确描述了此现象。

建议合并。该 PR 修复了 DeepSeek-V4 的一个关键 bug,代码简洁、测试完善。值得关注的是 DeepSeekV4Detector 的设计模式:直接继承 BaseReasoningFormatDetector 而非复用其他模型的 detector,避免了 token 不兼容问题,这是处理特殊 tokenizer 模型的正确做法。

讨论亮点

该 PR 的 review 评论较少,仅有一条来自 JustinTong0323 的 "LGTM"。提交历史中,第一个 commit 由 ShangmingCai 完成核心逻辑(新增 Detector),第二个 commit 由 JustinTong0323 补充将 DSML token 加入排除列表。在 CI 中出现了一次失败,但经过重新触发后最终通过(见评论中 ShangmingCai 的截图)。没有公开的设计争议。

实现拆解

  1. 导入 DeepSeek-V4 专用 token 常量:在 reasoning_parser.py 头部新增从 sglang.srt.entrypoints.openai.encoding_dsv4 导入 dsml_tokeneos_tokenthinking_start_tokenthinking_end_token,并重命名为 dsv4_* 前缀。
  2. 新增 DeepSeekV4Detector 类:直接继承 BaseReasoningFormatDetector,而不是 _DeepSeekV3DetectorQwen3Detector。其 __init__ 使用正确的 dsv4_thinking_start_tokendsv4_thinking_end_token,并将 think_excluded_tokens 设置为仅包含 [dsv4_eos_token, dsv4_dsml_token],同时保持 thinks_internally=Truereasoning_default="explicit_thinking"
  3. 更新 DetectorMap:将 "deepseek-v4" 的映射从 _DeepSeekV3Detector 改为 DeepSeekV4Detector
  4. 添加单元测试:在 test_reasoning_parser.py 中新增 TestDeepSeekV4Detector 测试类,包含两个测试用例:test_strict_thinking_excludes_deepseek_control_tokens 验证 think_excluded_tokens 为预期的 ["<|end▁of▁sentence|>", "|DSML|"]test_thinking_stays_explicit_opt_in 验证 reasoning_default"explicit_thinking"thinks_internallyTrue
文件 模块 状态 重要度
python/sglang/srt/parser/reasoning_parser.py 推理解析 modified 7.38
test/registered/unit/parser/test_reasoning_parser.py 测试 modified 6.11

关键符号

DeepSeekV4Detector.__init__

关键源码片段

python/sglang/srt/parser/reasoning_parser.py core-logic

核心变更文件:新增 DeepSeekV4Detector 类,更新 DetectorMap 映射,添加专用 token 导入。

# 新增 DeepSeek-V4 专用 token 导入,避免复用 Qwen3 的 token 定义
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,
)# ... 中间代码不变 ...class DeepSeekV4Detector(BaseReasoningFormatDetector):
    """
    DeepSeek-V4 专用推理检测器。
    直接继承 BaseReasoningFormatDetector 而非复用 Qwen3Detector,
    因为 DeepSeek 的 tokenizer 将常见短语编码为多个片段,
    导致 Qwen3 的 think_excluded_tokens 无法正确屏蔽 EOS。
    """
    def __init__(
        self,
        stream_reasoning: bool = True,
        force_reasoning: bool = False,
        continue_final_message: bool = False,
        previous_content: str = "",
        force_nonempty_content: bool = False,
    ):
        # 使用 DeepSeek-V4 特有的 thinking start/end token
        super().__init__(
            dsv4_thinking_start_token, # "<|begin▁of▁sentence|>" 的 think start 变体
            dsv4_thinking_end_token, # "<|end▁of▁sentence|>" 的 think end 变体
            # 严格思考模式下仅排除 EOS (id 1) 和 DSML token,
            # 不再包含 Qwen3 的 <tool_call>、<|im_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, # DeepSeek-V4 内部自动生成 <think> 标记
            reasoning_default="explicit_thinking", # 思考为 opt-in 模式
            force_nonempty_content=force_nonempty_content,
        )# ... 在 DetectorMap 中将映射从 _DeepSeekV3Detector 改为 DeepSeekV4Detector ...
"deepseek-v4": DeepSeekV4Detector,
test/registered/unit/parser/test_reasoning_parser.py test-coverage

测试文件:新增 TestDeepSeekV4Detector,验证 detector 的 token 排除和配置行为。

class TestDeepSeekV4Detector(CustomTestCase):
    def test_strict_thinking_excludes_deepseek_control_tokens(self):
        # 通过 ReasoningParser 使用模型类型 deepseek-v4 创建 detector
        detector = ReasoningParser(model_type="deepseek-v4").detector
        self.assertIsInstance(detector, DeepSeekV4Detector)
        # 验证严格思考模式下仅排除 EOS 和 DSML 两个 token
        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
        # 确保 reasoning_default 为 explicit_thinking ( 默认为关闭思考 )
        self.assertEqual(detector.reasoning_default, "explicit_thinking")
        # 确保 thinks_internally 为 True
        self.assertTrue(detector.thinks_internally)

评论区精华

没有提炼出高价值讨论线程

当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。

风险与影响

风险较低。变更仅限于 DeepSeek-V4 模型的推理解析器,不影响其他模型。核心改动是新增一个 detector 类并更新映射,逻辑独立且简单。思考排除 token 的变更缩小了范围,减少了误屏蔽的可能性。测试覆盖了关键行为。

直接影响:使用 DeepSeek-V4 模型且启用 --enable-strict-thinking 的用户,思考过程将正确屏蔽 EOS token,避免思考中途提前结束。间接影响:不影响其他模型或未启用 strict thinking 的场景。影响程度中等,主要针对 DeepSeek-V4 的生产流量。

核心路径变更 缺少测试覆盖(但已补充)

关联 Issue

未识别关联 Issue

当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。

完整报告

参与讨论