11# SPDX-License-Identifier: Apache-2.0
22# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
33
4+ import importlib
45import os
56from abc import abstractmethod
67from collections .abc import Callable , Sequence
@@ -129,50 +130,117 @@ def prepare_structured_tag(
129130
130131
131132class ReasoningParserManager :
132- reasoning_parsers : dict [str , type ] = {}
133+ """
134+ Central registry for ReasoningParser implementations.
135+
136+ Supports two registration modes:
137+ - Eager registration via `register_module`
138+ - Lazy registration via `register_lazy_module`
139+
140+ Each reasoning parser must inherit from `ReasoningParser`.
141+ """
142+
143+ reasoning_parsers : dict [str , type [ReasoningParser ]] = {}
144+ lazy_parsers : dict [str , tuple [str , str ]] = {} # name -> (module_path, class_name)
133145
134146 @classmethod
135- def get_reasoning_parser (cls , name : str | None ) -> type [ReasoningParser ]:
147+ def get_reasoning_parser (cls , name : str ) -> type [ReasoningParser ]:
136148 """
137- Get reasoning parser by name which is registered by `register_module`.
149+ Retrieve a registered or lazily registered ReasoningParser class.
150+
151+ If the parser is lazily registered, it will be imported and cached
152+ on first access.
138153
139- Raise a KeyError exception if the name is not registered.
154+ Raises:
155+ KeyError: if no parser is found under the given name.
140156 """
141157 if name in cls .reasoning_parsers :
142158 return cls .reasoning_parsers [name ]
143159
144- raise KeyError (f"reasoning helper: '{ name } ' not found in reasoning_parsers" )
160+ if name in cls .lazy_parsers :
161+ return cls ._load_lazy_parser (name )
162+
163+ raise KeyError (f"Reasoning parser '{ name } ' not found." )
164+
165+ @classmethod
166+ def list_registered (cls ) -> list [str ]:
167+ """Return names of all eagerly and lazily registered reasoning parsers."""
168+ return sorted (set (cls .reasoning_parsers .keys ()) | set (cls .lazy_parsers .keys ()))
169+
170+ @classmethod
171+ def _load_lazy_parser (cls , name : str ) -> type [ReasoningParser ]:
172+ """Import and register a lazily loaded reasoning parser."""
173+ module_path , class_name = cls .lazy_parsers [name ]
174+ try :
175+ mod = importlib .import_module (module_path )
176+ parser_cls = getattr (mod , class_name )
177+ if not issubclass (parser_cls , ReasoningParser ):
178+ raise TypeError (
179+ f"{ class_name } in { module_path } is not a ReasoningParser subclass."
180+ )
181+
182+ cls .reasoning_parsers [name ] = parser_cls # cache
183+ return parser_cls
184+ except Exception as e :
185+ logger .exception (
186+ "Failed to import lazy reasoning parser '%s' from %s: %s" ,
187+ name ,
188+ module_path ,
189+ e ,
190+ )
191+ raise
145192
146193 @classmethod
147194 def _register_module (
148195 cls ,
149- module : type ,
196+ module : type [ ReasoningParser ] ,
150197 module_name : str | list [str ] | None = None ,
151198 force : bool = True ,
152199 ) -> None :
200+ """Register a ReasoningParser class immediately."""
153201 if not issubclass (module , ReasoningParser ):
154202 raise TypeError (
155203 f"module must be subclass of ReasoningParser, but got { type (module )} "
156204 )
205+
157206 if module_name is None :
158- module_name = module .__name__
159- if isinstance (module_name , str ):
160- module_name = [module_name ]
161- for name in module_name :
207+ module_names = [module .__name__ ]
208+ elif isinstance (module_name , str ):
209+ module_names = [module_name ]
210+ elif is_list_of (module_name , str ):
211+ module_names = module_name
212+ else :
213+ raise TypeError ("module_name must be str, list[str], or None." )
214+
215+ for name in module_names :
162216 if not force and name in cls .reasoning_parsers :
163- existed_module = cls .reasoning_parsers [name ]
164- raise KeyError (
165- f"{ name } is already registered at { existed_module .__module__ } "
166- )
217+ existed = cls .reasoning_parsers [name ]
218+ raise KeyError (f"{ name } is already registered at { existed .__module__ } " )
167219 cls .reasoning_parsers [name ] = module
168220
221+ @classmethod
222+ def register_lazy_module (cls , name : str , module_path : str , class_name : str ) -> None :
223+ """
224+ Register a lazy module mapping for delayed import.
225+
226+ Example:
227+ ReasoningParserManager.register_lazy_module(
228+ name="qwen3",
229+ module_path="vllm.reasoning.parsers.qwen3_reasoning_parser",
230+ class_name="Qwen3ReasoningParser",
231+ )
232+ """
233+ cls .lazy_parsers [name ] = (module_path , class_name )
234+
169235 @classmethod
170236 def register_module (
171237 cls ,
172238 name : str | list [str ] | None = None ,
173239 force : bool = True ,
174- module : type | None = None ,
175- ) -> type | Callable :
240+ module : type [ReasoningParser ] | None = None ,
241+ ) -> (
242+ type [ReasoningParser ] | Callable [[type [ReasoningParser ]], type [ReasoningParser ]]
243+ ):
176244 """
177245 Register module with the given name or name list. it can be used as a
178246 decoder(with module as None) or normal function(with module as not
@@ -181,24 +249,29 @@ def register_module(
181249 if not isinstance (force , bool ):
182250 raise TypeError (f"force must be a boolean, but got { type (force )} " )
183251
184- # raise the error ahead of time
185- if not (name is None or isinstance (name , str ) or is_list_of (name , str )):
186- raise TypeError (
187- "name must be None, an instance of str, or a sequence of str, "
188- f"but got { type (name )} "
189- )
190-
191- # use it as a normal method: x.register_module(module=SomeClass)
252+ # Immediate registration (explicit call)
192253 if module is not None :
193254 cls ._register_module (module = module , module_name = name , force = force )
194255 return module
195256
196- # use it as a decorator: @x.register_module()
197- def _register (module ):
198- cls ._register_module (module = module , module_name = name , force = force )
199- return module
257+ # Decorator usage
258+ def _decorator (obj : type [ReasoningParser ]) -> type [ReasoningParser ]:
259+ module_path = obj .__module__
260+ class_name = obj .__name__
261+
262+ if isinstance (name , str ):
263+ names = [name ]
264+ elif is_list_of (name , str ):
265+ names = name
266+ else :
267+ names = [class_name ]
268+
269+ for n in names :
270+ cls .lazy_parsers [n ] = (module_path , class_name )
271+
272+ return obj
200273
201- return _register
274+ return _decorator
202275
203276 @classmethod
204277 def import_reasoning_parser (cls , plugin_path : str ) -> None :
0 commit comments