From cc56379e28a0600cbabe290508ef49d88cc2afd5 Mon Sep 17 00:00:00 2001 From: stevenkuang Date: Wed, 1 Jul 2026 18:16:07 +0800 Subject: [PATCH] [Model] Support Hy3 token suffix and JSON Schema array types (#47192) Signed-off-by: stevenkuang-tencent --- vllm/reasoning/hy_v3_reasoning_parser.py | 6 +++-- vllm/tool_parsers/hy_v3_tool_parser.py | 29 ++++++++++++++++-------- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/vllm/reasoning/hy_v3_reasoning_parser.py b/vllm/reasoning/hy_v3_reasoning_parser.py index 5beac22996..59631a3340 100644 --- a/vllm/reasoning/hy_v3_reasoning_parser.py +++ b/vllm/reasoning/hy_v3_reasoning_parser.py @@ -26,6 +26,8 @@ class HYV3ReasoningParser(BaseThinkingReasoningParser): """ def __init__(self, tokenizer: TokenizerLike, *args, **kwargs): + init_kwargs = getattr(tokenizer, "init_kwargs", None) or {} + self.suffix: str = init_kwargs.get("token_suffix") or "" super().__init__(tokenizer, *args, **kwargs) # First, If there is reasoning_effort in chat_kwargs, @@ -52,12 +54,12 @@ class HYV3ReasoningParser(BaseThinkingReasoningParser): @property def start_token(self) -> str: """The token that starts reasoning content.""" - return "" + return f"" @property def end_token(self) -> str: """The token that ends reasoning content.""" - return "" + return f"" def is_reasoning_end(self, input_ids: Sequence[int]) -> bool: if self._identity_parser is not None: diff --git a/vllm/tool_parsers/hy_v3_tool_parser.py b/vllm/tool_parsers/hy_v3_tool_parser.py index 619be5e9cc..0ffaf92d86 100644 --- a/vllm/tool_parsers/hy_v3_tool_parser.py +++ b/vllm/tool_parsers/hy_v3_tool_parser.py @@ -108,6 +108,14 @@ class HYV3ToolParser(ToolParser): Note: single ``type`` has the highest priority. """ if "type" in arg_schema: + type_val = arg_schema["type"] + # JSON Schema allows "type" to be an array to represent union types, + # e.g. "type": ["string", "object"]. + # Expand it into an anyOf-equivalent format: + # [{"type": "string"}, {"type": "object"}] + # so that _get_types / _parse_value can handle it uniformly later. + if isinstance(type_val, list): + return [{"type": t} for t in type_val] return [arg_schema] if "anyOf" in arg_schema: return arg_schema["anyOf"] @@ -261,19 +269,22 @@ class HYV3ToolParser(ToolParser): self._current_arg_is_string: bool = False # is current arg pure string? self._streamed_json_len: int = 0 # bytes of JSON already sent - self.tool_calls_start_token: str = "" - self.tool_calls_end_token: str = "" + init_kwargs = getattr(tokenizer, "init_kwargs", None) or {} + self.suffix: str = init_kwargs.get("token_suffix") or "" - self.tool_call_start_token: str = "" - self.tool_call_end_token: str = "" + self.tool_calls_start_token: str = f"" + self.tool_calls_end_token: str = f"" - self.tool_sep_token: str = "" + self.tool_call_start_token: str = f"" + self.tool_call_end_token: str = f"" - self.arg_key_start_token: str = "" - self.arg_key_end_token: str = "" + self.tool_sep_token: str = f"" - self.arg_value_start_token: str = "" - self.arg_value_end_token: str = "" + self.arg_key_start_token: str = f"" + self.arg_key_end_token: str = f"" + + self.arg_value_start_token: str = f"" + self.arg_value_end_token: str = f"" self.tool_call_regex = re.compile( rf"{self.tool_call_start_token}(.*?){self.tool_sep_token}"