Skip to content

vllm.parser.minimax_m3

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))