Prhub

#29920 feat(parser): resolve special-token suffix at runtime for compatibility

原始 PR 作者 JustinTong0323 合并时间 2026-07-05 00:13 文件变更 10 提交数 4 评论 9 代码增减 +174 / -38

执行摘要

运行时解析 Hunyuan 特殊 token 后缀以兼容带后缀的 tokenizer

Some tokenizers append a shared suffix to every special token (e.g. <tool_calls:TAG> instead of <tool_calls>). The hunyuan reasoning/tool-call detectors and the --tool-call-parser auto rule hard-coded the bare literals, so both parsing and auto-detection broke on such tokenizers. Resolving the real token strings from the vocab at runtime is preferable to hard-coding a per-model suffix.

推荐精读。此 PR 展示了一种灵活处理 tokenizer 后缀差异的模式:通过运行时词表解析替代硬编码,并通过 inspect.signature 实现条件参数注入,保持了模块的可扩展性。resolve_hunyuan_tokenshas_vocab_pattern 的设计值得在类似场景复用。同时,PR 在审核后及时修复了潜在崩溃和结构信息缺失问题,体现了良好的质量保障流程。

讨论亮点

Review 中 Gemini Code Assist 提出了两个改进建议:

  • resolve_hunyuan_tokens 中增加 isinstance(tok, str) 检查,防止词表中非字符串键(如 int/bytes)导致 TypeError。作者在第三个提交 03fee116 中已修复。
  • has_vocab_pattern 中预编译正则表达式并增加 isinstance 防护,避免重复编译开销和崩溃。最终版本采用 re.compileisinstance 检查,已在代码中落实。

PR 最终获得审核者 ispobock 的批准。

实现拆解

  1. 运行时 Token 解析函数:在 python/sglang/srt/function_call/hunyuan_detector.py 中新增 resolve_hunyuan_tokens(tokenizer) 函数。该函数通过 tokenizer.get_vocab() 获取词表,使用预编译的正则表达式 ^<name(:[^>]+)?>$ 匹配特殊 token(可能带后缀),返回 {name: real_token} 字典。未找到的 token 回退到裸文字形式。

  2. 检测器初始化适配HunyuanDetector.__init__ 新增 tokenizer=None 参数,调用 resolve_hunyuan_tokens 获取真实 token,并利用 _close 自动推导闭合 token。使用 re.escape 动态构建 tool_call_regexfunc_args_regex 等正则,确保流式解析使用正确的 token。

  3. 自动检测规则增强:在 python/sglang/srt/managers/template_detection.pyTemplateDetectionContext 新增 has_vocab_pattern(pattern) 方法,在词表中搜索匹配正则的 token。更新 _is_hunyuan 使用 has_vocab_pattern 匹配带后缀的 token(如 <tool_calls:opensource>),使 --tool-call-parser auto 能正确识别。

  4. 条件参数注入机制FunctionCallParser.__init__ReasoningParser.__init__ 新增 tokenizer=None 参数。实例化检测器时,通过 inspect.signature 检查其 __init__ 是否接受 tokenizer,仅在支持时传递。非 Hunyuan 检测器不受影响。

  5. 调用点串接:在 serving_responses.pyserving_chat.pyhttp_server.pyscheduler.pybase_grammar_backend.py 等约 16 处创建解析器实例的地方,传入 tokenizer=self.tokenizer_manager.tokenizer。测试辅助方法 reasoning_kit.py 同步更新。

文件 模块 状态 重要度
python/sglang/srt/function_call/hunyuan_detector.py 工具调用 modified 8.23
python/sglang/srt/managers/template_detection.py 模板检测 modified 6.52
python/sglang/srt/function_call/function_call_parser.py 函数解析 modified 6.45
python/sglang/srt/parser/reasoning_parser.py 推理解析 modified 6.45
python/sglang/srt/entrypoints/openai/serving_responses.py 响应处理 modified 5.95
python/sglang/srt/entrypoints/openai/serving_chat.py 聊天处理 modified 5.89
python/sglang/srt/entrypoints/http_server.py HTTP 服务 modified 5.67
python/sglang/srt/managers/scheduler.py 调度器 modified 5.46
python/sglang/srt/constrained/base_grammar_backend.py 语法后端 modified 5.11
python/sglang/test/kits/reasoning_kit.py 测试工具 modified 3.7

关键符号

resolve_hunyuan_tokens has_vocab_pattern HunyuanDetector.__init__ (function_call) HunyuanDetector.__init__ (reasoning_parser) FunctionCallParser.__init__ ReasoningParser.__init__

关键源码片段

python/sglang/srt/managers/template_detection.py core-logic

增强自动检测规则:添加 `has_vocab_pattern` 方法允许在词表中搜索正则模式;更新 `_is_hunyuan` 使用 `has_vocab_pattern` 匹配带后缀的 token 如 `<tool_calls:opensource>`。

