diff --git a/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py b/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py index ada6bcd112ee..0e1c8271637d 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/hf_prompt_execution_settings.py @@ -9,8 +9,11 @@ from transformers import GenerationConfig -imported = importlib.import_module("transformers") -ready = imported is not None and hasattr(imported, "GenerationConfig") +try: + imported = importlib.import_module("transformers") + ready = hasattr(imported, "GenerationConfig") +except ModuleNotFoundError: + ready = False class HuggingFacePromptExecutionSettings(PromptExecutionSettings): @@ -27,10 +30,13 @@ class HuggingFacePromptExecutionSettings(PromptExecutionSettings): def get_generation_config(self) -> "GenerationConfig": """Get the generation config.""" - from transformers import GenerationConfig - if not ready: - raise ImportError("transformers is not installed.") + raise ImportError( + "transformers is not installed. Install the 'hugging_face' extra with " + "`pip install semantic-kernel[hugging_face]`." + ) + + from transformers import GenerationConfig return GenerationConfig( **self.model_dump( diff --git a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py index 5dfa7d0aadb1..b72618eef5a7 100644 --- a/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py +++ b/python/semantic_kernel/connectors/ai/hugging_face/services/hf_text_completion.py @@ -11,8 +11,15 @@ else: from typing_extensions import override # pragma: no cover -import torch -from transformers import AutoTokenizer, TextIteratorStreamer, pipeline +try: + import torch + from transformers import AutoTokenizer, TextIteratorStreamer, pipeline +except ModuleNotFoundError as exc: + missing_package = exc.name or "Hugging Face dependencies" + raise ImportError( + f"{missing_package} is not installed. Install the 'hugging_face' extra with " + "`pip install semantic-kernel[hugging_face]`." + ) from exc from semantic_kernel.connectors.ai.hugging_face.hf_prompt_execution_settings import HuggingFacePromptExecutionSettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings diff --git a/python/semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py b/python/semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py index 9bafaa38b31f..df5e2348fc3d 100644 --- a/python/semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py +++ b/python/semantic_kernel/connectors/ai/onnx/services/onnx_gen_ai_completion_base.py @@ -37,7 +37,9 @@ def __init__(self, ai_model_path: str, **kwargs) -> None: ServiceInitializationError: When model cannot be loaded """ if not ready: - raise ImportError("onnxruntime-genai is not installed.") + raise ImportError( + "onnxruntime-genai is not installed. Install the 'onnx' extra with `pip install semantic-kernel[onnx]`." + ) try: json_gen_ai_config = os.path.join(ai_model_path + "/genai_config.json") with open(json_gen_ai_config) as file: diff --git a/python/tests/unit/connectors/test_optional_dependencies.py b/python/tests/unit/connectors/test_optional_dependencies.py new file mode 100644 index 000000000000..8cccfdea98a7 --- /dev/null +++ b/python/tests/unit/connectors/test_optional_dependencies.py @@ -0,0 +1,87 @@ +# Copyright (c) Microsoft. All rights reserved. + +import subprocess +import sys + +import pytest + + +@pytest.mark.parametrize( + ("module_name", "class_name", "package_name", "extra_name", "method_name", "load_directly"), + [ + ( + "semantic_kernel.connectors.ai.hugging_face.hf_prompt_execution_settings", + "HuggingFacePromptExecutionSettings", + "transformers", + "hugging_face", + "get_generation_config", + True, + ), + ( + "semantic_kernel.connectors.ai.onnx.services.onnx_gen_ai_completion_base", + "OnnxGenAICompletionBase", + "onnxruntime_genai", + "onnx", + "", + False, + ), + ], +) +def test_missing_optional_dependency_names_install_extra( + module_name: str, + class_name: str, + package_name: str, + extra_name: str, + method_name: str, + load_directly: bool, +) -> None: + script = """ +import importlib +import importlib.abc +import importlib.util +from pathlib import Path +import sys + +class MissingDependencyFinder(importlib.abc.MetaPathFinder): + def find_spec(self, fullname, path, target=None): + if fullname == sys.argv[3] or fullname.startswith(f"{sys.argv[3]}."): + raise ModuleNotFoundError(f"No module named '{sys.argv[3]}'", name=sys.argv[3]) + return None + +sys.meta_path.insert(0, MissingDependencyFinder()) +try: + if sys.argv[6] == "True": + package_spec = importlib.util.find_spec("semantic_kernel") + module_path = Path(package_spec.origin).parent.joinpath(*sys.argv[1].split(".")[1:]).with_suffix(".py") + module_spec = importlib.util.spec_from_file_location("optional_dependency_module", module_path) + module = importlib.util.module_from_spec(module_spec) + module_spec.loader.exec_module(module) + else: + module = importlib.import_module(sys.argv[1]) + connector = getattr(module, sys.argv[2])() if sys.argv[5] else getattr(module, sys.argv[2])("unused") + if sys.argv[5]: + getattr(connector, sys.argv[5])() +except ImportError as exc: + print(exc) +else: + raise AssertionError("Expected an ImportError for the blocked optional dependency") +""" + result = subprocess.run( + [ + sys.executable, + "-c", + script, + module_name, + class_name, + package_name, + extra_name, + method_name, + str(load_directly), + ], + capture_output=True, + check=True, + text=True, + ) + + assert package_name.replace("_", "-") in result.stdout + assert f"semantic-kernel[{extra_name}]" in result.stdout