feat(parser): resolve special-token suffix at runtime for compatibility (#29920)
This commit is contained in:
@@ -300,7 +300,9 @@ def create_grammar_backend(
|
|||||||
)
|
)
|
||||||
|
|
||||||
reasoning_parser = ReasoningParser(
|
reasoning_parser = ReasoningParser(
|
||||||
model_type=server_args.reasoning_parser, stream_reasoning=False
|
model_type=server_args.reasoning_parser,
|
||||||
|
stream_reasoning=False,
|
||||||
|
tokenizer=tokenizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
grammar_backend = ReasonerGrammarBackend(
|
grammar_backend = ReasonerGrammarBackend(
|
||||||
|
|||||||
@@ -1517,7 +1517,11 @@ async def parse_function_call_request(
|
|||||||
A native API endpoint to parse function calls from a text.
|
A native API endpoint to parse function calls from a text.
|
||||||
"""
|
"""
|
||||||
# 1) Initialize the parser based on the request body
|
# 1) Initialize the parser based on the request body
|
||||||
parser = FunctionCallParser(tools=obj.tools, tool_call_parser=obj.tool_call_parser)
|
parser = FunctionCallParser(
|
||||||
|
tools=obj.tools,
|
||||||
|
tool_call_parser=obj.tool_call_parser,
|
||||||
|
tokenizer=get_global_state().tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
|
|
||||||
# 2) Call the non-stream parsing method (non-stream)
|
# 2) Call the non-stream parsing method (non-stream)
|
||||||
normal_text, calls = parser.parse_non_stream(obj.text)
|
normal_text, calls = parser.parse_non_stream(obj.text)
|
||||||
@@ -1541,7 +1545,11 @@ async def separate_reasoning_request(
|
|||||||
A native API endpoint to separate reasoning from a text.
|
A native API endpoint to separate reasoning from a text.
|
||||||
"""
|
"""
|
||||||
# 1) Initialize the parser based on the request body
|
# 1) Initialize the parser based on the request body
|
||||||
parser = ReasoningParser(model_type=obj.reasoning_parser, request=request)
|
parser = ReasoningParser(
|
||||||
|
model_type=obj.reasoning_parser,
|
||||||
|
request=request,
|
||||||
|
tokenizer=get_global_state().tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
|
|
||||||
# 2) Call the non-stream parsing method (non-stream)
|
# 2) Call the non-stream parsing method (non-stream)
|
||||||
if obj.return_blocks:
|
if obj.return_blocks:
|
||||||
|
|||||||
@@ -175,7 +175,9 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
if self.reasoning_parser:
|
if self.reasoning_parser:
|
||||||
try:
|
try:
|
||||||
rp = ReasoningParser(
|
rp = ReasoningParser(
|
||||||
model_type=self.reasoning_parser, stream_reasoning=True
|
model_type=self.reasoning_parser,
|
||||||
|
stream_reasoning=True,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
self._reasoning_detector = rp.detector
|
self._reasoning_detector = rp.detector
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
@@ -668,7 +670,11 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
else:
|
else:
|
||||||
tools = [item.model_dump() for item in request.tools]
|
tools = [item.model_dump() for item in request.tools]
|
||||||
if self.tool_call_parser:
|
if self.tool_call_parser:
|
||||||
parser = FunctionCallParser(request.tools, self.tool_call_parser)
|
parser = FunctionCallParser(
|
||||||
|
request.tools,
|
||||||
|
self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
tool_call_constraint = parser.get_structure_constraint(
|
tool_call_constraint = parser.get_structure_constraint(
|
||||||
request.tool_choice,
|
request.tool_choice,
|
||||||
parallel_tool_calls=request.parallel_tool_calls,
|
parallel_tool_calls=request.parallel_tool_calls,
|
||||||
@@ -1327,6 +1333,7 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
stream_reasoning=False,
|
stream_reasoning=False,
|
||||||
force_reasoning=force_reasoning,
|
force_reasoning=force_reasoning,
|
||||||
request=request,
|
request=request,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
reasoning_text, text = parser.parse_non_stream(text)
|
reasoning_text, text = parser.parse_non_stream(text)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -1510,7 +1517,9 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
# For required/named: only use parser when structural_tag was used
|
# For required/named: only use parser when structural_tag was used
|
||||||
# as constraint (mirrors the streaming path). For auto: always try.
|
# as constraint (mirrors the streaming path). For auto: always try.
|
||||||
if self.tool_call_parser:
|
if self.tool_call_parser:
|
||||||
parser = FunctionCallParser(tools, self.tool_call_parser)
|
parser = FunctionCallParser(
|
||||||
|
tools, self.tool_call_parser, tokenizer=self.tokenizer_manager.tokenizer
|
||||||
|
)
|
||||||
should_try_parser = (
|
should_try_parser = (
|
||||||
not is_required or parser.detector.supports_structural_tag()
|
not is_required or parser.detector.supports_structural_tag()
|
||||||
)
|
)
|
||||||
@@ -1623,6 +1632,7 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
request.stream_reasoning,
|
request.stream_reasoning,
|
||||||
is_force_reasoning,
|
is_force_reasoning,
|
||||||
request,
|
request,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
reasoning_parser = reasoning_parser_dict[index]
|
reasoning_parser = reasoning_parser_dict[index]
|
||||||
return reasoning_parser.parse_stream_chunk(delta)
|
return reasoning_parser.parse_stream_chunk(delta)
|
||||||
@@ -1867,6 +1877,7 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
probe = FunctionCallParser(
|
probe = FunctionCallParser(
|
||||||
tools=request.tools,
|
tools=request.tools,
|
||||||
tool_call_parser=self.tool_call_parser,
|
tool_call_parser=self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
use_native_parser = probe.detector.supports_structural_tag()
|
use_native_parser = probe.detector.supports_structural_tag()
|
||||||
if use_native_parser:
|
if use_native_parser:
|
||||||
@@ -1877,6 +1888,7 @@ class OpenAIServingChat(OpenAIServingBase):
|
|||||||
parser_dict[index] = FunctionCallParser(
|
parser_dict[index] = FunctionCallParser(
|
||||||
tools=request.tools,
|
tools=request.tools,
|
||||||
tool_call_parser=self.tool_call_parser,
|
tool_call_parser=self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
parser = parser_dict[index]
|
parser = parser_dict[index]
|
||||||
|
|||||||
@@ -672,6 +672,7 @@ class OpenAIServingResponses(OpenAIServingChat):
|
|||||||
stream_reasoning=False,
|
stream_reasoning=False,
|
||||||
force_reasoning=self._is_thinking_enabled_for_request(request),
|
force_reasoning=self._is_thinking_enabled_for_request(request),
|
||||||
request=request,
|
request=request,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
reasoning_content, content = reasoning_parser.parse_non_stream(final_output)
|
reasoning_content, content = reasoning_parser.parse_non_stream(final_output)
|
||||||
else:
|
else:
|
||||||
@@ -714,7 +715,11 @@ class OpenAIServingResponses(OpenAIServingChat):
|
|||||||
and self.tool_call_parser
|
and self.tool_call_parser
|
||||||
and request.tool_choice != "none"
|
and request.tool_choice != "none"
|
||||||
):
|
):
|
||||||
parser = FunctionCallParser(chat_tools, self.tool_call_parser)
|
parser = FunctionCallParser(
|
||||||
|
chat_tools,
|
||||||
|
self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
should_try_native = (
|
should_try_native = (
|
||||||
not is_required or parser.detector.supports_structural_tag()
|
not is_required or parser.detector.supports_structural_tag()
|
||||||
)
|
)
|
||||||
@@ -1799,14 +1804,22 @@ class OpenAIServingResponses(OpenAIServingChat):
|
|||||||
if chat_tools and request.tool_choice != "none":
|
if chat_tools and request.tool_choice != "none":
|
||||||
native_supports_structural_tag = False
|
native_supports_structural_tag = False
|
||||||
if self.tool_call_parser:
|
if self.tool_call_parser:
|
||||||
probe = FunctionCallParser(chat_tools, self.tool_call_parser)
|
probe = FunctionCallParser(
|
||||||
|
chat_tools,
|
||||||
|
self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
native_supports_structural_tag = (
|
native_supports_structural_tag = (
|
||||||
probe.detector.supports_structural_tag()
|
probe.detector.supports_structural_tag()
|
||||||
)
|
)
|
||||||
if is_required and not native_supports_structural_tag:
|
if is_required and not native_supports_structural_tag:
|
||||||
tool_parser = JsonArrayParser()
|
tool_parser = JsonArrayParser()
|
||||||
elif self.tool_call_parser:
|
elif self.tool_call_parser:
|
||||||
tool_parser = FunctionCallParser(chat_tools, self.tool_call_parser)
|
tool_parser = FunctionCallParser(
|
||||||
|
chat_tools,
|
||||||
|
self.tool_call_parser,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
|
)
|
||||||
reasoning_parser_obj: Optional[ReasoningParser] = None
|
reasoning_parser_obj: Optional[ReasoningParser] = None
|
||||||
if self.reasoning_parser:
|
if self.reasoning_parser:
|
||||||
reasoning_parser_obj = ReasoningParser(
|
reasoning_parser_obj = ReasoningParser(
|
||||||
@@ -1814,6 +1827,7 @@ class OpenAIServingResponses(OpenAIServingChat):
|
|||||||
stream_reasoning=True,
|
stream_reasoning=True,
|
||||||
force_reasoning=self._is_thinking_enabled_for_request(request),
|
force_reasoning=self._is_thinking_enabled_for_request(request),
|
||||||
request=request,
|
request=request,
|
||||||
|
tokenizer=self.tokenizer_manager.tokenizer,
|
||||||
)
|
)
|
||||||
|
|
||||||
current_output_index = -1
|
current_output_index = -1
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
from typing import Dict, List, Literal, Optional, Set, Tuple, Type, Union
|
from typing import Dict, List, Literal, Optional, Set, Tuple, Type, Union
|
||||||
|
|
||||||
@@ -89,10 +90,15 @@ class FunctionCallParser:
|
|||||||
"gemma4": Gemma4Detector,
|
"gemma4": Gemma4Detector,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, tools: List[Tool], tool_call_parser: str):
|
def __init__(self, tools: List[Tool], tool_call_parser: str, tokenizer=None):
|
||||||
detector_class = self.ToolCallParserEnum.get(tool_call_parser)
|
detector_class = self.ToolCallParserEnum.get(tool_call_parser)
|
||||||
if detector_class:
|
if detector_class:
|
||||||
detector = detector_class()
|
kwargs = {}
|
||||||
|
if tokenizer is not None:
|
||||||
|
sig = inspect.signature(detector_class)
|
||||||
|
if "tokenizer" in sig.parameters:
|
||||||
|
kwargs["tokenizer"] = tokenizer
|
||||||
|
detector = detector_class(**kwargs)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f"Unsupported tool_call_parser: {tool_call_parser}")
|
raise ValueError(f"Unsupported tool_call_parser: {tool_call_parser}")
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,50 @@ from sglang.srt.function_call.core_types import (
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Bare (suffix-less) Hunyuan special tokens. The shipping Hy3 tokenizer appends
|
||||||
|
# a shared suffix to each (e.g. ``<tool_calls:opensource>``); resolve the real
|
||||||
|
# token string from the vocab at runtime and fall back to these literals.
|
||||||
|
_HUNYUAN_TOKEN_NAMES = (
|
||||||
|
"tool_calls",
|
||||||
|
"tool_call",
|
||||||
|
"tool_sep",
|
||||||
|
"arg_key",
|
||||||
|
"arg_value",
|
||||||
|
"think",
|
||||||
|
)
|
||||||
|
|
||||||
|
_HUNYUAN_TOKEN_RE = re.compile(
|
||||||
|
r"^<(?P<name>" + "|".join(_HUNYUAN_TOKEN_NAMES) + r")(?::[^>]+)?>$"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_hunyuan_tokens(tokenizer) -> Dict[str, str]:
|
||||||
|
"""Map bare token names to their real (possibly suffixed) strings in vocab.
|
||||||
|
|
||||||
|
Returns ``{name: token_str}`` for each name found. A bare literal is used
|
||||||
|
when the tokenizer carries no suffixed form, so the same detector serves
|
||||||
|
both the preview (suffix-less) and shipping (suffixed) Hy3 tokenizers.
|
||||||
|
"""
|
||||||
|
tokens: Dict[str, str] = {}
|
||||||
|
vocab = None
|
||||||
|
if tokenizer is not None:
|
||||||
|
try:
|
||||||
|
vocab = tokenizer.get_vocab()
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning("Failed to read Hunyuan tokenizer vocab: %s", e)
|
||||||
|
vocab = None
|
||||||
|
if isinstance(vocab, dict):
|
||||||
|
for tok in vocab:
|
||||||
|
if not isinstance(tok, str):
|
||||||
|
continue
|
||||||
|
m = _HUNYUAN_TOKEN_RE.match(tok)
|
||||||
|
if m:
|
||||||
|
tokens[m.group("name")] = tok
|
||||||
|
for name in _HUNYUAN_TOKEN_NAMES:
|
||||||
|
tokens.setdefault(name, f"<{name}>")
|
||||||
|
return tokens
|
||||||
|
|
||||||
|
|
||||||
class HunyuanDetector(BaseFormatDetector):
|
class HunyuanDetector(BaseFormatDetector):
|
||||||
"""
|
"""
|
||||||
Detector for Hunyuan (HYV3) tool call format.
|
Detector for Hunyuan (HYV3) tool call format.
|
||||||
@@ -55,26 +99,49 @@ class HunyuanDetector(BaseFormatDetector):
|
|||||||
_INTEGER_PREFIXES = ("int", "uint", "long", "short", "unsigned")
|
_INTEGER_PREFIXES = ("int", "uint", "long", "short", "unsigned")
|
||||||
_NUMBER_PREFIXES = ("num", "float")
|
_NUMBER_PREFIXES = ("num", "float")
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, tokenizer=None):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self.bot_token = "<tool_calls>"
|
t = resolve_hunyuan_tokens(tokenizer)
|
||||||
self.eot_token = "</tool_calls>"
|
tool_calls = t["tool_calls"]
|
||||||
|
tool_call = t["tool_call"]
|
||||||
|
tool_sep = t["tool_sep"]
|
||||||
|
arg_key = t["arg_key"]
|
||||||
|
arg_value = t["arg_value"]
|
||||||
|
|
||||||
self.tool_call_start_token = "<tool_call>"
|
def _close(open_tok: str) -> str:
|
||||||
self.tool_call_end_token = "</tool_call>"
|
return "</" + open_tok[1:] if open_tok.startswith("<") else open_tok
|
||||||
self.tool_sep_token = "<tool_sep>"
|
|
||||||
|
|
||||||
self.arg_key_start_token = "<arg_key>"
|
self.bot_token = tool_calls
|
||||||
self.arg_key_end_token = "</arg_key>"
|
self.eot_token = _close(tool_calls)
|
||||||
self.arg_value_start_token = "<arg_value>"
|
self.tool_call_start_token = tool_call
|
||||||
self.arg_value_end_token = "</arg_value>"
|
self.tool_call_end_token = _close(tool_call)
|
||||||
|
self.tool_sep_token = tool_sep
|
||||||
|
self.arg_key_start_token = arg_key
|
||||||
|
self.arg_key_end_token = _close(arg_key)
|
||||||
|
self.arg_value_start_token = arg_value
|
||||||
|
self.arg_value_end_token = _close(arg_value)
|
||||||
|
|
||||||
|
tc_end = _close(tool_call)
|
||||||
|
ak_end = _close(arg_key)
|
||||||
|
av_end = _close(arg_value)
|
||||||
self.tool_call_regex = re.compile(
|
self.tool_call_regex = re.compile(
|
||||||
r"<tool_call>(.*?)<tool_sep>(.*?)</tool_call>", re.DOTALL
|
re.escape(tool_call)
|
||||||
|
+ r"(.*?)"
|
||||||
|
+ re.escape(tool_sep)
|
||||||
|
+ r"(.*?)"
|
||||||
|
+ re.escape(tc_end),
|
||||||
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
self.func_args_regex = re.compile(
|
self.func_args_regex = re.compile(
|
||||||
r"<arg_key>(.*?)</arg_key>\s*<arg_value>(.*?)</arg_value>", re.DOTALL
|
re.escape(arg_key)
|
||||||
|
+ r"(.*?)"
|
||||||
|
+ re.escape(ak_end)
|
||||||
|
+ r"\s*"
|
||||||
|
+ re.escape(arg_value)
|
||||||
|
+ r"(.*?)"
|
||||||
|
+ re.escape(av_end),
|
||||||
|
re.DOTALL,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Streaming state
|
# Streaming state
|
||||||
@@ -467,9 +534,9 @@ class HunyuanDetector(BaseFormatDetector):
|
|||||||
|
|
||||||
def structure_info(self) -> _GetInfoFunc:
|
def structure_info(self) -> _GetInfoFunc:
|
||||||
return lambda name: StructureInfo(
|
return lambda name: StructureInfo(
|
||||||
begin=f"<tool_calls>\n<tool_call>{name}<tool_sep>",
|
begin=f"{self.bot_token}\n{self.tool_call_start_token}{name}{self.tool_sep_token}",
|
||||||
end="</tool_call>\n</tool_calls>",
|
end=f"{self.tool_call_end_token}\n{self.eot_token}",
|
||||||
trigger="<tool_calls>",
|
trigger=self.bot_token,
|
||||||
)
|
)
|
||||||
|
|
||||||
def supports_structural_tag(self) -> bool:
|
def supports_structural_tag(self) -> bool:
|
||||||
|
|||||||
@@ -718,7 +718,9 @@ class Scheduler(
|
|||||||
# Set reasoning_parser and think_end_id if --reasoning_parser is enabled
|
# Set reasoning_parser and think_end_id if --reasoning_parser is enabled
|
||||||
if self.server_args.reasoning_parser and self.tokenizer:
|
if self.server_args.reasoning_parser and self.tokenizer:
|
||||||
reasoning_parser = ReasoningParser(
|
reasoning_parser = ReasoningParser(
|
||||||
model_type=self.server_args.reasoning_parser, stream_reasoning=False
|
model_type=self.server_args.reasoning_parser,
|
||||||
|
stream_reasoning=False,
|
||||||
|
tokenizer=self.tokenizer,
|
||||||
)
|
)
|
||||||
self.model_config.think_end_id = self.tokenizer.encode(
|
self.model_config.think_end_id = self.tokenizer.encode(
|
||||||
reasoning_parser.detector.think_end_token, add_special_tokens=False
|
reasoning_parser.detector.think_end_token, add_special_tokens=False
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ class TemplateDetectionContext:
|
|||||||
def has_pattern(self, pattern: str, flags: int = 0) -> bool:
|
def has_pattern(self, pattern: str, flags: int = 0) -> bool:
|
||||||
return re.search(pattern, self.template, flags) is not None
|
return re.search(pattern, self.template, flags) is not None
|
||||||
|
|
||||||
|
def has_vocab_pattern(self, pattern: str) -> bool:
|
||||||
|
compiled = re.compile(pattern)
|
||||||
|
return any(isinstance(tok, str) and compiled.search(tok) for tok in self.vocab)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class DetectionRule:
|
class DetectionRule:
|
||||||
@@ -237,10 +241,17 @@ def _is_deepseek_v4(ctx):
|
|||||||
|
|
||||||
|
|
||||||
def _is_hunyuan(ctx):
|
def _is_hunyuan(ctx):
|
||||||
return (
|
# The shipping Hy3 tokenizer appends a shared suffix to each special token
|
||||||
(ctx.has_text("<tool_calls>") or ctx.has_vocab("<tool_calls>"))
|
# (e.g. ``<tool_calls:opensource>``), so match the bare or suffixed form.
|
||||||
and (ctx.has_text("<tool_sep>") or ctx.has_vocab("<tool_sep>"))
|
tc = ctx.has_text("<tool_calls>") or ctx.has_vocab_pattern(
|
||||||
) or (ctx.has_text("reasoning_effort") and ctx.has_text("interleaved_thinking"))
|
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")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _is_poolside_v1(ctx):
|
def _is_poolside_v1(ctx):
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import inspect
|
||||||
from typing import Dict, List, Optional, Tuple, Type
|
from typing import Dict, List, Optional, Tuple, Type
|
||||||
|
|
||||||
from sglang.srt.entrypoints.openai.protocol import ChatCompletionRequest
|
from sglang.srt.entrypoints.openai.protocol import ChatCompletionRequest
|
||||||
|
from sglang.srt.function_call.hunyuan_detector import resolve_hunyuan_tokens
|
||||||
from sglang.srt.parser.harmony_parser import HarmonyParser
|
from sglang.srt.parser.harmony_parser import HarmonyParser
|
||||||
|
|
||||||
|
|
||||||
@@ -541,13 +543,19 @@ class HunyuanDetector(BaseReasoningFormatDetector):
|
|||||||
force_reasoning: bool = False,
|
force_reasoning: bool = False,
|
||||||
continue_final_message: bool = False,
|
continue_final_message: bool = False,
|
||||||
previous_content: str = "",
|
previous_content: str = "",
|
||||||
|
tokenizer=None,
|
||||||
):
|
):
|
||||||
|
t = resolve_hunyuan_tokens(tokenizer)
|
||||||
|
think_open = t["think"]
|
||||||
|
think_close = (
|
||||||
|
"</" + think_open[1:] if think_open.startswith("<") else think_open
|
||||||
|
)
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"<think>",
|
think_open,
|
||||||
"</think>",
|
think_close,
|
||||||
force_reasoning=force_reasoning,
|
force_reasoning=force_reasoning,
|
||||||
stream_reasoning=stream_reasoning,
|
stream_reasoning=stream_reasoning,
|
||||||
tool_start_token="<tool_calls>",
|
tool_start_token=t["tool_calls"],
|
||||||
continue_final_message=continue_final_message,
|
continue_final_message=continue_final_message,
|
||||||
previous_content=previous_content,
|
previous_content=previous_content,
|
||||||
)
|
)
|
||||||
@@ -1096,6 +1104,7 @@ class ReasoningParser:
|
|||||||
stream_reasoning: bool = True,
|
stream_reasoning: bool = True,
|
||||||
force_reasoning: Optional[bool] = None,
|
force_reasoning: Optional[bool] = None,
|
||||||
request: ChatCompletionRequest = None,
|
request: ChatCompletionRequest = None,
|
||||||
|
tokenizer=None,
|
||||||
):
|
):
|
||||||
if not model_type:
|
if not model_type:
|
||||||
raise ValueError("Model type must be specified")
|
raise ValueError("Model type must be specified")
|
||||||
@@ -1130,6 +1139,11 @@ class ReasoningParser:
|
|||||||
if chat_template_kwargs.get("force_nonempty_content") is True:
|
if chat_template_kwargs.get("force_nonempty_content") is True:
|
||||||
kwargs["force_nonempty_content"] = True
|
kwargs["force_nonempty_content"] = True
|
||||||
|
|
||||||
|
if tokenizer is not None:
|
||||||
|
sig = inspect.signature(detector_class)
|
||||||
|
if "tokenizer" in sig.parameters:
|
||||||
|
kwargs["tokenizer"] = tokenizer
|
||||||
|
|
||||||
self.detector = detector_class(**kwargs)
|
self.detector = detector_class(**kwargs)
|
||||||
|
|
||||||
def parse_non_stream(self, full_text: str) -> Tuple[Optional[str], Optional[str]]:
|
def parse_non_stream(self, full_text: str) -> Tuple[Optional[str], Optional[str]]:
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ class ReasoningTokenUsageMixin:
|
|||||||
def init_reasoning_token_verifier(cls):
|
def init_reasoning_token_verifier(cls):
|
||||||
assert cls.reasoning_parser_name, "reasoning_parser_name must be set"
|
assert cls.reasoning_parser_name, "reasoning_parser_name must be set"
|
||||||
cls.tokenizer = get_tokenizer(cls.model)
|
cls.tokenizer = get_tokenizer(cls.model)
|
||||||
parser = ReasoningParser(cls.reasoning_parser_name)
|
parser = ReasoningParser(cls.reasoning_parser_name, tokenizer=cls.tokenizer)
|
||||||
cls.think_end_token_id = cls.tokenizer.convert_tokens_to_ids(
|
cls.think_end_token_id = cls.tokenizer.convert_tokens_to_ids(
|
||||||
parser.detector.think_end_token
|
parser.detector.think_end_token
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user