Skip to content

Commit 5098ba3

Browse files
authoredJan 24, 2025
Python: Improve handling for kernel plugin from file. (#10286)
### Motivation and Context This PR addresses an issue in the `KernelPlugin.from_directory()` method where classes without any `@kernel_function` decorated methods are still being initialized during plugin loading. The fix makes sure that only classes with at least one `@kernel_function` decorated method are instantiated and included as plugins. Classes without `@kernel_function` methods are now skipped entirely. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description This PR: - Updates the `from_python_file` to inspect each class for `@kernel_function` decorated methods before instantiation. - Updates the `add_plugin` method `plugin_name` to make sure it is indeed a string, per the type hint. - Adds a unit test to exercise the new behavior - Closes #10280 <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [X] The code builds clean without any errors or warnings - [X] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [X] All unit tests pass, and I have added new tests where possible - [X] I didn't break anyone 😄
1 parent 1ce010b commit 5098ba3

File tree

3 files changed

+18
-1
lines changed

3 files changed

+18
-1
lines changed
 

‎python/semantic_kernel/functions/kernel_function_extension.py

+2
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,8 @@ def add_plugin(
8686
return self.plugins[plugin.name]
8787
if not plugin_name:
8888
raise ValueError("plugin_name must be provided if a plugin is not supplied.")
89+
if not isinstance(plugin_name, str):
90+
raise TypeError("plugin_name must be a string.")
8991
if plugin:
9092
self.plugins[plugin_name] = KernelPlugin.from_object(
9193
plugin_name=plugin_name, plugin_instance=plugin, description=description

‎python/semantic_kernel/functions/kernel_plugin.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,16 @@ def from_python_file(
398398
for name, cls_instance in inspect.getmembers(module, inspect.isclass):
399399
if cls_instance.__module__ != module_name:
400400
continue
401-
instance = getattr(module, name)(**class_init_arguments.get(name, {}) if class_init_arguments else {})
401+
# Check whether this class has at least one @kernel_function decorated method
402+
has_kernel_function = False
403+
for _, method in inspect.getmembers(cls_instance, inspect.isfunction):
404+
if getattr(method, "__kernel_function__", False):
405+
has_kernel_function = True
406+
break
407+
if not has_kernel_function:
408+
continue
409+
init_args = class_init_arguments.get(name, {}) if class_init_arguments else {}
410+
instance = getattr(module, name)(**init_args)
402411
return cls.from_object(plugin_name=plugin_name, description=description, plugin_instance=instance)
403412
raise PluginInitializationError(f"No class found in file: {py_file}")
404413

‎python/tests/unit/kernel/test_kernel.py

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

33
import os
4+
from pathlib import Path
45
from typing import Union
56
from unittest.mock import AsyncMock, MagicMock, patch
67

@@ -483,6 +484,11 @@ def test_plugin_name_error(kernel: Kernel):
483484
kernel.add_plugin(" ", None)
484485

485486

487+
def test_plugin_name_not_string_error(kernel: Kernel):
488+
with pytest.raises(TypeError):
489+
kernel.add_plugin(" ", plugin_name=Path(__file__).parent)
490+
491+
486492
def test_plugins_add_plugins(kernel: Kernel):
487493
plugin1 = KernelPlugin(name="TestPlugin")
488494
plugin2 = KernelPlugin(name="TestPlugin2")

0 commit comments

Comments
 (0)