[Frontend] Port the MiniMax M3 tool parser to the parser engine (#59743)
Signed-off-by: Bugen Zhao <i@bugenzhao.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
6fddc9b21e
commit
5145e35e50
@@ -42,6 +42,7 @@ from vllm.parser.engine.registered_adapters import (
|
||||
KimiK2Parser,
|
||||
MiMoParser,
|
||||
MinimaxM2Parser,
|
||||
MinimaxM3Parser,
|
||||
NemotronV3Parser,
|
||||
Plamo3Parser,
|
||||
Qwen3Parser,
|
||||
@@ -539,6 +540,66 @@ def _build_minimax_m2(scenario: Scenario, validate: bool = True) -> Sample:
|
||||
return sample
|
||||
|
||||
|
||||
# ── MiniMax M3 (namespaced XML elements, no reasoning) ───────────────
|
||||
|
||||
_MINIMAX_M3_NS = "]<]minimax[>["
|
||||
|
||||
|
||||
def _minimax_m3_value(value: Any) -> str:
|
||||
"""Render a value the way the M3 chat template's ``to_xml`` does."""
|
||||
ns = _MINIMAX_M3_NS
|
||||
if isinstance(value, dict):
|
||||
return "".join(
|
||||
f"{ns}<{key}>{_minimax_m3_value(item)}{ns}</{key}>"
|
||||
for key, item in value.items()
|
||||
)
|
||||
if isinstance(value, list):
|
||||
return "".join(
|
||||
f"{ns}<item>{_minimax_m3_value(item)}{ns}</item>" for item in value
|
||||
)
|
||||
if isinstance(value, bool):
|
||||
return "true" if value else "false"
|
||||
return str(value)
|
||||
|
||||
|
||||
def _minimax_m3_segments(scenario: Scenario) -> list[tuple[str, bool]]:
|
||||
ns = _MINIMAX_M3_NS
|
||||
segs: list[tuple[str, bool]] = []
|
||||
if scenario.content:
|
||||
# The tool parser receives reasoning-stripped content.
|
||||
segs.append((scenario.content, False))
|
||||
if scenario.tool_calls:
|
||||
invokes = "".join(
|
||||
f'{ns}<invoke name="{tc.name}">'
|
||||
f"{_minimax_m3_value(tc.arguments)}{ns}</invoke>\n"
|
||||
for tc in scenario.tool_calls
|
||||
)
|
||||
segs.append((f"{ns}<tool_call>\n{invokes}{ns}</tool_call>", False))
|
||||
return segs
|
||||
|
||||
|
||||
def _minimax_m3_expected_content(scenario: Scenario) -> str | None:
|
||||
if scenario.tool_calls and not (scenario.content or "").strip():
|
||||
return None
|
||||
return scenario.content
|
||||
|
||||
|
||||
def _build_minimax_m3(scenario: Scenario, validate: bool = True) -> Sample:
|
||||
sample = _make_sample(
|
||||
sample_id=f"minimax_m3-{scenario.id}",
|
||||
description=scenario.description,
|
||||
vocab={},
|
||||
segments=_minimax_m3_segments(scenario),
|
||||
expected_reasoning=None,
|
||||
expected_content=_minimax_m3_expected_content(scenario),
|
||||
expected_tool_calls=_expected_tc(scenario),
|
||||
tools=_expected_tools(scenario),
|
||||
)
|
||||
if validate:
|
||||
_validate_sample(sample, MinimaxM3Parser)
|
||||
return sample
|
||||
|
||||
|
||||
# ── Gemma4 (channel reasoning, custom arg format) ────────────────────
|
||||
|
||||
_GEMMA4_VOCAB: dict[str, int] = {
|
||||
@@ -1275,6 +1336,7 @@ _BUILDERS: dict[str, Any] = {
|
||||
"gemma4": _build_gemma4,
|
||||
"granite": _build_granite,
|
||||
"minimax_m2": _build_minimax_m2,
|
||||
"minimax_m3": _build_minimax_m3,
|
||||
"nemotron_v3": _build_nemotron_v3,
|
||||
"granite_thinking_parser": _build_granite_thinking,
|
||||
"seed_oss": _build_seed_oss,
|
||||
|
||||
Reference in New Issue
Block a user