@dataclass(frozen=True)
class TemplateDetectionContext:
    template: str
    reasoning_config: Optional["ReasoningToggleConfig"]
    force_reasoning: bool
    vocab: set[str]
​
    def has_text(self, needle: str) -> bool:
        return needle in self.template
​
    def has_vocab(self, token: str) -> bool:
        return token in self.vocab
​
    def has_pattern(self, pattern: str, flags: int = 0) -> bool:
        return re.search(pattern, self.template, flags) is not None
​
    def has_vocab_pattern(self, pattern: str) -> bool:
        """检查词表中是否有任何 token 匹配给定的正则模式。        在不确定 token 是否带后缀时使用,例如匹配 ``<tool_calls:opensource>``。
        """
        compiled = re.compile(pattern)
        return any(
            isinstance(tok, str) and compiled.search(tok)
            for tok in self.vocab
        )# ... 自动检测规则中的 Hunyuan 检测def _is_hunyuan(ctx):
    # shipping Hy3 tokenizer 会为每个特殊 token 追加共享后缀
    # ( 例如 ``<tool_calls:opensource>``),因此同时匹配裸文字和带后缀形式
    tc = ctx.has_text("<tool_calls>") or ctx.has_vocab_pattern(
        r"^<tool_calls(?::[^>]+)?>$"
    )
    sep = ctx.has_text("<tool_sep>") or ctx.has_vocab_pattern(
        r"^<tool_sep(?::[^>]+)?>$"
    )
    return (tc and sep) or (
        ctx.has_text("reasoning_effort") and ctx.has_text("interleaved_thinking")
    )
python/sglang/srt/function_call/function_call_parser.py core-logic

条件参数注入:`FunctionCallParser.__init__` 现在接受 `tokenizer` 参数,并通过 `inspect.signature` 检查检测器是否接受该参数,只在支持时传递,保持非 Hunyuan 检测器不受影响。

class FunctionCallParser:
    ToolCallParserEnum: Dict[str, Type[BaseFormatDetector]] = {
        # ... (many entries)
        "hunyuan": HunyuanDetector,
        # ...
    }
​
    def __init__(self, tools: List[Tool], tool_call_parser: str, tokenizer=None):
        detector_class = self.ToolCallParserEnum.get(tool_call_parser)
        if detector_class:
            # 仅当检测器的构造函数接受 tokenizer 参数时才传递,
            # 从而不干扰非 Hunyuan 检测器
            kwargs = {}
            if tokenizer is not None:
                sig = inspect.signature(detector_class)
                if "tokenizer" in sig.parameters:
                    kwargs["tokenizer"] = tokenizer
            detector = detector_class(**kwargs)
        else:
            raise ValueError(f"Unsupported tool_call_parser: {tool_call_parser}")
​
        self.detector = detector
        self.tools = tools
        self.tool_strict_level = envs.SGLANG_TOOL_STRICT_LEVEL.get()

评论区精华

resolve_hunyuan_tokens 中非字符串键的类型安全 正确性

词表可能包含 `int` 或 `bytes` 等非字符串键,直接调用 `_HUNYUAN_TOKEN_RE.match(tok)` 会触发 `TypeError`。建议增加 `isinstance(tok, str)` 检查。

结论:已在提交 `03fee116` 中修复,添加了 `if not isinstance(tok, str): continue`。 · 已解决

has_vocab_pattern 预编译与类型防护 性能

循环中每次调用 `re.search` 都重新编译正则,且缺少非字符串键防护,建议预编译并添加 `isinstance`。

结论:已采纳,最终实现使用 `re.compile(pattern)` 和 `isinstance(tok, str)` 检查。 · 已解决

风险与影响

  • 初始化性能resolve_hunyuan_tokenshas_vocab_pattern 都需扫描整个词表(通常数万 token),但仅发生在检测器创建时(每个请求仅一次),影响可控。
  • 兼容性tokenizer=None 时完全回退到旧行为,向后兼容。
  • 潜在回归structure_info 中的 begin 字符串在初期重构中遗漏了 tool_call_start_token,但在最终提交 e7d6fd25 中已修复。
  • 非字符串键崩溃:已通过 isinstance 检查充分防护。
  • 用户:Hunyuan v3 模型用户现在可以正确使用 --tool-call-parser auto 和推理/工具调用流式解析,无论 tokenizer 是否带后缀。之前因硬编码裸文字导致解析失败或自动检测错误的问题得以解决。
  • 系统:无额外运行时开销(解析仅初始化一次)。新增的 has_vocab_pattern 在模板检测阶段调用,但 vocab 通常不大且检测调用次数有限。
  • 团队:无负面影响。代码保持向后兼容,并通过条件参数注入避免侵入非 Hunyuan 检测器。
非字符串键崩溃防护 vocab 扫描性能 structure_info 遗漏修复 向后兼容性

关联 Issue

未识别关联 Issue

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

完整报告

参与讨论