diff --git a/python/semantic_kernel/utils/experimental_decorator.py b/python/semantic_kernel/utils/experimental_decorator.py new file mode 100644 index 000000000000..78682de23357 --- /dev/null +++ b/python/semantic_kernel/utils/experimental_decorator.py @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft. All rights reserved. + +import types +from typing import Callable, Type + + +def experimental_function(func: Callable) -> Callable: + if isinstance(func, types.FunctionType): + if func.__doc__: + func.__doc__ += "\n\nNote: This function is experimental and may change in the future." + else: + func.__doc__ = "Note: This function is experimental and may change in the future." + + func.is_experimental = True + + return func + + +def experimental_class(cls: Type) -> Type: + if isinstance(cls, type): + if cls.__doc__: + cls.__doc__ += "\n\nNote: This class is experimental and may change in the future." + else: + cls.__doc__ = "Note: This class is experimental and may change in the future." + + cls.is_experimental = True + + return cls diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 10a3e66dabcf..5bb684b71522 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -94,6 +94,20 @@ def decorated_native_function(self) -> str: return CustomPlugin +@pytest.fixture(scope="session") +def experimental_plugin_class(): + from semantic_kernel.functions.kernel_function_decorator import kernel_function + from semantic_kernel.utils.experimental_decorator import experimental_class + + @experimental_class + class ExperimentalPlugin: + @kernel_function(name="getLightStatus") + def decorated_native_function(self) -> str: + return "test" + + return ExperimentalPlugin + + @pytest.fixture(scope="session") def create_mock_function() -> Callable: from semantic_kernel.contents.streaming_text_content import StreamingTextContent diff --git a/python/tests/unit/functions/test_kernel_experimental_decorator.py b/python/tests/unit/functions/test_kernel_experimental_decorator.py new file mode 100644 index 000000000000..78148f2d576e --- /dev/null +++ b/python/tests/unit/functions/test_kernel_experimental_decorator.py @@ -0,0 +1,31 @@ +# # Copyright (c) Microsoft. All rights reserved. + +from semantic_kernel.utils.experimental_decorator import ( + experimental_function, +) + + +@experimental_function +def my_function() -> None: + """This is a sample function docstring.""" + pass + + +@experimental_function +def my_function_no_doc_string() -> None: + pass + + +def test_function_experimental_decorator() -> None: + assert ( + my_function.__doc__ + == "This is a sample function docstring.\n\nNote: This function is experimental and may change in the future." + ) # noqa: E501 + assert hasattr(my_function, "is_experimental") + assert my_function.is_experimental is True + + +def test_function_experimental_decorator_with_no_doc_string() -> None: + assert my_function_no_doc_string.__doc__ == "Note: This function is experimental and may change in the future." + assert hasattr(my_function_no_doc_string, "is_experimental") + assert my_function_no_doc_string.is_experimental is True diff --git a/python/tests/unit/functions/test_kernel_function_decorators.py b/python/tests/unit/functions/test_kernel_function_decorators.py index 8d57e49506c9..b7daa1a87da0 100644 --- a/python/tests/unit/functions/test_kernel_function_decorators.py +++ b/python/tests/unit/functions/test_kernel_function_decorators.py @@ -1,3 +1,5 @@ +# Copyright (c) Microsoft. All rights reserved. + from typing import TYPE_CHECKING, Annotated, Any, AsyncGenerator, AsyncIterable, Optional, Union import pytest diff --git a/python/tests/unit/kernel/test_kernel.py b/python/tests/unit/kernel/test_kernel.py index ca3cf26f9c04..b0c5066912f5 100644 --- a/python/tests/unit/kernel/test_kernel.py +++ b/python/tests/unit/kernel/test_kernel.py @@ -589,4 +589,14 @@ def test_instantiate_prompt_execution_settings_through_kernel(kernel_with_servic assert settings.service_id == "service" +# endregion +# experimental class decorator + + +def test_experimental_class_has_decorator_and_flag(experimental_plugin_class): + assert hasattr(experimental_plugin_class, "is_experimental") + assert experimental_plugin_class.is_experimental + assert "This class is experimental and may change in the future." in experimental_plugin_class.__doc__ + + # endregion