diff --git a/python/semantic_kernel/skill_definition/functions_view.py b/python/semantic_kernel/skill_definition/functions_view.py index 4fdbb25918de..72a6af4d419a 100644 --- a/python/semantic_kernel/skill_definition/functions_view.py +++ b/python/semantic_kernel/skill_definition/functions_view.py @@ -43,4 +43,17 @@ def is_semantic(self, skill_name: str, function_name: str) -> bool: return as_sf def is_native(self, skill_name: str, function_name: str) -> bool: - return not self.is_semantic(skill_name, function_name) + as_sf = self._semantic_functions.get(skill_name, []) + as_sf = any(f.name == function_name for f in as_sf) + + as_nf = self._native_functions.get(skill_name, []) + as_nf = any(f.name == function_name for f in as_nf) + + if as_sf and as_nf: + raise KernelException( + KernelException.ErrorCodes.AmbiguousImplementation, + f"There are 2 functions with the same name: {function_name}." + f"One is native and the other semantic.", + ) + + return as_nf diff --git a/python/tests/unit/skill_definition/test_functions_view.py b/python/tests/unit/skill_definition/test_functions_view.py new file mode 100644 index 000000000000..76eb3b2a22d5 --- /dev/null +++ b/python/tests/unit/skill_definition/test_functions_view.py @@ -0,0 +1,152 @@ +# Copyright (c) Microsoft. All rights reserved. + +import pytest +from semantic_kernel.kernel_exception import KernelException +from semantic_kernel.skill_definition.function_view import FunctionView +from semantic_kernel.skill_definition.functions_view import FunctionsView, FunctionView + + +def test_add_semantic_function(): + view = FunctionView( + name="function1", + skill_name="skill1", + description="Semantic function", + parameters=[], + is_semantic=True, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(view) + semantic_functions = functions_view._semantic_functions.get("skill1") + assert len(semantic_functions) == 1 + assert semantic_functions[0] == view + + +def test_add_native_function(): + view = FunctionView( + name="function2", + skill_name="skill2", + description="Native function", + parameters=[], + is_semantic=False, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(view) + native_functions = functions_view._native_functions.get("skill2") + assert len(native_functions) == 1 + assert native_functions[0] == view + + +def test_add_multiple_functions(): + semantic_function = FunctionView( + name="function1", + skill_name="skill1", + description="Semantic function", + parameters=[], + is_semantic=True, + is_asynchronous=True, + ) + native_function = FunctionView( + name="function2", + skill_name="skill2", + description="Native function", + parameters=[], + is_semantic=False, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(semantic_function) + functions_view.add_function(native_function) + semantic_functions = functions_view._semantic_functions.get("skill1") + native_functions = functions_view._native_functions.get("skill2") + assert len(semantic_functions) == 1 + assert semantic_functions[0] == semantic_function + assert len(native_functions) == 1 + assert native_functions[0] == native_function + + +def test_is_semantic(): + semantic_function = FunctionView( + name="function1", + skill_name="skill1", + description="Semantic function", + parameters=[], + is_semantic=True, + is_asynchronous=True, + ) + native_function = FunctionView( + name="function2", + skill_name="skill2", + description="Native function", + parameters=[], + is_semantic=False, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(semantic_function) + functions_view.add_function(native_function) + assert functions_view.is_semantic("skill1", "function1") is True + assert functions_view.is_semantic("skill2", "function2") is False + assert functions_view.is_semantic("skill1", "unregistered_function") is False + + +def test_is_native(): + semantic_function = FunctionView( + name="function1", + skill_name="skill1", + description="Semantic function", + parameters=[], + is_semantic=True, + is_asynchronous=True, + ) + native_function = FunctionView( + name="function2", + skill_name="skill2", + description="Native function", + parameters=[], + is_semantic=False, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(semantic_function) + functions_view.add_function(native_function) + assert functions_view.is_native("skill1", "function1") is False + assert functions_view.is_native("skill2", "function2") is True + assert functions_view.is_native("skill2", "unregistered_function") is False + + +def test_ambiguous_implementation(): + semantic_function = FunctionView( + name="function1", + skill_name="skill1", + description="Semantic function", + parameters=[], + is_semantic=True, + is_asynchronous=True, + ) + native_function = FunctionView( + name="function1", + skill_name="skill1", + description="Native function", + parameters=[], + is_semantic=False, + is_asynchronous=True, + ) + functions_view = FunctionsView() + functions_view.add_function(semantic_function) + functions_view.add_function(native_function) + + with pytest.raises(KernelException) as exc_info: + functions_view.is_semantic("skill1", "function1") + + assert ( + exc_info.value.error_code == KernelException.ErrorCodes.AmbiguousImplementation + ) + + with pytest.raises(KernelException) as exc_info: + functions_view.is_native("skill1", "function1") + + assert ( + exc_info.value.error_code == KernelException.ErrorCodes.AmbiguousImplementation + )