Skip to content

Commit 56bdf85

Browse files
authored
[Feature] Avoid eager import of the "mistral_common" package. (#40043)
Signed-off-by: Neil Schemenauer <nas@arctrix.com>
1 parent eba7306 commit 56bdf85

5 files changed

Lines changed: 47 additions & 23 deletions

File tree

vllm/entrypoints/openai/chat_completion/serving.py

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,13 +73,9 @@
7373
from vllm.renderers import ChatParams
7474
from vllm.sampling_params import BeamSearchParams, SamplingParams
7575
from vllm.tokenizers import TokenizerLike
76-
from vllm.tool_parsers.mistral_tool_parser import (
77-
MistralToolCall,
78-
MistralToolParser,
79-
)
8076
from vllm.tool_parsers.utils import partial_json_loads
8177
from vllm.utils.collection_utils import as_list
82-
from vllm.utils.mistral import is_mistral_tokenizer
78+
from vllm.utils.mistral import is_mistral_tokenizer, is_mistral_tool_parser
8379

8480
if TYPE_CHECKING:
8581
from vllm.entrypoints.serve.render.serving import OpenAIServingRender
@@ -143,10 +139,12 @@ def __init__(
143139
enable_auto_tools=enable_auto_tools,
144140
model_name=self.model_config.model,
145141
)
146-
_is_mistral_tool_parser = self.tool_parser is not None and issubclass(
147-
self.tool_parser, MistralToolParser
148-
)
149-
if _is_mistral_tool_parser and self.reasoning_parser_cls is not None:
142+
if (
143+
is_mistral_tool_parser(self.tool_parser)
144+
and self.reasoning_parser_cls is not None
145+
):
146+
from vllm.tool_parsers.mistral_tool_parser import MistralToolParser
147+
150148
MistralToolParser.model_can_reason = True
151149

152150
self.exclude_tools_when_tool_choice_none = exclude_tools_when_tool_choice_none
@@ -823,6 +821,10 @@ async def chat_completion_stream_generator(
823821
harmony_tools_streamed[i] |= tools_streamed_flag
824822
# Mistral grammar path: combined reasoning + tool streaming
825823
elif is_mistral_grammar_path:
824+
from vllm.tool_parsers.mistral_tool_parser import (
825+
MistralToolParser,
826+
)
827+
826828
assert tool_parser is not None
827829
assert isinstance(tool_parser, MistralToolParser)
828830
assert reasoning_end_arr is not None
@@ -904,6 +906,10 @@ async def chat_completion_stream_generator(
904906
else:
905907
# Generate ID based on tokenizer type
906908
if is_mistral_tokenizer(tokenizer):
909+
from vllm.tool_parsers.mistral_tool_parser import (
910+
MistralToolCall,
911+
)
912+
907913
tool_call_id = MistralToolCall.generate_random_id()
908914
else:
909915
tool_call_id = make_tool_call_id(
@@ -1275,8 +1281,6 @@ async def chat_completion_full_generator(
12751281
request_metadata: RequestResponseMetadata,
12761282
reasoning_parser: ReasoningParser | None = None,
12771283
) -> ErrorResponse | ChatCompletionResponse:
1278-
from vllm.tokenizers.mistral import MistralTokenizer
1279-
12801284
created_time = int(time.time())
12811285
final_res: RequestOutput | None = None
12821286

@@ -1393,12 +1397,17 @@ async def chat_completion_full_generator(
13931397
enable_auto_tools=self.enable_auto_tools,
13941398
tool_parser_cls=self.tool_parser,
13951399
)
1396-
tool_call_class = (
1397-
MistralToolCall if is_mistral_tokenizer(tokenizer) else ToolCall
1398-
)
1400+
if is_mistral_tokenizer(tokenizer):
1401+
from vllm.tool_parsers.mistral_tool_parser import MistralToolCall
1402+
1403+
tool_call_class: type[ToolCall] = MistralToolCall
1404+
else:
1405+
tool_call_class = ToolCall
13991406

14001407
use_mistral_tool_parser = request._grammar_from_tool_parser
14011408
if use_mistral_tool_parser:
1409+
from vllm.tool_parsers.mistral_tool_parser import MistralToolParser
1410+
14021411
tool_call_items = MistralToolParser.build_non_streaming_tool_calls(
14031412
tool_calls
14041413
)
@@ -1436,7 +1445,7 @@ async def chat_completion_full_generator(
14361445
# Generate ID using the correct format (kimi_k2 or random),
14371446
# but leave it to the class if it's Mistral to preserve
14381447
# 9-char IDs
1439-
if isinstance(tokenizer, MistralTokenizer):
1448+
if is_mistral_tokenizer(tokenizer):
14401449
tool_call_class_items.append(tool_call_class(function=tc))
14411450
else:
14421451
generated_id = make_tool_call_id(
@@ -1469,7 +1478,7 @@ async def chat_completion_full_generator(
14691478
# Generate ID using the correct format (kimi_k2 or random),
14701479
# but leave it to the class if it's Mistral to preserve
14711480
# 9-char IDs
1472-
if isinstance(tokenizer, MistralTokenizer):
1481+
if is_mistral_tokenizer(tokenizer):
14731482
tool_call_class_items.append(
14741483
tool_call_class(function=tool_call)
14751484
)
@@ -1519,7 +1528,7 @@ async def chat_completion_full_generator(
15191528
# Generate ID using the correct format (kimi_k2 or random),
15201529
# but leave it to the class if it's Mistral to preserve
15211530
# 9-char IDs
1522-
if isinstance(tokenizer, MistralTokenizer):
1531+
if is_mistral_tokenizer(tokenizer):
15231532
tool_call_items.append(tool_call_class(function=tc))
15241533
else:
15251534
generated_id = make_tool_call_id(

vllm/entrypoints/openai/engine/serving.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,14 +65,14 @@
6565
from vllm.sampling_params import BeamSearchParams, SamplingParams
6666
from vllm.tokenizers import TokenizerLike
6767
from vllm.tool_parsers import ToolParser
68-
from vllm.tool_parsers.mistral_tool_parser import MistralToolParser
6968
from vllm.tracing import (
7069
contains_trace_headers,
7170
extract_trace_headers,
7271
log_tracing_disabled_warning,
7372
)
7473
from vllm.utils import random_uuid
7574
from vllm.utils.async_utils import collect_from_async_generator
75+
from vllm.utils.mistral import is_mistral_tool_parser
7676

7777
logger = init_logger(__name__)
7878

@@ -615,8 +615,7 @@ def _parse_tool_calls_from_content(
615615
# let the parser handle the output.
616616
use_mistral_tool_parser = (
617617
isinstance(request, ChatCompletionRequest)
618-
and tool_parser_cls is not None
619-
and issubclass(tool_parser_cls, MistralToolParser)
618+
and is_mistral_tool_parser(tool_parser_cls)
620619
and request._grammar_from_tool_parser
621620
)
622621

vllm/entrypoints/serve/render/serving.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@
5555
prompt_to_seq,
5656
)
5757
from vllm.tool_parsers import ToolParser
58-
from vllm.tool_parsers.mistral_tool_parser import MistralToolParser
5958
from vllm.utils import random_uuid
60-
from vllm.utils.mistral import is_mistral_tokenizer
59+
from vllm.utils.mistral import is_mistral_tokenizer, is_mistral_tool_parser
6160
from vllm.utils.mistral import mt as _mt
6261

6362
logger = init_logger(__name__)
@@ -582,7 +581,7 @@ async def preprocess_chat(
582581
tool_choice = getattr(request, "tool_choice", "none")
583582
tokenizer = renderer.get_tokenizer()
584583
is_mistral_grammar_eligible = (
585-
issubclass(tool_parser, MistralToolParser)
584+
is_mistral_tool_parser(tool_parser)
586585
and is_mistral_tokenizer(tokenizer)
587586
and tokenizer.supports_grammar
588587
)

vllm/tool_parsers/mistral_tool_parser.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,8 @@ class MistralToolParser(ToolParser):
118118
set.
119119
"""
120120

121+
IS_MISTRAL_TOOL_PARSER = True # used by vllm.utils.mistral
122+
121123
# Used to generate correct grammar in `adjust_request`
122124
model_can_reason: bool = False
123125

vllm/utils/mistral.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
if TYPE_CHECKING:
1313
# if type checking, eagerly import the module
1414
import vllm.tokenizers.mistral as mt
15+
import vllm.tool_parsers.mistral_tool_parser as mtp
1516
else:
1617
mt = LazyLoader("mt", globals(), "vllm.tokenizers.mistral")
18+
mtp = LazyLoader("mtp", globals(), "vllm.tool_parsers.mistral_tool_parser")
1719

1820

1921
def is_mistral_tokenizer(obj: TokenizerLike | None) -> TypeGuard[mt.MistralTokenizer]:
@@ -26,3 +28,16 @@ def is_mistral_tokenizer(obj: TokenizerLike | None) -> TypeGuard[mt.MistralToken
2628
getattr(cls, "IS_MISTRAL_TOKENIZER", False)
2729
and isinstance(obj, mt.MistralTokenizer)
2830
)
31+
32+
33+
def is_mistral_tool_parser(cls: type | None) -> bool:
34+
"""Return true if *cls* is (a subclass of) MistralToolParser.
35+
36+
Uses a class attribute check so that importing
37+
``vllm.tool_parsers.mistral_tool_parser`` — and transitively
38+
``mistral_common`` — is not required.
39+
"""
40+
return bool(
41+
getattr(cls, "IS_MISTRAL_TOOL_PARSER", False)
42+
and issubclass(cls, mtp.MistralToolParser) # type: ignore[arg-type]
43+
)

0 commit comments

Comments
 (0)