# PR #32400 完整报告

- 仓库：`sgl-project/sglang`
- 标题：fix(reasoning): let --enable-strict-thinking works for DeepSeek-V4
- 合并时间：2026-07-27 18:15
- 原文链接：http://prhub.com.cn/sgl-project/sglang/pull/32400

---

# 执行摘要

- 一句话：修复 DeepSeek-V4 严格思考模式无法正确屏蔽 EOS
- 推荐动作：建议合并。该 PR 修复了 DeepSeek-V4 的一个关键 bug，代码简洁、测试完善。值得关注的是 `DeepSeekV4Detector` 的设计模式：直接继承 `BaseReasoningFormatDetector` 而非复用其他模型的 detector，避免了 token 不兼容问题，这是处理特殊 tokenizer 模型的正确做法。

# 功能与动机

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 中明确描述了此现象。

# 实现拆解

1. **导入 DeepSeek-V4 专用 token 常量**：在 `reasoning_parser.py` 头部新增从 `sglang.srt.entrypoints.openai.encoding_dsv4` 导入 `dsml_token`、`eos_token`、`thinking_start_token`、`thinking_end_token`，并重命名为 `dsv4_*` 前缀。
2. **新增 DeepSeekV4Detector 类**：直接继承 `BaseReasoningFormatDetector`，而不是 `_DeepSeekV3Detector` 或 `Qwen3Detector`。其 `__init__` 使用正确的 `dsv4_thinking_start_token` 和 `dsv4_thinking_end_token`，并将 `think_excluded_tokens` 设置为仅包含 `[dsv4_eos_token, dsv4_dsml_token]`，同时保持 `thinks_internally=True` 和 `reasoning_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_internally` 为 `True`。

关键文件：
- `python/sglang/srt/parser/reasoning_parser.py`（模块 推理解析；类别 source；类型 core-logic；符号 DeepSeekV4Detector, __init__）: 核心变更文件：新增 DeepSeekV4Detector 类，更新 DetectorMap 映射，添加专用 token 导入。
- `test/registered/unit/parser/test_reasoning_parser.py`（模块 测试；类别 test；类型 test-coverage；符号 TestDeepSeekV4Detector, test_strict_thinking_excludes_deepseek_control_tokens, test_thinking_stays_explicit_opt_in）: 测试文件：新增 TestDeepSeekV4Detector，验证 detector 的 token 排除和配置行为。

关键符号：DeepSeekV4Detector.__init__

## 关键源码片段

### `python/sglang/srt/parser/reasoning_parser.py`

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

```python
# 新增 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`

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

```python
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)

```

# 评论区精华

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

- 暂无高价值评论线程

# 风险与影响

- 风险：风险较低。变更仅限于 DeepSeek-V4 模型的推理解析器，不影响其他模型。核心改动是新增一个 detector 类并更新映射，逻辑独立且简单。思考排除 token 的变更缩小了范围，减少了误屏蔽的可能性。测试覆盖了关键行为。
- 影响：直接影响：使用 DeepSeek-V4 模型且启用 `--enable-strict-thinking` 的用户，思考过程将正确屏蔽 EOS token，避免思考中途提前结束。间接影响：不影响其他模型或未启用 strict thinking 的场景。影响程度中等，主要针对 DeepSeek-V4 的生产流量。
- 风险标记：核心路径变更 , 缺少测试覆盖（但已补充）

# 关联脉络

- PR #31793 [Fix][AMD] Qwen3.5 MoE: disable global-slot shared-expert fusion under per-rank EP backends: 同样涉及推理解析器（reasoning_parser.py）中 detector 的调整，展示了模型特定 detector 的修改模式。