[Model] Support Hy3 token suffix and JSON Schema array types (#47192)
Signed-off-by: stevenkuang-tencent <stevenkuang@tencent.com>
stevenkuang committed
Jul 1, 2026 at 18:16 UTC
cc56379e28a0600cbabe290508ef49d88cc2afd5
2 files changed
+24
-11
vllm/reasoning/hy_v3_reasoning_parser.py
+4
-2
@@ -26,6 +26,8 @@ class HYV3ReasoningParser(BaseThinkingReasoningParser):
26
"""
27
28
def __init__(self, tokenizer: TokenizerLike, *args, **kwargs):
29
+ init_kwargs = getattr(tokenizer, "init_kwargs", None) or {}
30
+ self.suffix: str = init_kwargs.get("token_suffix") or ""
31
super().__init__(tokenizer, *args, **kwargs)
32
33
# First, If there is reasoning_effort in chat_kwargs,
@@ -52,12 +54,12 @@ class HYV3ReasoningParser(BaseThinkingReasoningParser):
54
@property
55
def start_token(self) -> str:
56
"""The token that starts reasoning content."""
55
- return "<think>"
57
+ return f"<think{self.suffix}>"
58
59
@property
60
def end_token(self) -> str:
61
"""The token that ends reasoning content."""
60
- return "</think>"
62
+ return f"</think{self.suffix}>"
63
64
def is_reasoning_end(self, input_ids: Sequence[int]) -> bool:
65
if self._identity_parser is not None:
vllm/tool_parsers/hy_v3_tool_parser.py
+20
-9
@@ -108,6 +108,14 @@ class HYV3ToolParser(ToolParser):
108
Note: single ``type`` has the highest priority.
109
"""
110
if "type" in arg_schema:
111
+ type_val = arg_schema["type"]
112
+ # JSON Schema allows "type" to be an array to represent union types,
113
+ # e.g. "type": ["string", "object"].
114
+ # Expand it into an anyOf-equivalent format:
115
+ # [{"type": "string"}, {"type": "object"}]
116
+ # so that _get_types / _parse_value can handle it uniformly later.
117
+ if isinstance(type_val, list):
118
+ return [{"type": t} for t in type_val]
119
return [arg_schema]
120
if "anyOf" in arg_schema:
121
return arg_schema["anyOf"]
@@ -261,19 +269,22 @@ class HYV3ToolParser(ToolParser):
269
self._current_arg_is_string: bool = False # is current arg pure string?
270
self._streamed_json_len: int = 0 # bytes of JSON already sent
271
264
- self.tool_calls_start_token: str = "<tool_calls>"
265
- self.tool_calls_end_token: str = "</tool_calls>"
272
+ init_kwargs = getattr(tokenizer, "init_kwargs", None) or {}
273
+ self.suffix: str = init_kwargs.get("token_suffix") or ""
274
267
- self.tool_call_start_token: str = "<tool_call>"
268
- self.tool_call_end_token: str = "</tool_call>"
275
+ self.tool_calls_start_token: str = f"<tool_calls{self.suffix}>"
276
+ self.tool_calls_end_token: str = f"</tool_calls{self.suffix}>"
277
270
- self.tool_sep_token: str = "<tool_sep>"
278
+ self.tool_call_start_token: str = f"<tool_call{self.suffix}>"
279
+ self.tool_call_end_token: str = f"</tool_call{self.suffix}>"
280
272
- self.arg_key_start_token: str = "<arg_key>"
273
- self.arg_key_end_token: str = "</arg_key>"
281
+ self.tool_sep_token: str = f"<tool_sep{self.suffix}>"
282
275
- self.arg_value_start_token: str = "<arg_value>"
276
- self.arg_value_end_token: str = "</arg_value>"
283
+ self.arg_key_start_token: str = f"<arg_key{self.suffix}>"
284
+ self.arg_key_end_token: str = f"</arg_key{self.suffix}>"
285
+
286
+ self.arg_value_start_token: str = f"<arg_value{self.suffix}>"
287
+ self.arg_value_end_token: str = f"</arg_value{self.suffix}>"
288
289
self.tool_call_regex = re.compile(
290
rf"{self.tool_call_start_token}(.*?){self.tool_sep_token}"