[Frontend] Lazily initialize chat media connectors (#49914)

Signed-off-by: Andreas Karatzas <akaratza@amd.com>

Andreas Karatzas committed Jul 30, 2026 at 01:56 UTC e04a30a77cb17d5a9753c53c51ee30bba887053c
2 files changed +43 -11
tests/entrypoints/unit_tests/test_chat_utils.py
+24
@@ -15,6 +15,7 @@ from vllm.assets.image import ImageAsset
15 from vllm.assets.video import VideoAsset
16 from vllm.config import ModelConfig
17 from vllm.entrypoints.chat_utils import (
18 + MEDIA_CONNECTOR_REGISTRY,
19 AsyncMultiModalItemTracker,
20 ConversationMessage,
21 _postprocess_messages,
@@ -709,6 +710,29 @@ def test_parse_chat_messages_empty_system(
710 ]
711
712
713 +@pytest.mark.asyncio
714 +async def test_text_only_chat_does_not_initialize_media_connector(
715 + mistral_model_config,
716 + monkeypatch,
717 +):
718 + load_connector = MagicMock()
719 + monkeypatch.setattr(MEDIA_CONNECTOR_REGISTRY, "load", load_connector)
720 + messages = [{"role": "user", "content": "Who are you?"}]
721 +
722 + parse_chat_messages(
723 + messages,
724 + mistral_model_config,
725 + content_format="string",
726 + )
727 + await parse_chat_messages_async(
728 + messages,
729 + mistral_model_config,
730 + content_format="string",
731 + )
732 +
733 + load_connector.assert_not_called()
734 +
735 +
736 @pytest.mark.asyncio
737 async def test_parse_chat_messages_single_image_async(
738 phi3v_model_config,
vllm/entrypoints/chat_utils.py
+19 -11
@@ -945,16 +945,19 @@ class MultiModalContentParser(BaseMultiModalContentParser):
945 super().__init__()
946
947 self._tracker = tracker
948 + self._mm_processor_kwargs = mm_processor_kwargs
949
949 - self._connector: MediaConnector = MEDIA_CONNECTOR_REGISTRY.load(
950 + @cached_property
951 + def _connector(self) -> MediaConnector:
952 + # Connector setup may probe VLLM_MEDIA_CACHE. Defer it until a request
953 + # actually contains media so text-only parsing never blocks on that I/O.
954 + return MEDIA_CONNECTOR_REGISTRY.load(
955 envs.VLLM_MEDIA_CONNECTOR,
951 - media_io_kwargs=tracker.media_io_kwargs,
952 - allowed_local_media_path=tracker.allowed_local_media_path,
953 - allowed_media_domains=tracker.allowed_media_domains,
956 + media_io_kwargs=self._tracker.media_io_kwargs,
957 + allowed_local_media_path=self._tracker.allowed_local_media_path,
958 + allowed_media_domains=self._tracker.allowed_media_domains,
959 )
960
956 - self._mm_processor_kwargs = mm_processor_kwargs
957 -
961 @property
962 def model_config(self) -> ModelConfig:
963 return self._tracker.model_config
@@ -1097,13 +1100,18 @@ class AsyncMultiModalContentParser(BaseMultiModalContentParser):
1100 super().__init__()
1101
1102 self._tracker = tracker
1100 - self._connector: MediaConnector = MEDIA_CONNECTOR_REGISTRY.load(
1103 + self._mm_processor_kwargs: dict[str, Any] | None = mm_processor_kwargs
1104 +
1105 + @cached_property
1106 + def _connector(self) -> MediaConnector:
1107 + # Connector setup may probe VLLM_MEDIA_CACHE. Defer it until a request
1108 + # actually contains media so text-only parsing never blocks on that I/O.
1109 + return MEDIA_CONNECTOR_REGISTRY.load(
1110 envs.VLLM_MEDIA_CONNECTOR,
1102 - media_io_kwargs=tracker.media_io_kwargs,
1103 - allowed_local_media_path=tracker.allowed_local_media_path,
1104 - allowed_media_domains=tracker.allowed_media_domains,
1111 + media_io_kwargs=self._tracker.media_io_kwargs,
1112 + allowed_local_media_path=self._tracker.allowed_local_media_path,
1113 + allowed_media_domains=self._tracker.allowed_media_domains,
1114 )
1106 - self._mm_processor_kwargs: dict[str, Any] | None = mm_processor_kwargs
1115
1116 @property
1117 def model_config(self) -> ModelConfig: