MiniMax M3 parser for namespace-delimited XML-style tool calls.
MiniMax M3 prefixes every structural tag with the ]<]minimax[>[ marker::
]<]minimax[>[<tool_call>
]<]minimax[>[<invoke name="create_order">
]<]minimax[>[<user_id>42]<]minimax[>[</user_id>
]<]minimax[>[<items>
]<]minimax[>[<item>]<]minimax[>[<sku>book-001]<]minimax[>[</sku>]<]minimax[>[</item>
]<]minimax[>[</items>
]<]minimax[>[</invoke>
]<]minimax[>[</tool_call>
Each <invoke> becomes one tool call. Parameters are elements named after
the argument and may nest: an element holding child elements becomes an
object, or an array when its schema says so (the chat template renders list
items as <item>). Arguments are emitted once the whole <invoke> is
parsed. Reasoning is handled by MiniMaxM3ReasoningParser.
Classes:
-
MinimaxM3Parser
–
MiniMax M3 tool-call parser backed by the declarative parser engine.
MinimaxM3Parser
Bases: ParserEngine
MiniMax M3 tool-call parser backed by the declarative parser engine.
Source code in vllm/parser/minimax_m3.py
| class MinimaxM3Parser(ParserEngine):
"""MiniMax M3 tool-call parser backed by the declarative parser engine."""
def __init__(
self,
tokenizer: TokenizerLike,
tools: list[Tool] | None = None,
**kwargs,
) -> None:
kwargs.setdefault("parser_engine_config", minimax_m3_config())
super().__init__(tokenizer, tools, **kwargs)
self._arg_converter = self._convert_args
def adjust_request(
self, request: ChatCompletionRequest | ResponsesRequest
) -> ChatCompletionRequest | ResponsesRequest:
# The M3 markers are non-special added tokens, so they decode with
# special tokens skipped.
return request
def _convert_args(self, raw_args: str, partial: bool) -> str:
func_name = next(
(slot.name for slot in self._tool_slots if slot.args == raw_args), ""
)
properties = find_tool_properties(self._tools, func_name.strip())
return _minimax_m3_arg_converter(raw_args, partial, properties)
|
_container_schema(schema)
Return the first array or object alternative of schema.
Source code in vllm/parser/minimax_m3.py
| def _container_schema(schema: dict[str, Any]) -> tuple[str, dict[str, Any]] | None:
"""Return the first ``array`` or ``object`` alternative of ``schema``."""
types = schema.get("type")
for kind in ("array", "object"):
if types == kind or (isinstance(types, list) and kind in types):
return kind, schema
if types is None:
if "items" in schema:
return "array", schema
if "properties" in schema or "additionalProperties" in schema:
return "object", schema
for choice_field in ("anyOf", "oneOf"):
for choice in schema.get(choice_field) or ():
if isinstance(choice, dict) and (found := _container_schema(choice)):
return found
return None
|
_convert_object(elements, schema)
Convert named elements to an object; repeated names collect into a list.
Source code in vllm/parser/minimax_m3.py
| def _convert_object(
elements: list[tuple[str, _Element]], schema: dict[str, Any] | None
) -> dict[str, Any]:
"""Convert named elements to an object; repeated names collect into a list."""
properties = (schema or {}).get("properties") or {}
additional = (schema or {}).get("additionalProperties")
result: dict[str, Any] = {}
repeated: set[str] = set()
for name, element in elements:
converted = _convert_element(element, properties.get(name, additional))
if name not in result:
result[name] = converted
elif name in repeated:
result[name].append(converted)
else:
result[name] = [result[name], converted]
repeated.add(name)
return result
|
_parse_element(text, pos, depth)
Parse the element starting at pos; None if malformed.
Source code in vllm/parser/minimax_m3.py
| def _parse_element(text: str, pos: int, depth: int) -> tuple[str, _Element, int] | None:
"""Parse the element starting at ``pos``; ``None`` if malformed."""
if depth > MAX_ELEMENT_DEPTH:
return None
name_start = pos + len(ELEMENT_START)
name_end = text.find(">", name_start)
if name_end <= name_start:
return None
name = text[name_start:name_end]
if name.startswith("/") or not name.strip():
return None
close_tag = f"{ELEMENT_END_START}{name}>"
pos = name_end + 1
text_parts: list[str] = []
children: list[tuple[str, _Element]] = []
while True:
marker = text.find(NAMESPACE, pos)
if marker < 0:
return None
text_parts.append(text[pos:marker])
if text.startswith(close_tag, marker):
pos = marker + len(close_tag)
break
if not text.startswith(ELEMENT_START, marker):
return None
child = _parse_element(text, marker, depth + 1)
if child is None:
return None
child_name, child_value, pos = child
children.append((child_name, child_value))
body_text = "".join(text_parts)
if not children:
return name, body_text, pos
if body_text.strip():
# Keep mixed text under a reserved field that no child uses.
field = MIXED_TEXT_FIELD
while any(child_name == field for child_name, _ in children):
field = "$" + field
children.append((field, body_text))
return name, children, pos
|
_parse_invoke_params(raw_args)
Parse the parameter elements of one <invoke> body.
Parsing stops at the first malformed or unterminated element, keeping the
parameters before it.
Source code in vllm/parser/minimax_m3.py
| def _parse_invoke_params(raw_args: str) -> list[tuple[str, _Element]]:
"""Parse the parameter elements of one ``<invoke>`` body.
Parsing stops at the first malformed or unterminated element, keeping the
parameters before it.
"""
params: list[tuple[str, _Element]] = []
pos = 0
while True:
while pos < len(raw_args) and raw_args[pos].isspace():
pos += 1
if not raw_args.startswith(ELEMENT_START, pos):
return params
element = _parse_element(raw_args, pos, depth=1)
if element is None:
return params
name, value, pos = element
params.append((name, value))
|