diff --git a/tests/parser/test_streaming.py b/tests/parser/test_streaming.py index 71f214f3ee65..6830d694e1a5 100644 --- a/tests/parser/test_streaming.py +++ b/tests/parser/test_streaming.py @@ -7,7 +7,7 @@ from vllm.entrypoints.openai.chat_completion.protocol import ChatCompletionRequest from vllm.entrypoints.openai.engine.protocol import DeltaMessage -from vllm.parser.abstract_parser import _WrappedParser +from vllm.parser.abstract_parser import DelegatingParser from vllm.reasoning.basic_parsers import BaseThinkingReasoningParser from vllm.tool_parsers.hermes_tool_parser import Hermes2ProToolParser @@ -45,9 +45,11 @@ def request_obj(): def make_parser(tokenizer, reasoning=False, tool=False): - _WrappedParser.reasoning_parser_cls = ThinkReasoningParser if reasoning else None - _WrappedParser.tool_parser_cls = Hermes2ProToolParser if tool else None - return _WrappedParser(tokenizer) + class TestParser(DelegatingParser): + reasoning_parser_cls = ThinkReasoningParser if reasoning else None + tool_parser_cls = Hermes2ProToolParser if tool else None + + return TestParser(tokenizer) def stream_text(parser, tokenizer, text, request, prompt_token_ids=None): diff --git a/vllm/parser/__init__.py b/vllm/parser/__init__.py index dc256daaa7e2..de815b2e1fd4 100644 --- a/vllm/parser/__init__.py +++ b/vllm/parser/__init__.py @@ -4,7 +4,6 @@ from vllm.parser.abstract_parser import ( DelegatingParser, Parser, - _WrappedParser, ) from vllm.parser.parser_manager import ParserManager @@ -12,21 +11,4 @@ "Parser", "DelegatingParser", "ParserManager", - "_WrappedParser", ] - -_PARSERS_TO_REGISTER = { - "minimax_m2": ( # name - "minimax_m2_parser", # filename - "MiniMaxM2Parser", # class_name - ), -} - - -def register_lazy_parsers(): - for name, (file_name, class_name) in _PARSERS_TO_REGISTER.items(): - module_path = f"vllm.parser.{file_name}" - ParserManager.register_lazy_module(name, module_path, class_name) - - -register_lazy_parsers() diff --git a/vllm/parser/abstract_parser.py b/vllm/parser/abstract_parser.py index 96c04805bcda..ed052ceb7682 100644 --- a/vllm/parser/abstract_parser.py +++ b/vllm/parser/abstract_parser.py @@ -37,12 +37,11 @@ from vllm.logger import init_logger from vllm.reasoning.abs_reasoning_parsers import ReasoningParser from vllm.tokenizers import TokenizerLike -from vllm.tool_parsers.abstract_tool_parser import ToolParser +from vllm.tool_parsers.abstract_tool_parser import Tool, ToolParser from vllm.tool_parsers.streaming import ( extract_named_tool_call_streaming, extract_required_tool_call_streaming, ) -from vllm.tool_parsers.utils import Tool from vllm.utils import random_uuid logger = init_logger(__name__) @@ -90,19 +89,25 @@ class Parser: reasoning_parser_cls: type[ReasoningParser] | None = None tool_parser_cls: type[ToolParser] | None = None - def __init__(self, tokenizer: TokenizerLike, *args, **kwargs): - """ - Initialize the Parser. - - Args: - tokenizer: The tokenizer used by the model. This is required for - token-based parsing operations. - """ + def __init__( + self, + tokenizer: TokenizerLike, + tools: list[Tool] | None = None, + *args, + **kwargs, + ): self.model_tokenizer = tokenizer self._reasoning_parser: ReasoningParser | None = None self._tool_parser: ToolParser | None = None self._stream_state = StreamState() + if self.__class__.reasoning_parser_cls is not None: + self._reasoning_parser = self.__class__.reasoning_parser_cls( + tokenizer, *args, **kwargs + ) + if self.__class__.tool_parser_cls is not None: + self._tool_parser = self.__class__.tool_parser_cls(tokenizer, tools) + @cached_property def vocab(self) -> dict[str, int]: """Get the vocabulary mapping from tokens to IDs.""" @@ -767,34 +772,3 @@ def parse_delta( self._append_unstreamed_tool_args(delta_message) return delta_message - - -class _WrappedParser(DelegatingParser): - """ - A DelegatingParser subclass that instantiates parsers from class attributes. - - This class is used to dynamically create a parser that wraps individual - ReasoningParser and ToolParser classes. The class attributes - `reasoning_parser_cls` and `tool_parser_cls` should be set before - instantiation. - - Usage: - _WrappedParser.reasoning_parser_cls = MyReasoningParser - _WrappedParser.tool_parser_cls = MyToolParser - parser = _WrappedParser(tokenizer) - """ - - reasoning_parser_cls: type[ReasoningParser] | None = None - tool_parser_cls: type[ToolParser] | None = None - - def __init__( - self, tokenizer: TokenizerLike, tools: list[Tool] | None = None, **kwargs - ): - super().__init__(tokenizer) - # Instantiate the underlying parsers from class attributes - if self.__class__.reasoning_parser_cls is not None: - self._reasoning_parser = self.__class__.reasoning_parser_cls( - tokenizer, **kwargs - ) - if self.__class__.tool_parser_cls is not None: - self._tool_parser = self.__class__.tool_parser_cls(tokenizer, tools) diff --git a/vllm/parser/minimax_m2_parser.py b/vllm/parser/minimax_m2_parser.py deleted file mode 100644 index 34aaa7268446..000000000000 --- a/vllm/parser/minimax_m2_parser.py +++ /dev/null @@ -1,61 +0,0 @@ -# SPDX-License-Identifier: Apache-2.0 -# SPDX-FileCopyrightText: Copyright contributors to the vLLM project - -""" -MiniMax M2 Parser - A unified parser for MiniMax M2 models. - -This parser combines the existing MiniMaxM2ReasoningParser and -MinimaxM2ToolParser into a single unified interface by delegating -to those implementations. -""" - -from vllm.logger import init_logger -from vllm.parser.abstract_parser import DelegatingParser -from vllm.reasoning.minimax_m2_reasoning_parser import MiniMaxM2ReasoningParser -from vllm.tokenizers import TokenizerLike -from vllm.tool_parsers.abstract_tool_parser import ( - Tool, -) -from vllm.tool_parsers.minimax_m2_tool_parser import MinimaxM2ToolParser - -logger = init_logger(__name__) - - -class MiniMaxM2Parser(DelegatingParser): - """ - Unified parser for MiniMax M2 models that handles both reasoning - extraction and tool call parsing. - - This parser delegates to the existing implementations: - - MiniMaxM2ReasoningParser for reasoning extraction - - MinimaxM2ToolParser for tool call parsing - - MiniMax M2 models have two special behaviors: - 1. Reasoning: They don't generate start token, only end - token. All content before is reasoning, content after is the - actual response. - 2. Tool Calls: They use ... tags - with ... and ... - syntax. - """ - - # Class-level parser classes for compatibility - reasoning_parser_cls = MiniMaxM2ReasoningParser - tool_parser_cls = MinimaxM2ToolParser - - def __init__( - self, - tokenizer: TokenizerLike, - tools: list[Tool] | None = None, - *args, - **kwargs, - ): - super().__init__(tokenizer, *args, **kwargs) - - # Initialize the underlying parsers - self._reasoning_parser = MiniMaxM2ReasoningParser(tokenizer, *args, **kwargs) - self._tool_parser = MinimaxM2ToolParser(tokenizer, tools) - - logger.debug( - "vLLM Successfully initialized parser %s!", self.__class__.__name__ - ) diff --git a/vllm/parser/parser_manager.py b/vllm/parser/parser_manager.py index f8bded62d590..7afd39d4feab 100644 --- a/vllm/parser/parser_manager.py +++ b/vllm/parser/parser_manager.py @@ -3,14 +3,9 @@ from __future__ import annotations -import importlib -import os -from collections.abc import Callable from typing import TYPE_CHECKING from vllm.logger import init_logger -from vllm.utils.collection_utils import is_list_of -from vllm.utils.import_utils import import_from_path if TYPE_CHECKING: from vllm.parser.abstract_parser import Parser @@ -22,170 +17,10 @@ class ParserManager: """ - Central registry for Parser implementations. - - Supports two registration modes: - - Eager registration via `register_module` - - Lazy registration via `register_lazy_module` + Provides a unified Parser by composing individual reasoning and tool + parsers from their respective registries. """ - parsers: dict[str, type[Parser]] = {} - lazy_parsers: dict[str, tuple[str, str]] = {} # name -> (module_path, class_name) - - @classmethod - def get_parser_internal(cls, name: str) -> type[Parser]: - """ - Retrieve a registered or lazily registered Parser class. - - Args: - name: The registered name of the parser. - - Returns: - The Parser class. - - Raises: - KeyError: If no parser is found under the given name. - """ - if name in cls.parsers: - return cls.parsers[name] - - if name in cls.lazy_parsers: - return cls._load_lazy_parser(name) - - registered = ", ".join(cls.list_registered()) - raise KeyError(f"Parser '{name}' not found. Available parsers: {registered}") - - @classmethod - def _load_lazy_parser(cls, name: str) -> type[Parser]: - """Import and register a lazily loaded parser.""" - from vllm.parser.abstract_parser import Parser - - module_path, class_name = cls.lazy_parsers[name] - try: - mod = importlib.import_module(module_path) - parser_cls = getattr(mod, class_name) - if not issubclass(parser_cls, Parser): - raise TypeError( - f"{class_name} in {module_path} is not a Parser subclass." - ) - cls.parsers[name] = parser_cls # cache - return parser_cls - except Exception as e: - logger.exception( - "Failed to import lazy parser '%s' from %s: %s", - name, - module_path, - e, - ) - raise - - @classmethod - def _register_module( - cls, - module: type[Parser], - module_name: str | list[str] | None = None, - force: bool = True, - ) -> None: - """Register a Parser class immediately.""" - from vllm.parser.abstract_parser import Parser - - if not issubclass(module, Parser): - raise TypeError( - f"module must be subclass of Parser, but got {type(module)}" - ) - - if module_name is None: - module_names = [module.__name__] - elif isinstance(module_name, str): - module_names = [module_name] - elif is_list_of(module_name, str): - module_names = module_name - else: - raise TypeError("module_name must be str, list[str], or None.") - - for name in module_names: - if not force and name in cls.parsers: - existed = cls.parsers[name] - raise KeyError(f"{name} is already registered at {existed.__module__}") - cls.parsers[name] = module - - @classmethod - def register_lazy_module(cls, name: str, module_path: str, class_name: str) -> None: - """ - Register a lazy module mapping for delayed import. - - Example: - ParserManager.register_lazy_module( - name="minimax_m2", - module_path="vllm.parser.minimax_m2_parser", - class_name="MiniMaxM2Parser", - ) - """ - cls.lazy_parsers[name] = (module_path, class_name) - - @classmethod - def register_module( - cls, - name: str | list[str] | None = None, - force: bool = True, - module: type[Parser] | None = None, - ) -> type[Parser] | Callable[[type[Parser]], type[Parser]]: - """ - Register a Parser class. - - Can be used as a decorator or called directly. - - Usage: - @ParserManager.register_module("my_parser") - class MyParser(Parser): - ... - - Or: - ParserManager.register_module(module=MyParser) - """ - if not isinstance(force, bool): - raise TypeError(f"force must be a boolean, but got {type(force)}") - - # Immediate registration - if module is not None: - cls._register_module(module=module, module_name=name, force=force) - return module - - # Decorator usage - def _decorator(obj: type[Parser]) -> type[Parser]: - module_path = obj.__module__ - class_name = obj.__name__ - - if isinstance(name, str): - names = [name] - elif name is not None and is_list_of(name, str): - names = name - else: - names = [class_name] - - for n in names: - cls.lazy_parsers[n] = (module_path, class_name) - - return obj - - return _decorator - - @classmethod - def list_registered(cls) -> list[str]: - """Return names of all registered parsers.""" - return sorted(set(cls.parsers.keys()) | set(cls.lazy_parsers.keys())) - - @classmethod - def import_parser(cls, plugin_path: str) -> None: - """Import a user-defined parser from an arbitrary path.""" - module_name = os.path.splitext(os.path.basename(plugin_path))[0] - try: - import_from_path(module_name, plugin_path) - except Exception: - logger.exception( - "Failed to load module '%s' from %s.", module_name, plugin_path - ) - @classmethod def get_tool_parser( cls, @@ -246,12 +81,10 @@ def get_parser( model_name: str | None = None, ) -> type[Parser] | None: """ - Get a unified Parser that handles both reasoning and tool parsing. + Get a Parser that handles both reasoning and tool parsing. - This method checks if a unified Parser exists that can handle both - reasoning extraction and tool call parsing. If no unified parser - exists, it creates a DelegatingParser that wraps the individual - reasoning and tool parsers. + Composes individual reasoning and tool parsers into a single + DelegatingParser subclass. Args: tool_parser_name: The name of the tool parser. @@ -262,37 +95,9 @@ def get_parser( Returns: A Parser class, or None if neither parser is specified. """ - from vllm.parser.abstract_parser import _WrappedParser - if not tool_parser_name and not reasoning_parser_name: return None - # Strategy 1: If both names match, check for a unified parser with that name - if tool_parser_name and tool_parser_name == reasoning_parser_name: - try: - parser = cls.get_parser_internal(tool_parser_name) - logger.info( - "Using unified parser '%s' for both reasoning and tool parsing.", - tool_parser_name, - ) - return parser - except KeyError: - pass # No unified parser with this name - - # Strategy 2: Check for parser with either name - for name in [tool_parser_name, reasoning_parser_name]: - if name: - try: - parser = cls.get_parser_internal(name) - logger.info( - "Using unified parser '%s' for reasoning and tool parsing.", - name, - ) - return parser - except KeyError: - pass - - # Strategy 3: Create a DelegatingParser with the individual parser classes reasoning_parser_cls = cls.get_reasoning_parser(reasoning_parser_name) tool_parser_cls = cls.get_tool_parser( tool_parser_name, enable_auto_tools, model_name @@ -301,8 +106,13 @@ def get_parser( if reasoning_parser_cls is None and tool_parser_cls is None: return None - # Set the class-level attributes on the imported _WrappedParser - _WrappedParser.reasoning_parser_cls = reasoning_parser_cls - _WrappedParser.tool_parser_cls = tool_parser_cls + from vllm.parser.abstract_parser import DelegatingParser + + r_cls = reasoning_parser_cls + t_cls = tool_parser_cls + + class _Parser(DelegatingParser): + reasoning_parser_cls = r_cls + tool_parser_cls = t_cls - return _WrappedParser + return _Parser