[Bugfix] Validate tool-parser/tokenizer compatibility at startup (#59749)

Signed-off-by: www6v <www6v@126.com>
Signed-off-by: sfeng33 <4florafeng@gmail.com>
Co-authored-by: sfeng33 <4florafeng@gmail.com>
This commit is contained in:
www6v
2026-10-07 23:46:41 +00:00
committed by GitHub
co-authored by sfeng33
parent d6fe5dca68
commit 38dc8ee500
+56 -2
View File
@@ -73,6 +73,29 @@ class ParserManager:
raise TypeError(f"{reasoning_parser_name=} has not been registered") from e
return parser
@classmethod
def validate_parser_tokenizer(
cls,
parser_cls: type[Parser],
tokenizer: TokenizerLike,
tool_parser_name: str | None = None,
reasoning_parser_name: str | None = None,
model_name: str | None = None,
) -> None:
"""Fail at startup if the tokenizer lacks what the parsers need."""
try:
parser_cls(tokenizer)
except Exception as e:
flags = []
if parser_cls.tool_parser_cls is not None:
flags.append(f"--tool-call-parser {tool_parser_name}")
if parser_cls.reasoning_parser_cls is not None:
flags.append(f"--reasoning-parser {reasoning_parser_name}")
raise TypeError(
f"{' and '.join(flags)} cannot be used with the tokenizer of "
f"{model_name!r}: {e}"
) from e
@classmethod
def get_parser(
cls,
@@ -97,13 +120,44 @@ class ParserManager:
If True, HarmonyParser is always returned.
tool_strict_level: Server-side floor for tool-call structural
tags (``--tool-strict-level``).
tokenizer: Tokenizer whose `response_template` metadata is
validated when the `hf` parser is selected.
tokenizer: Tokenizer the composed parser is validated against
at startup (see :meth:`validate_parser_tokenizer`). ``None``
skips the check (e.g. ``skip_tokenizer_init``).
Returns:
A Parser class, or None if neither parser is specified.
"""
parser_cls = cls._compose_parser(
tool_parser_name=tool_parser_name,
reasoning_parser_name=reasoning_parser_name,
enable_auto_tools=enable_auto_tools,
model_name=model_name,
is_harmony=is_harmony,
tool_strict_level=tool_strict_level,
tokenizer=tokenizer,
)
if parser_cls is not None and tokenizer is not None:
cls.validate_parser_tokenizer(
parser_cls,
tokenizer,
tool_parser_name=tool_parser_name,
reasoning_parser_name=reasoning_parser_name,
model_name=model_name,
)
return parser_cls
@classmethod
def _compose_parser(
cls,
tool_parser_name: str | None,
reasoning_parser_name: str | None,
enable_auto_tools: bool,
model_name: str | None,
is_harmony: bool,
tool_strict_level: str,
tokenizer: TokenizerLike | None,
) -> type[Parser] | None:
if not tool_parser_name and not reasoning_parser_name:
return None