chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,128 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any
import pytest
from semantic_kernel.contents.kernel_content import KernelContent
from semantic_kernel.exceptions.function_exceptions import FunctionResultError
from semantic_kernel.functions.function_result import FunctionResult
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
def test_function_result_str_with_value():
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False),
value="test_value",
)
assert str(result) == "test_value"
def test_function_result_str_with_list_value():
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False),
value=["test_value1", "test_value2"],
)
assert str(result) == "test_value1,test_value2"
def test_function_result_str_with_kernel_content_list():
class MockKernelContent(KernelContent):
def __str__(self) -> str:
return "mock_content"
def to_element(self) -> Any:
pass
@classmethod
def from_element(cls: type["KernelContent"], element: Any) -> "KernelContent":
pass
def to_dict(self) -> dict[str, Any]:
pass
content = MockKernelContent(inner_content="inner_content")
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False), value=[content]
)
assert str(result) == "mock_content"
def test_function_result_str_with_dict_value():
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False),
value={"key1": "value1", "key2": "value2"},
)
assert str(result) == "value2"
def test_function_result_str_empty_value():
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False), value=None
)
assert str(result) == ""
def test_function_result_str_with_conversion_error():
class Unconvertible:
def __str__(self):
raise ValueError("Cannot convert to string")
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False),
value=Unconvertible(),
)
with pytest.raises(FunctionResultError, match="Failed to convert value to string"):
str(result)
def test_function_result_get_inner_content_with_list():
class MockKernelContent(KernelContent):
def __str__(self) -> str:
return "mock_content"
def to_element(self) -> Any:
pass
@classmethod
def from_element(cls: type["KernelContent"], element: Any) -> "KernelContent":
pass
def to_dict(self) -> dict[str, Any]:
pass
content = MockKernelContent(inner_content="inner_content")
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False), value=[content]
)
assert result.get_inner_content() == "inner_content"
def test_function_result_get_inner_content_with_kernel_content():
class MockKernelContent(KernelContent):
def __str__(self) -> str:
return "mock_content"
def to_element(self) -> Any:
pass
@classmethod
def from_element(cls: type["KernelContent"], element: Any) -> "KernelContent":
pass
def to_dict(self) -> dict[str, Any]:
pass
content = MockKernelContent(inner_content="inner_content")
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False), value=content
)
assert result.get_inner_content() == "inner_content"
def test_function_result_get_inner_content_no_inner_content():
result = FunctionResult(
function=KernelFunctionMetadata(name="test_function", is_prompt=False, is_asynchronous=False),
value="test_value",
)
assert result.get_inner_content() is None
@@ -0,0 +1,181 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.functions.kernel_arguments import KernelArguments
def test_kernel_arguments():
kargs = KernelArguments()
assert kargs is not None
assert kargs.execution_settings is None
assert not kargs.keys()
def test_kernel_arguments_with_input():
kargs = KernelArguments(input=10)
assert kargs is not None
assert kargs["input"] == 10
def test_kernel_arguments_with_input_get():
kargs = KernelArguments(input=10)
assert kargs is not None
assert kargs.get("input", None) == 10
assert not kargs.get("input2", None)
def test_kernel_arguments_keys():
kargs = KernelArguments(input=10)
assert kargs is not None
assert list(kargs.keys()) == ["input"]
def test_kernel_arguments_with_execution_settings():
test_pes = PromptExecutionSettings(service_id="test")
kargs = KernelArguments(settings=[test_pes])
assert kargs is not None
assert kargs.execution_settings == {"test": test_pes}
def test_kernel_arguments_bool():
# An empty KernelArguments object should return False
assert not KernelArguments()
# An KernelArguments object with keyword arguments should return True
assert KernelArguments(input=10)
# An KernelArguments object with execution_settings should return True
assert KernelArguments(settings=PromptExecutionSettings(service_id="test"))
# An KernelArguments object with both keyword arguments and execution_settings should return True
assert KernelArguments(input=10, settings=PromptExecutionSettings(service_id="test"))
@pytest.mark.parametrize(
"lhs, rhs, expected_dict, expected_settings_keys",
[
# Merging different keys
(KernelArguments(a=1), KernelArguments(b=2), {"a": 1, "b": 2}, None),
# RHS overwrites when keys duplicate
(KernelArguments(a=1), KernelArguments(a=99), {"a": 99}, None),
# Merging with a plain dict
(KernelArguments(a=1), {"b": 2}, {"a": 1, "b": 2}, None),
# Merging execution_settings together
(
KernelArguments(settings=PromptExecutionSettings(service_id="s1")),
KernelArguments(settings=PromptExecutionSettings(service_id="s2")),
{},
["s1", "s2"],
),
# Same service_id is overwritten by RHS
(
KernelArguments(settings=PromptExecutionSettings(service_id="shared")),
KernelArguments(settings=PromptExecutionSettings(service_id="shared")),
{},
["shared"],
),
],
)
def test_kernel_arguments_or_operator(lhs, rhs, expected_dict, expected_settings_keys):
"""Test the __or__ operator (lhs | rhs) with various argument combinations."""
result = lhs | rhs
assert isinstance(result, KernelArguments)
assert dict(result) == expected_dict
if expected_settings_keys is None:
assert result.execution_settings is None
else:
assert sorted(result.execution_settings.keys()) == sorted(expected_settings_keys)
@pytest.mark.parametrize("rhs", [42, "foo", None])
def test_kernel_arguments_or_operator_with_invalid_type(rhs):
"""Test the __or__ operator with an invalid type raises TypeError."""
with pytest.raises(TypeError):
KernelArguments() | rhs
@pytest.mark.parametrize(
"lhs, rhs, expected_dict, expected_settings_keys",
[
# Dict merge (in-place)
(KernelArguments(a=1), {"b": 2}, {"a": 1, "b": 2}, None),
# Merging between KernelArguments
(KernelArguments(a=1), KernelArguments(b=2), {"a": 1, "b": 2}, None),
# Retain existing execution_settings after dict merge
(KernelArguments(a=1, settings=PromptExecutionSettings(service_id="s1")), {"b": 2}, {"a": 1, "b": 2}, ["s1"]),
# In-place merge of execution_settings
(
KernelArguments(settings=PromptExecutionSettings(service_id="s1")),
KernelArguments(settings=PromptExecutionSettings(service_id="s2")),
{},
["s1", "s2"],
),
],
)
def test_kernel_arguments_inplace_merge(lhs, rhs, expected_dict, expected_settings_keys):
"""Test the |= operator with various argument combinations without execution_settings."""
original_id = id(lhs)
lhs |= rhs
# Verify this is the same object (in-place)
assert id(lhs) == original_id
assert dict(lhs) == expected_dict
if expected_settings_keys is None:
assert lhs.execution_settings is None
else:
assert sorted(lhs.execution_settings.keys()) == sorted(expected_settings_keys)
@pytest.mark.parametrize(
"rhs, lhs, expected_dict, expected_settings_keys",
[
# Merging different keys
({"b": 2}, KernelArguments(a=1), {"b": 2, "a": 1}, None),
# RHS overwrites when keys duplicate
({"a": 1}, KernelArguments(a=99), {"a": 99}, None),
# Merging with a KernelArguments
({"b": 2}, KernelArguments(a=1), {"b": 2, "a": 1}, None),
# Merging execution_settings together
(
{"test": "value"},
KernelArguments(settings=PromptExecutionSettings(service_id="s2")),
{"test": "value"},
["s2"],
),
# Plain dict on the left with KernelArguments+settings on the right
(
{"a": 1},
KernelArguments(b=2, settings=PromptExecutionSettings(service_id="shared")),
{"a": 1, "b": 2},
["shared"],
),
# KernelArguments on both sides with execution_settings
(
KernelArguments(a=1, settings=PromptExecutionSettings(service_id="s1")),
KernelArguments(b=2, settings=PromptExecutionSettings(service_id="s2")),
{"a": 1, "b": 2},
["s1", "s2"],
),
# Same service_id is overwritten by RHS (KernelArguments)
(
KernelArguments(a=1, settings=PromptExecutionSettings(service_id="shared")),
KernelArguments(b=2, settings=PromptExecutionSettings(service_id="shared")),
{"a": 1, "b": 2},
["shared"],
),
],
)
def test_kernel_arguments_ror_operator(rhs, lhs, expected_dict, expected_settings_keys):
"""Test the __ror__ operator (lhs | rhs) with various argument combinations."""
result = rhs | lhs
assert isinstance(result, KernelArguments)
assert dict(result) == expected_dict
if expected_settings_keys is None:
assert result.execution_settings is None
else:
assert sorted(result.execution_settings.keys()) == sorted(expected_settings_keys)
@pytest.mark.parametrize("lhs", [42, "foo", None])
def test_kernel_arguments_ror_operator_with_invalid_type(lhs):
"""Test the __ror__ operator with an invalid type raises TypeError."""
with pytest.raises(TypeError):
lhs | KernelArguments()
@@ -0,0 +1,282 @@
# Copyright (c) Microsoft. All rights reserved.
from collections.abc import AsyncGenerator, AsyncIterable
from inspect import Parameter, Signature
from typing import TYPE_CHECKING, Annotated, Any, Union
import pytest
from semantic_kernel.functions.kernel_function_decorator import _process_signature, kernel_function
from semantic_kernel.kernel_pydantic import KernelBaseModel
if TYPE_CHECKING:
from semantic_kernel.functions.kernel_arguments import KernelArguments
class InputObject(KernelBaseModel):
arg1: str
arg2: int
class MiscClass:
__test__ = False
@kernel_function(description="description")
def func_with_description(self, input):
return input
@kernel_function(description="description")
def func_no_name(self, input):
return input
@kernel_function(description="description", name="my-name")
def func_with_name(self, input):
return input
@kernel_function
def func_docstring_as_description(self, input):
"""Description."""
return input
@kernel_function
def func_input_annotated(self, input: Annotated[str, "input description"]):
return input
@kernel_function
def func_input_annotated_optional(self, input: Annotated[str | None, "input description"] = "test"):
return input
@kernel_function
def func_input_optional(self, input: str | None = "test"):
return input
@kernel_function
def func_return_type(self, input: str) -> str:
return input
@kernel_function
def func_return_type_optional(self, input: str) -> str | None:
return input
@kernel_function
def func_return_type_annotated(self, input: str) -> Annotated[str, "test return"]:
return input
@kernel_function
def func_return_type_streaming(self, input: str) -> Annotated[AsyncGenerator[str, Any], "test return"]: # type: ignore
yield input
@kernel_function
def func_input_object(self, input: InputObject):
return input
@kernel_function
def func_input_object_optional(self, input: InputObject | None = None):
return input
@kernel_function
def func_input_object_annotated(self, input: Annotated[InputObject, "input description"]):
return input
@kernel_function
def func_input_object_annotated_optional(self, input: Annotated[InputObject | None, "input description"] = None):
return input
@kernel_function
def func_input_object_union(self, input: InputObject | str):
return input
@kernel_function
def func_no_typing(self, input):
return input
def test_func_name_as_name():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_with_description")
assert my_func.__kernel_function_name__ == "func_with_description"
def test_description():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_with_description")
assert my_func.__kernel_function_description__ == "description"
def test_kernel_function_name_not_specified():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_no_name")
assert my_func.__kernel_function_name__ == "func_no_name"
def test_kernel_function_with_name_specified():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_with_name")
assert my_func.__kernel_function_name__ == "my-name"
def test_kernel_function_docstring_as_description():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_docstring_as_description")
assert my_func.__kernel_function_description__ == "Description."
def test_kernel_function_param_annotated():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_annotated")
assert my_func.__kernel_function_parameters__[0]["description"] == "input description"
assert my_func.__kernel_function_parameters__[0]["type_"] == "str"
assert my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
def test_kernel_function_param_optional():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_optional")
assert my_func.__kernel_function_parameters__[0]["type_"] == "str"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0]["default_value"] == "test"
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
def test_kernel_function_param_annotated_optional():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_annotated_optional")
assert my_func.__kernel_function_parameters__[0]["description"] == "input description"
assert my_func.__kernel_function_parameters__[0]["type_"] == "str"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0]["default_value"] == "test"
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
def test_kernel_function_return_type():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_return_type")
assert my_func.__kernel_function_return_type__ == "str"
assert my_func.__kernel_function_return_required__
assert not my_func.__kernel_function_streaming__
def test_kernel_function_return_type_optional():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_return_type_optional")
assert my_func.__kernel_function_return_type__ == "str"
assert my_func.__kernel_function_return_description__ == ""
assert not my_func.__kernel_function_return_required__
assert not my_func.__kernel_function_streaming__
def test_kernel_function_return_type_annotated():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_return_type_annotated")
assert my_func.__kernel_function_return_type__ == "str"
assert my_func.__kernel_function_return_description__ == "test return"
assert my_func.__kernel_function_return_required__
assert not my_func.__kernel_function_streaming__
def test_kernel_function_return_type_streaming():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_return_type_streaming")
assert my_func.__kernel_function_return_type__ in ("str, Any", "str, typing.Any")
assert my_func.__kernel_function_return_description__ == "test return"
assert my_func.__kernel_function_return_required__
assert my_func.__kernel_function_streaming__
def test_kernel_function_input_object():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_object")
assert my_func.__kernel_function_parameters__[0]["type_"] == "InputObject"
assert my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
assert my_func.__kernel_function_parameters__[0]["type_object"] == InputObject
def test_kernel_function_input_object_optional():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_object_optional")
assert my_func.__kernel_function_parameters__[0]["type_"] == "InputObject"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0]["default_value"] is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
assert my_func.__kernel_function_parameters__[0]["type_object"] == InputObject
def test_kernel_function_input_object_annotated():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_object_annotated")
assert my_func.__kernel_function_parameters__[0]["description"] == "input description"
assert my_func.__kernel_function_parameters__[0]["type_"] == "InputObject"
assert my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
assert my_func.__kernel_function_parameters__[0]["type_object"] == InputObject
def test_kernel_function_input_object_annotated_optional():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_object_annotated_optional")
assert my_func.__kernel_function_parameters__[0]["description"] == "input description"
assert my_func.__kernel_function_parameters__[0]["type_"] == "InputObject"
assert not my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0]["default_value"] is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
assert my_func.__kernel_function_parameters__[0]["type_object"] == InputObject
def test_kernel_function_input_object_union():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_input_object_union")
assert my_func.__kernel_function_parameters__[0]["type_"] == "InputObject, str"
assert my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
def test_kernel_function_no_typing():
decorator_test = MiscClass()
my_func = getattr(decorator_test, "func_no_typing")
assert my_func.__kernel_function_parameters__[0]["type_"] == "Any"
assert my_func.__kernel_function_parameters__[0]["is_required"]
assert my_func.__kernel_function_parameters__[0].get("default_value") is None
assert my_func.__kernel_function_parameters__[0]["name"] == "input"
@pytest.mark.parametrize(
("name", "annotation", "description", "type_", "is_required"),
[
("anno_str", Annotated[str, "test"], "test", "str", True),
("anno_opt_str", Annotated[str | None, "test"], "test", "str", False),
("anno_iter_str", Annotated[AsyncIterable[str], "test"], "test", "str", True),
("anno_opt_str_int", Annotated[str | int | None, "test"], "test", "str, int", False),
("str", str, None, "str", True),
("union", Union[str, int, float, "KernelArguments"], None, "str, int, float, KernelArguments", True),
("new_union", "str | int | float | KernelArguments", None, "str, int, float, KernelArguments", True),
("opt_str", str | None, None, "str", False),
("list_str", list[str], None, "list[str]", True),
("dict_str", dict[str, str], None, "dict[str, str]", True),
("list_str_opt", list[str] | None, None, "list[str]", False),
("anno_dict_str", Annotated[dict[str, str], "description"], "description", "dict[str, str]", True),
("anno_opt_dict_str", Annotated[dict | str | None, "description"], "description", "dict, str", False),
],
)
def test_annotation_parsing(name, annotation, description, type_, is_required):
param = Parameter(
name=name,
annotation=annotation,
default=Parameter.empty,
kind=Parameter.POSITIONAL_OR_KEYWORD,
)
func_sig = Signature(parameters=[param])
annotations = _process_signature(func_sig)
assert len(annotations) == 1
annotation_dict = annotations[0]
assert description == annotation_dict.get("description")
assert type_ == annotation_dict["type_"]
assert is_required == annotation_dict["is_required"]
@@ -0,0 +1,568 @@
# Copyright (c) Microsoft. All rights reserved.
from collections.abc import AsyncGenerator, Iterable
from typing import Annotated, Any
from unittest.mock import Mock
import pytest
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.exceptions import FunctionExecutionException, FunctionInitializationError
from semantic_kernel.filters.functions.function_invocation_context import FunctionInvocationContext
from semantic_kernel.filters.kernel_filters_extension import _rebuild_function_invocation_context
from semantic_kernel.functions.function_result import FunctionResult
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata
from semantic_kernel.kernel import Kernel
from semantic_kernel.kernel_pydantic import KernelBaseModel
class CustomType(KernelBaseModel):
id: str
name: str
class CustomTypeNonPydantic:
id: str
name: str
def __init__(self, id: str, name: str):
self.id = id
self.name = name
@pytest.fixture
def get_custom_type_function_pydantic():
@kernel_function
def func_default(param: list[CustomType]):
return input
return KernelFunction.from_method(func_default, "test")
@pytest.fixture
def get_custom_type_function_nonpydantic():
@kernel_function
def func_default(param: list[CustomTypeNonPydantic]):
return input
return KernelFunction.from_method(func_default, "test")
def test_init_native_function_with_input_description():
@kernel_function(description="Mock description", name="mock_function")
def mock_function(input: Annotated[str, "input"], arguments: "KernelArguments") -> None:
pass
mock_method = mock_function
native_function = KernelFunction.from_method(method=mock_method, plugin_name="MockPlugin")
assert native_function.method == mock_method
assert native_function.parameters[0].name == "input"
assert native_function.parameters[0].description == "input"
assert not native_function.parameters[0].default_value
assert native_function.parameters[0].type_ == "str"
assert native_function.parameters[0].is_required is True
assert native_function.parameters[1].name == "arguments"
assert native_function.parameters[1].description is None
assert not native_function.parameters[1].default_value
assert native_function.parameters[1].type_ == "KernelArguments"
assert native_function.parameters[1].is_required is True
def test_init_native_function_without_input_description():
@kernel_function()
def mock_function(arguments: "KernelArguments") -> None:
pass
mock_function.__kernel_function__ = True
mock_function.__kernel_function_name__ = "mock_function_no_input_desc"
mock_function.__kernel_function_description__ = "Mock description no input desc"
mock_function.__kernel_function_parameters__ = [
{
"name": "arguments",
"description": "Param 1 description",
"default_value": "default_param1_value",
"is_required": True,
}
]
mock_method = mock_function
native_function = KernelFunction.from_method(method=mock_method, plugin_name="MockPlugin")
assert native_function.method == mock_method
assert native_function.parameters[0].name == "arguments"
assert native_function.parameters[0].description == "Param 1 description"
assert native_function.parameters[0].default_value == "default_param1_value"
assert native_function.parameters[0].type_ == "str"
assert native_function.parameters[0].is_required is True
def test_init_native_function_from_kernel_function_decorator():
@kernel_function(
description="Test description",
name="test_function",
)
def decorated_function(input: Annotated[str | None, "Test input description"] = "test_default_value") -> None:
pass
assert decorated_function.__kernel_function__ is True
assert decorated_function.__kernel_function_description__ == "Test description"
assert decorated_function.__kernel_function_name__ == "test_function"
native_function = KernelFunction.from_method(method=decorated_function, plugin_name="MockPlugin")
assert native_function.method == decorated_function
assert native_function.parameters[0].name == "input"
assert native_function.parameters[0].description == "Test input description"
assert native_function.parameters[0].default_value == "test_default_value"
assert native_function.parameters[0].type_ == "str"
assert native_function.parameters[0].is_required is False
assert type(native_function.return_parameter) is KernelParameterMetadata
def test_init_native_function_from_kernel_function_decorator_defaults():
@kernel_function()
def decorated_function() -> None:
pass
assert decorated_function.__kernel_function__ is True
assert decorated_function.__kernel_function_description__ is None
assert decorated_function.__kernel_function_name__ == "decorated_function"
native_function = KernelFunction.from_method(method=decorated_function, plugin_name="MockPlugin")
assert native_function.method == decorated_function
assert len(native_function.parameters) == 0
def test_init_method_is_none():
with pytest.raises(FunctionInitializationError):
KernelFunction.from_method(method=None, plugin_name="MockPlugin")
def test_init_method_is_not_kernel_function():
def not_kernel_function():
pass
with pytest.raises(FunctionInitializationError):
KernelFunction.from_method(method=not_kernel_function, plugin_name="MockPlugin")
def test_init_invalid_name():
@kernel_function(name="invalid name")
def invalid_name():
pass
with pytest.raises(FunctionInitializationError):
KernelFunction.from_method(method=invalid_name, plugin_name="MockPlugin")
async def test_invoke_non_async(kernel: Kernel):
@kernel_function()
def non_async_function() -> str:
return ""
native_function = KernelFunction.from_method(method=non_async_function, plugin_name="MockPlugin")
result = await native_function.invoke(kernel=kernel, arguments=None)
assert result.value == ""
with pytest.raises(NotImplementedError):
async for _ in native_function.invoke_stream(kernel=kernel, arguments=None):
pass
async def test_invoke_async(kernel: Kernel):
@kernel_function()
async def async_function() -> str:
return ""
native_function = KernelFunction.from_method(method=async_function, plugin_name="MockPlugin")
result = await native_function.invoke(kernel=kernel, arguments=None)
assert result.value == ""
with pytest.raises(NotImplementedError):
async for _ in native_function.invoke_stream(kernel=kernel, arguments=None):
pass
async def test_invoke_gen(kernel: Kernel):
@kernel_function()
def gen_function() -> Iterable[str]:
yield ""
native_function = KernelFunction.from_method(method=gen_function, plugin_name="MockPlugin")
result = await native_function.invoke(kernel=kernel, arguments=None)
assert result.value == [""]
async for partial_result in native_function.invoke_stream(kernel=kernel, arguments=None):
assert partial_result == ""
async def test_invoke_gen_async(kernel: Kernel):
@kernel_function()
async def async_gen_function() -> AsyncGenerator[str, Any]:
yield ""
native_function = KernelFunction.from_method(method=async_gen_function, plugin_name="MockPlugin")
result = await native_function.invoke(kernel=kernel, arguments=None)
assert result.value == [""]
async for partial_result in native_function.invoke_stream(kernel=kernel, arguments=None):
assert partial_result == ""
async def test_service_execution(kernel: Kernel, openai_unit_test_env):
service = OpenAIChatCompletion(service_id="test", ai_model_id="test")
req_settings = service.get_prompt_execution_settings_class()(service_id="test")
req_settings.temperature = 0.5
kernel.add_service(service)
arguments = KernelArguments(settings=req_settings)
@kernel_function(name="function")
def my_function(kernel, service, execution_settings, arguments) -> str:
assert kernel is not None
assert isinstance(kernel, Kernel)
assert service is not None
assert isinstance(service, OpenAIChatCompletion)
assert execution_settings is not None
assert isinstance(execution_settings, req_settings.__class__)
assert execution_settings.temperature == 0.5
assert arguments is not None
assert isinstance(arguments, KernelArguments)
return "ok"
func = KernelFunction.from_method(my_function, "test")
result = await func.invoke(kernel, arguments)
assert result.value == "ok"
async def test_required_param_not_supplied(kernel: Kernel):
@kernel_function()
def my_function(input: str) -> str:
return input
func = KernelFunction.from_method(my_function, "test")
with pytest.raises(FunctionExecutionException):
await func.invoke(kernel=kernel, arguments=KernelArguments())
async def test_service_execution_with_complex_object(kernel: Kernel):
class InputObject(KernelBaseModel):
arg1: str
arg2: int
@kernel_function(name="function")
def my_function(input_obj: InputObject) -> str:
assert input_obj is not None
assert isinstance(input_obj, InputObject)
assert input_obj.arg1 == "test"
assert input_obj.arg2 == 5
return f"{input_obj.arg1} {input_obj.arg2}"
func = KernelFunction.from_method(my_function, "test")
arguments = KernelArguments(input_obj=InputObject(arg1="test", arg2=5))
result = await func.invoke(kernel, arguments)
assert result.value == "test 5"
class InputObject(KernelBaseModel):
arg1: str
arg2: int
async def test_service_execution_with_complex_object_from_str(kernel: Kernel):
@kernel_function(name="function")
def my_function(input_obj: InputObject) -> str:
assert input_obj is not None
assert isinstance(input_obj, InputObject)
assert input_obj.arg1 == "test"
assert input_obj.arg2 == 5
return f"{input_obj.arg1} {input_obj.arg2}"
func = KernelFunction.from_method(my_function, "test")
arguments = KernelArguments(input_obj={"arg1": "test", "arg2": 5})
result = await func.invoke(kernel, arguments)
assert result.value == "test 5"
async def test_service_execution_with_complex_object_from_str_mixed(kernel: Kernel):
@kernel_function(name="function")
def my_function(input_obj: InputObject, input_str: str) -> str:
assert input_obj is not None
assert isinstance(input_obj, InputObject)
assert input_obj.arg1 == "test"
assert input_obj.arg2 == 5
return f"{input_obj.arg1} {input_str} {input_obj.arg2}"
func = KernelFunction.from_method(my_function, "test")
arguments = KernelArguments(input_obj={"arg1": "test", "arg2": 5}, input_str="test2")
result = await func.invoke(kernel, arguments)
assert result.value == "test test2 5"
async def test_service_execution_with_complex_object_from_str_mixed_multi(kernel: Kernel):
@kernel_function(name="function")
def my_function(input_obj: InputObject, input_str: str | int) -> str:
assert input_obj is not None
assert isinstance(input_obj, InputObject)
assert input_obj.arg1 == "test"
assert input_obj.arg2 == 5
return f"{input_obj.arg1} {input_str} {input_obj.arg2}"
func = KernelFunction.from_method(my_function, "test")
arguments = KernelArguments(input_obj={"arg1": "test", "arg2": 5}, input_str="test2")
result = await func.invoke(kernel, arguments)
assert result.value == "test test2 5"
def test_function_from_lambda():
func = KernelFunctionFromMethod(method=kernel_function(lambda x: x**2, name="square"), plugin_name="math")
assert func is not None
async def test_function_invoke_return_list_type(kernel: Kernel):
@kernel_function(name="list_func")
def test_list_func() -> list[str]:
return ["test1", "test2"]
func = KernelFunction.from_method(test_list_func, "test")
result = await kernel.invoke(function=func)
assert str(result) == "test1,test2"
async def test_function_invocation_filters(kernel: Kernel):
func = KernelFunctionFromMethod(method=kernel_function(lambda input: input**2, name="square"), plugin_name="math")
kernel.add_function(plugin_name="math", function=func)
pre_call_count = 0
post_call_count = 0
async def custom_filter(context, next):
nonlocal pre_call_count
pre_call_count += 1
await next(context)
nonlocal post_call_count
post_call_count += 1
kernel.add_filter("function_invocation", custom_filter)
result = await kernel.invoke(plugin_name="math", function_name="square", arguments=KernelArguments(input=2))
assert result.value == 4
assert pre_call_count == 1
assert post_call_count == 1
async def test_function_invocation_multiple_filters(kernel: Kernel):
call_stack = []
@kernel_function(name="square")
def func(input: int):
nonlocal call_stack
call_stack.append("func")
return input**2
kernel.add_function(plugin_name="math", function=func)
async def custom_filter1(context, next):
nonlocal call_stack
call_stack.append("custom_filter1_pre")
await next(context)
call_stack.append("custom_filter1_post")
async def custom_filter2(context, next):
nonlocal call_stack
call_stack.append("custom_filter2_pre")
await next(context)
call_stack.append("custom_filter2_post")
kernel.add_filter("function_invocation", custom_filter1)
kernel.add_filter("function_invocation", custom_filter2)
result = await kernel.invoke(plugin_name="math", function_name="square", arguments=KernelArguments(input=2))
assert result.value == 4
assert call_stack == [
"custom_filter1_pre",
"custom_filter2_pre",
"func",
"custom_filter2_post",
"custom_filter1_post",
]
async def test_function_invocation_filters_streaming(kernel: Kernel):
call_stack = []
@kernel_function(name="square")
async def func(input: int):
nonlocal call_stack
call_stack.append("func1")
yield input**2
call_stack.append("func2")
yield input**3
kernel.add_function(plugin_name="math", function=func)
async def custom_filter(context, next):
nonlocal call_stack
call_stack.append("custom_filter_pre")
await next(context)
async def override_stream(stream):
nonlocal call_stack
async for partial in stream:
call_stack.append("overridden_func")
yield partial * 2
stream = context.result.value
context.result = FunctionResult(function=context.result.function, value=override_stream(stream))
call_stack.append("custom_filter_post")
kernel.add_filter("function_invocation", custom_filter)
index = 0
async for partial in kernel.invoke_stream(
plugin_name="math", function_name="square", arguments=KernelArguments(input=2)
):
assert partial == 8 if index == 0 else 16
index += 1
assert call_stack == [
"custom_filter_pre",
"custom_filter_post",
"func1",
"overridden_func",
"func2",
"overridden_func",
]
async def test_default_handling(kernel: Kernel):
@kernel_function
def func_default(input: str = "test"):
return input
func = kernel.add_function(plugin_name="test", function_name="func_default", function=func_default)
res = await kernel.invoke(func)
assert str(res) == "test"
async def test_default_handling_2(kernel: Kernel):
@kernel_function
def func_default(base: str, input: str = "test"):
return input
func = kernel.add_function(plugin_name="test", function_name="func_default", function=func_default)
res = await kernel.invoke(func, base="base")
assert str(res) == "test"
def test_parse_list_of_objects(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
param_type = list[CustomType]
value = [{"id": "1", "name": "John"}, {"id": "2", "name": "Jane"}]
result = func._parse_parameter(value, param_type)
assert isinstance(result, list)
assert len(result) == 2
assert all(isinstance(item, CustomType) for item in result)
def test_parse_individual_object(get_custom_type_function_pydantic):
value = {"id": "2", "name": "Jane"}
func = get_custom_type_function_pydantic
result = func._parse_parameter(value, CustomType)
assert isinstance(result, CustomType)
assert result.id == "2"
assert result.name == "Jane"
def test_parse_non_list_raises_exception(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
param_type = list[CustomType]
value = {"id": "2", "name": "Jane"}
with pytest.raises(FunctionExecutionException, match=r"Expected a list for .*"):
func._parse_parameter(value, param_type)
def test_parse_invalid_dict_raises_exception(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
value = {"id": "1"}
with pytest.raises(FunctionExecutionException, match=r"Parameter is expected to be parsed to .*"):
func._parse_parameter(value, CustomType)
def test_parse_invalid_value_raises_exception(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
value = "invalid_value"
with pytest.raises(FunctionExecutionException, match=r"Parameter is expected to be parsed to .*"):
func._parse_parameter(value, CustomType)
def test_parse_invalid_list_raises_exception(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
param_type = list[CustomType]
value = ["invalid_value"]
with pytest.raises(FunctionExecutionException, match=r"Parameter is expected to be parsed to .*"):
func._parse_parameter(value, param_type)
def test_parse_dict_with_init_non_pydantic(get_custom_type_function_nonpydantic):
func = get_custom_type_function_nonpydantic
value = {"id": "3", "name": "Alice"}
result = func._parse_parameter(value, CustomTypeNonPydantic)
assert isinstance(result, CustomTypeNonPydantic)
assert result.id == "3"
assert result.name == "Alice"
def test_parse_invalid_dict_raises_exception_new(get_custom_type_function_nonpydantic):
func = get_custom_type_function_nonpydantic
value = {"wrong_key": "3", "name": "Alice"}
with pytest.raises(FunctionExecutionException, match=r"Parameter is expected to be parsed to .*"):
func._parse_parameter(value, CustomTypeNonPydantic)
def test_gather_function_parameters_exception_handling(get_custom_type_function_pydantic):
kernel = Mock(spec=Kernel) # Mock kernel
func = get_custom_type_function_pydantic
_rebuild_function_invocation_context()
context = FunctionInvocationContext(kernel=kernel, function=func, arguments=KernelArguments(param="test"))
with pytest.raises(FunctionExecutionException, match=r"Parameter param is expected to be parsed to .* but is not."):
func.gather_function_parameters(context)
@pytest.mark.parametrize(
("mode"),
[
("python"),
("json"),
],
)
def test_function_model_dump(get_custom_type_function_pydantic, mode):
func: KernelFunctionFromMethod = get_custom_type_function_pydantic
model_dump = func.model_dump(mode=mode)
assert isinstance(model_dump, dict)
assert "metadata" in model_dump
assert len(model_dump["metadata"]["parameters"]) == 1
def test_function_model_dump_json(get_custom_type_function_pydantic):
func = get_custom_type_function_pydantic
model_dump = func.model_dump_json()
assert isinstance(model_dump, str)
assert "metadata" in model_dump
@@ -0,0 +1,648 @@
# Copyright (c) Microsoft. All rights reserved.
import os
import tempfile
from copy import deepcopy
from unittest.mock import patch
import pytest
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion import OpenAITextCompletion
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.const import METADATA_EXCEPTION_KEY
from semantic_kernel.contents import AuthorRole
from semantic_kernel.contents.chat_message_content import ChatMessageContent
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions import FunctionInitializationError
from semantic_kernel.filters.functions.function_invocation_context import FunctionInvocationContext
from semantic_kernel.filters.kernel_filters_extension import _rebuild_function_invocation_context
from semantic_kernel.filters.prompts.prompt_render_context import PromptRenderContext
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.kernel import Kernel
from semantic_kernel.prompt_template.input_variable import InputVariable
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
def test_init_minimal_prompt():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_minimal_prompt_template():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt_template=KernelPromptTemplate(prompt_template_config=PromptTemplateConfig(template="test")),
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_minimal_prompt_template_config():
function = KernelFunctionFromPrompt(
function_name="test", plugin_name="test", prompt_template_config=PromptTemplateConfig(template="test")
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_no_prompt():
with pytest.raises(FunctionInitializationError):
KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
)
def test_init_invalid_name():
with pytest.raises(FunctionInitializationError):
KernelFunctionFromPrompt(function_name="test func", plugin_name="test", prompt="test")
def test_init_prompt_execution_settings_none():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=None,
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_prompt_execution_settings_none_with_prompt_template():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt_template=KernelPromptTemplate(
prompt_template_config=PromptTemplateConfig(template="test", execution_settings={})
),
prompt_execution_settings=None,
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_prompt_execution_settings():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_prompt_execution_settings_list():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=[PromptExecutionSettings(service_id="test")],
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
def test_init_prompt_execution_settings_dict():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings={"test": PromptExecutionSettings(service_id="test")},
)
assert function.name == "test"
assert function.plugin_name == "test"
assert function.description is None
assert function.prompt_template.prompt_template_config.template == "test"
async def test_invoke_chat_stream(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
# This part remains unchanged - for synchronous mocking example
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_chat_message_contents"
) as mock:
mock.return_value = [ChatMessageContent(role=AuthorRole.ASSISTANT, content="test", metadata={})]
result = await function.invoke(kernel=kernel)
assert str(result) == "test"
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_streaming_chat_message_contents"
) as mock:
mock.return_value = [
StreamingChatMessageContent(choice_index=0, role=AuthorRole.ASSISTANT, content="test", metadata={})
]
async for result in function.invoke_stream(kernel=kernel):
assert str(result) == "test"
async def test_invoke_exception(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_chat_message_contents",
side_effect=Exception,
) as mock:
mock.return_value = [ChatMessageContent(role=AuthorRole.ASSISTANT, content="test", metadata={})]
with pytest.raises(Exception, match="test"):
await function.invoke(kernel=kernel)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_streaming_chat_message_contents",
side_effect=Exception,
) as mock:
mock.return_value = [
StreamingChatMessageContent(choice_index=0, role=AuthorRole.ASSISTANT, content="test", metadata={})
]
with pytest.raises(Exception):
async for result in function.invoke_stream(kernel=kernel):
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
async def test_invoke_text(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.get_text_contents",
) as mock:
mock.return_value = [TextContent(text="test", metadata={})]
result = await function.invoke(kernel=kernel)
assert str(result) == "test"
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.get_streaming_text_contents",
) as mock:
mock.return_value = [TextContent(text="test", metadata={})]
async for result in function.invoke_stream(kernel=kernel):
assert str(result) == "test"
async def test_invoke_exception_text(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.get_text_contents",
side_effect=Exception,
) as mock:
mock.return_value = [TextContent(text="test", metadata={})]
with pytest.raises(Exception, match="test"):
await function.invoke(kernel=kernel)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion.OpenAITextCompletion.get_streaming_text_contents",
side_effect=Exception,
) as mock:
mock.return_value = []
with pytest.raises(Exception):
async for result in function.invoke_stream(kernel=kernel):
assert isinstance(result.metadata[METADATA_EXCEPTION_KEY], Exception)
async def test_invoke_defaults(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt_template_config=PromptTemplateConfig(
template="{{$input}}",
input_variables=[InputVariable(name="input", type="str", default="test", is_required=False)],
),
prompt_execution_settings=PromptExecutionSettings(service_id="test"),
)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_chat_message_contents"
) as mock:
mock.return_value = [ChatMessageContent(role=AuthorRole.ASSISTANT, content="test", metadata={})]
result = await function.invoke(kernel=kernel)
assert str(result) == "test"
def test_create_with_multiple_settings():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt_template_config=PromptTemplateConfig(
template="test",
execution_settings=[
PromptExecutionSettings(service_id="test", temperature=0.0),
PromptExecutionSettings(service_id="test2", temperature=1.0),
],
),
)
assert (
function.prompt_template.prompt_template_config.execution_settings["test"].extension_data["temperature"] == 0.0
)
assert (
function.prompt_template.prompt_template_config.execution_settings["test2"].extension_data["temperature"] == 1.0
)
async def test_create_with_multiple_settings_one_service_registered(openai_unit_test_env):
kernel = Kernel()
kernel.add_service(OpenAIChatCompletion(service_id="test2", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt_template_config=PromptTemplateConfig(
template="test",
execution_settings=[
PromptExecutionSettings(service_id="test", temperature=0.0),
PromptExecutionSettings(service_id="test2", temperature=1.0),
],
),
)
with patch(
"semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion.OpenAIChatCompletion.get_chat_message_contents"
) as mock:
mock.return_value = [ChatMessageContent(role=AuthorRole.ASSISTANT, content="test", metadata={})]
result = await function.invoke(kernel=kernel)
assert str(result) == "test"
def test_from_yaml_fail():
with pytest.raises(FunctionInitializationError):
KernelFunctionFromPrompt.from_yaml("template_format: something_else")
def test_from_directory_prompt_only():
with pytest.raises(FunctionInitializationError):
KernelFunctionFromPrompt.from_directory(
path=os.path.join(
os.path.dirname(__file__),
"../../assets",
"test_plugins",
"TestPlugin",
"TestFunctionPromptOnly",
),
plugin_name="test",
)
def test_from_directory_config_only():
with pytest.raises(FunctionInitializationError):
KernelFunctionFromPrompt.from_directory(
path=os.path.join(
os.path.dirname(__file__),
"../../assets",
"test_plugins",
"TestPlugin",
"TestFunctionConfigOnly",
),
plugin_name="test",
)
async def test_prompt_render(kernel: Kernel, openai_unit_test_env):
kernel.add_service(OpenAIChatCompletion(service_id="default", ai_model_id="test"))
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
template_format="semantic-kernel",
)
_rebuild_function_invocation_context()
context = FunctionInvocationContext(function=function, kernel=kernel, arguments=KernelArguments())
prompt_render_result = await function._render_prompt(context)
assert prompt_render_result.rendered_prompt == "test"
async def test_prompt_render_with_filter(kernel: Kernel, openai_unit_test_env):
kernel.add_service(OpenAIChatCompletion(service_id="default", ai_model_id="test"))
@kernel.filter("prompt_rendering")
async def prompt_rendering_filter(context: PromptRenderContext, next):
await next(context)
context.rendered_prompt = f"preface {context.rendered_prompt or ''}"
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
template_format="semantic-kernel",
)
_rebuild_function_invocation_context()
context = FunctionInvocationContext(function=function, kernel=kernel, arguments=KernelArguments())
prompt_render_result = await function._render_prompt(context)
assert prompt_render_result.rendered_prompt == "preface test"
@pytest.mark.parametrize(
("mode"),
[
("python"),
("json"),
],
)
def test_function_model_dump(mode: str):
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
template_format="semantic-kernel",
prompt_template_config=PromptTemplateConfig(
template="test",
input_variables=[InputVariable(name="input", type="str", default="test", is_required=False)],
),
)
model_dump = function.model_dump(mode=mode)
assert isinstance(model_dump, dict)
assert "metadata" in model_dump
assert len(model_dump["metadata"]["parameters"]) == 1
def test_function_model_dump_json():
function = KernelFunctionFromPrompt(
function_name="test",
plugin_name="test",
prompt="test",
template_format="semantic-kernel",
)
model_dump_json = function.model_dump_json()
assert isinstance(model_dump_json, str)
assert "test" in model_dump_json
def test_from_directory_utf8_encoding_default():
"""Test loading plugin with default UTF-8 encoding."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
# UTF-8 content with international characters
prompt_content = """Hello! I can help with questions in multiple languages:
English: Hello world!
Spanish: ¡Hola mundo!
Chinese: 你好世界!
Japanese: こんにちは世界!
Question: {{$input}}
"""
config_content = """{
"schema": 1,
"description": "A multilingual assistant function",
"input_variables": [
{
"name": "input",
"description": "User's question",
"required": true
}
]
}"""
# Write files with UTF-8 encoding
with open(prompt_path, "w", encoding="utf-8") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="utf-8") as f:
f.write(config_content)
# Test default behavior (should use UTF-8)
function = KernelFunctionFromPrompt.from_directory(temp_dir)
assert function.name == os.path.basename(temp_dir)
assert function.description == "A multilingual assistant function"
assert "你好世界" in function.prompt_template.prompt_template_config.template
assert "こんにちは世界" in function.prompt_template.prompt_template_config.template
def test_from_directory_explicit_utf8_encoding():
"""Test loading plugin with explicit UTF-8 encoding."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
prompt_content = "Hello with UTF-8 characters: ñáéíóú {{$input}}"
config_content = '{"schema": 1, "description": "Test with UTF-8 characters"}'
with open(prompt_path, "w", encoding="utf-8") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="utf-8") as f:
f.write(config_content)
# Test explicit UTF-8 encoding
function = KernelFunctionFromPrompt.from_directory(temp_dir, encoding="utf-8")
assert function.description == "Test with UTF-8 characters"
assert "ñáéíóú" in function.prompt_template.prompt_template_config.template
def test_from_directory_latin1_encoding():
"""Test loading plugin with Latin-1 encoding."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
# Content with Latin-1 characters (Western European)
prompt_content = """Assistant for Western European languages:
French: café, naïve, résumé
German: Müller, Größe, weiß
Spanish: niño, señora, años
Question: {{$input}}
"""
config_content = """{
"schema": 1,
"description": "Western European language assistant",
"input_variables": [
{
"name": "input",
"description": "User's question",
"required": true
}
]
}"""
# Write files with Latin-1 encoding
with open(prompt_path, "w", encoding="latin-1") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="latin-1") as f:
f.write(config_content)
# Load with Latin-1 encoding
function = KernelFunctionFromPrompt.from_directory(temp_dir, encoding="latin-1")
assert function.description == "Western European language assistant"
assert "café" in function.prompt_template.prompt_template_config.template
assert "Müller" in function.prompt_template.prompt_template_config.template
assert "niño" in function.prompt_template.prompt_template_config.template
def test_from_directory_cp1252_encoding():
"""Test loading plugin with Windows-1252 encoding."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
# Content with Windows-1252 specific characters
prompt_content = """Windows text processing assistant:
Smart quotes: "Hello" and 'world'
Em dash: Yes—absolutely!
Ellipsis: Wait…
Question: {{$input}}
"""
config_content = """{
"schema": 1,
"description": "Windows text processing assistant",
"input_variables": [
{
"name": "input",
"description": "User's question about text processing",
"required": true
}
]
}"""
# Write files with Windows-1252 encoding
with open(prompt_path, "w", encoding="cp1252") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="cp1252") as f:
f.write(config_content)
# Load with Windows-1252 encoding
function = KernelFunctionFromPrompt.from_directory(temp_dir, encoding="cp1252")
assert function.description == "Windows text processing assistant"
assert '"Hello"' in function.prompt_template.prompt_template_config.template
assert "Yes—absolutely" in function.prompt_template.prompt_template_config.template
assert "Wait…" in function.prompt_template.prompt_template_config.template
def test_from_directory_with_plugin_name_and_encoding():
"""Test loading plugin with both plugin name and encoding specified."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
prompt_content = "Simple assistant: {{$input}}"
config_content = '{"schema": 1, "description": "Simple assistant"}'
with open(prompt_path, "w", encoding="utf-8") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="utf-8") as f:
f.write(config_content)
# Load with both plugin name and encoding specified
function = KernelFunctionFromPrompt.from_directory(
path=temp_dir, plugin_name="MyCustomPlugin", encoding="utf-8"
)
assert function.metadata.plugin_name == "MyCustomPlugin"
assert function.description == "Simple assistant"
assert function.prompt_template.prompt_template_config.template == "Simple assistant: {{$input}}"
def test_from_directory_encoding_error_handling():
"""Test that incorrect encoding raises appropriate error."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
# Write UTF-8 content
prompt_content = "Hello with UTF-8: 你好世界 {{$input}}"
config_content = '{"schema": 1, "description": "UTF-8 content"}'
with open(prompt_path, "w", encoding="utf-8") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="utf-8") as f:
f.write(config_content)
# Try to read with ASCII encoding - should fail
with pytest.raises(UnicodeDecodeError):
KernelFunctionFromPrompt.from_directory(temp_dir, encoding="ascii")
def test_from_directory_backward_compatibility():
"""Test that existing code without encoding parameter still works."""
with tempfile.TemporaryDirectory() as temp_dir:
prompt_path = os.path.join(temp_dir, "skprompt.txt")
config_path = os.path.join(temp_dir, "config.json")
prompt_content = "Basic ASCII content: {{$input}}"
config_content = '{"schema": 1, "description": "Basic function"}'
with open(prompt_path, "w", encoding="utf-8") as f:
f.write(prompt_content)
with open(config_path, "w", encoding="utf-8") as f:
f.write(config_content)
# Test that old calling style still works
function = KernelFunctionFromPrompt.from_directory(temp_dir)
assert function.description == "Basic function"
assert function.prompt_template.prompt_template_config.template == "Basic ASCII content: {{$input}}"
def test_kernel_function_from_prompt_deepcopy():
"""Test deepcopying a KernelFunctionFromPrompt."""
function = KernelFunctionFromPrompt(
function_name="test_function",
plugin_name="test_plugin",
prompt="Hello, world!",
description="A test function.",
)
copied_function = deepcopy(function)
assert copied_function is not function
assert copied_function.name == function.name
assert copied_function.plugin_name == function.plugin_name
assert copied_function.description == function.description
assert copied_function.prompt_template.prompt_template_config.template == (
function.prompt_template.prompt_template_config.template
)
assert copied_function.prompt_template is not function.prompt_template
@@ -0,0 +1,91 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata
def test_kernel_function_metadata():
function_metadata = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
parameters=[],
is_prompt=True,
is_asynchronous=True,
)
assert function_metadata.is_prompt
def test_kernel_function_metadata_defaults():
function_metadata = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
assert function_metadata.parameters == []
assert function_metadata.is_asynchronous
def test_kernel_function_metadata_name_pattern_error():
with pytest.raises(ValueError):
KernelFunctionMetadata(
name="*",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
def test_kernel_function_metadata_name_empty_error():
with pytest.raises(ValueError):
KernelFunctionMetadata(
name="",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
def test_kernel_function_equals():
function_metadata_1 = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
function_metadata_2 = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
assert function_metadata_1 == function_metadata_2
def test_kernel_function_not_equals():
function_metadata_1 = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
function_metadata_2 = KernelFunctionMetadata(
name="function2",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
assert function_metadata_1 != function_metadata_2
def test_kernel_function_not_equals_other_object():
function_metadata_1 = KernelFunctionMetadata(
name="function1",
plugin_name="plugin1",
description="Semantic function",
is_prompt=True,
)
function_metadata_2 = KernelParameterMetadata(name="function2", description="Semantic function", default_value="")
assert function_metadata_1 != function_metadata_2
@@ -0,0 +1,67 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from pydantic import ValidationError
from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata
def test_kernel_parameter_metadata_init():
metadata = KernelParameterMetadata(
name="test",
description="description",
is_required=True,
type="str",
default_value="default",
)
assert metadata.name == "test"
assert metadata.description == "description"
assert metadata.is_required is True
assert metadata.default_value == "default"
def test_kernel_parameter_metadata_valid():
metadata = KernelParameterMetadata(
name="param1",
description="A test parameter",
default_value="default",
type_="str",
is_required=True,
type_object=str,
)
assert metadata.name == "param1"
assert metadata.description == "A test parameter"
assert metadata.default_value == "default"
assert metadata.type_ == "str"
assert metadata.is_required is True
assert metadata.type_object is str
assert metadata.schema_data == {"type": "string", "description": "A test parameter"}
def test_kernel_parameter_metadata_invalid_name():
with pytest.raises(ValidationError):
KernelParameterMetadata(
name="invalid name!", description="A test parameter", default_value="default", type_="str"
)
def test_kernel_parameter_metadata_infer_schema_with_type_object():
metadata = KernelParameterMetadata(name="param2", type_object=int, description="An integer parameter")
assert metadata.schema_data == {"type": "integer", "description": "An integer parameter"}
def test_kernel_parameter_metadata_infer_schema_with_type_name():
metadata = KernelParameterMetadata(name="param3", type_="int", default_value=42, description="An integer parameter")
assert metadata.schema_data == {"type": "integer", "description": "An integer parameter (default value: 42)"}
def test_kernel_parameter_metadata_without_schema_data():
metadata = KernelParameterMetadata(name="param4", type_="bool")
assert metadata.schema_data == {"type": "boolean"}
def test_kernel_parameter_metadata_with_partial_data():
metadata = KernelParameterMetadata(name="param5", type_="float", default_value=3.14)
assert metadata.schema_data == {"type": "number", "description": "(default value: 3.14)"}
@@ -0,0 +1,538 @@
# Copyright (c) Microsoft. All rights reserved.
import os
from collections.abc import Callable
from typing import Any
import pytest
from pytest import raises
from semantic_kernel.connectors.ai import PromptExecutionSettings
from semantic_kernel.connectors.openapi_plugin.openapi_parser import OpenApiParser
from semantic_kernel.exceptions.function_exceptions import PluginInitializationError
from semantic_kernel.functions import kernel_function
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.functions.kernel_plugin import KernelPlugin
from semantic_kernel.prompt_template.input_variable import InputVariable
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
@pytest.fixture
def mock_function() -> Callable[..., Any]:
@kernel_function
def mock_function(input: str) -> None:
pass
return mock_function
# region Init
def test_init_fail_no_name():
with raises(TypeError):
KernelPlugin(description="A unit test plugin")
def test_init_with_no_functions():
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert not plugin.functions
def test_init_with_kernel_functions(mock_function):
function_plugin_name = "MockPlugin"
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=function_plugin_name)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=native_function)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
assert native_function.plugin_name == function_plugin_name
def test_init_with_kernel_functions_list(mock_function):
function_plugin_name = "MockPlugin"
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=function_plugin_name)
plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=[native_function]
)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
assert native_function.plugin_name == function_plugin_name
def test_init_with_list_other_fail():
with raises(ValueError):
KernelPlugin(name="test_plugin", description="A unit test plugin", functions=["str"])
def test_init_with_other_fail():
with raises(ValueError):
KernelPlugin(name="test_plugin", description="A unit test plugin", functions="str")
def test_init_with_kernel_functions_dict(mock_function):
function_plugin_name = "MockPlugin"
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=function_plugin_name)
plugin = KernelPlugin(
name=expected_plugin_name,
description=expected_plugin_description,
functions={native_function.name: native_function},
)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
assert native_function.plugin_name == function_plugin_name
def test_init_with_callable_functions(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=mock_function)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
def test_init_with_callable_functions_list(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=[mock_function])
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
def test_init_with_kernel_plugin(mock_function):
function_plugin_name = "MockPlugin"
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=function_plugin_name)
first_plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=native_function
)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=first_plugin)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
assert native_function.plugin_name == function_plugin_name
def test_init_with_kernel_plugin_list(mock_function):
function_plugin_name = "MockPlugin"
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=function_plugin_name)
first_plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=native_function
)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=[first_plugin])
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].plugin_name == expected_plugin_name
assert native_function.plugin_name == function_plugin_name
def test_init_exposes_the_native_function_it_contains(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name="MockPlugin")
plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=[native_function]
)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].name == native_function.name
def test_init_with_prompt_function():
req_settings = PromptExecutionSettings(extension_data={"max_tokens": 2000, "temperature": 0.7, "top_p": 0.8})
prompt = "Use this input: {{$request}}"
prompt_template_config = PromptTemplateConfig(
template=prompt,
name="chat",
template_format="semantic-kernel",
input_variables=[
InputVariable(name="request", description="The user input", is_required=True),
],
execution_settings={"default": req_settings},
)
expected_plugin_name = "test_plugin"
expected_function_name = "mock_function"
semantic_function = KernelFunctionFromPrompt(
prompt=prompt,
prompt_template_config=prompt_template_config,
plugin_name=expected_plugin_name,
function_name=expected_function_name,
)
expected_plugin_description = "A unit test plugin"
plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=[semantic_function]
)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"] == semantic_function
def test_init_with_both_function_types(mock_function):
req_settings = PromptExecutionSettings(extension_data={"max_tokens": 2000, "temperature": 0.7, "top_p": 0.8})
prompt = "Use this input: {{$request}}"
prompt_template_config = PromptTemplateConfig(
template=prompt,
name="chat",
template_format="semantic-kernel",
input_variables=[
InputVariable(name="request", description="The user input", is_required=True),
],
execution_settings={"default": req_settings},
)
expected_plugin_name = "test_plugin"
expected_function_name = "prompt_function"
semantic_function = KernelFunction.from_prompt(
prompt=prompt,
prompt_template_config=prompt_template_config,
plugin_name=expected_plugin_name,
function_name=expected_function_name,
)
native_function = KernelFunctionFromMethod(method=mock_function, plugin_name="MockPlugin")
# Add both types to the default kernel plugin
expected_plugin_description = "A unit test plugin"
plugin = KernelPlugin(
name=expected_plugin_name,
description=expected_plugin_description,
functions=[semantic_function, native_function],
)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 2
for func in [semantic_function, native_function]:
assert func.name in plugin
assert plugin[func.name].name == func.name
def test_init_with_same_function_names(mock_function):
req_settings = PromptExecutionSettings(extension_data={"max_tokens": 2000, "temperature": 0.7, "top_p": 0.8})
prompt = "Use this input: {{$request}}"
prompt_template_config = PromptTemplateConfig(
template=prompt,
name="chat",
template_format="semantic-kernel",
input_variables=[
InputVariable(name="request", description="The user input", is_required=True),
],
execution_settings={"default": req_settings},
)
expected_plugin_name = "test_plugin"
expected_function_name = "mock_function"
semantic_function = KernelFunction.from_prompt(
prompt=prompt,
prompt_template_config=prompt_template_config,
plugin_name=expected_plugin_name,
function_name=expected_function_name,
)
native_function = KernelFunctionFromMethod(method=mock_function, plugin_name="MockPlugin")
plugin = KernelPlugin(name=expected_plugin_name, functions=[semantic_function, native_function])
assert len(plugin.functions) == 1
# region Dict-like methods
def test_set_item(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=expected_plugin_name)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description)
plugin["mock_function"] = native_function
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].metadata == native_function.metadata
function = plugin["mock_function"]
assert function.name == "mock_function"
def test_set(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=expected_plugin_name)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description)
plugin.set("mock_function", native_function)
assert plugin.name == expected_plugin_name
assert plugin.description == expected_plugin_description
assert len(plugin.functions) == 1
assert plugin["mock_function"].metadata == native_function.metadata
function = plugin.get("mock_function", None)
assert function.name == "mock_function"
function2 = plugin.get("mock_function2", None)
assert function2 is None
def test_set_default(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=expected_plugin_name)
native_function2 = KernelFunction.from_method(method=mock_function, plugin_name="other")
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description)
native_function == plugin.setdefault("mock_function", native_function)
native_function == plugin.setdefault("mock_function", native_function2)
assert len(plugin.functions) == 1
with raises(ValueError):
plugin.setdefault("mock_function2", None)
def test_update(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=expected_plugin_name)
native_function2 = KernelFunction.from_method(method=mock_function, plugin_name="p2")
native_function3 = KernelFunction.from_method(method=mock_function, plugin_name="p3")
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description)
plugin.update(mock_function=native_function)
assert len(plugin.functions) == 1
plugin.update([native_function2])
assert len(plugin.functions) == 1
plugin.update({"mock_function": native_function3})
assert len(plugin.functions) == 1
plugin2 = KernelPlugin(name="p2", description="p2")
plugin2.update(plugin)
assert len(plugin2.functions) == 1
plugin2.update([plugin])
assert len(plugin2.functions) == 1
with raises(TypeError):
plugin.update(1)
with raises(TypeError):
plugin.update(1, 2)
def test_iter():
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
func1 = KernelFunctionFromPrompt("test1", expected_plugin_name, None, "test prompt")
func2 = KernelFunctionFromPrompt("test1", expected_plugin_name, None, "test prompt")
func3 = KernelFunctionFromPrompt("test1", expected_plugin_name, None, "test prompt")
plugin = KernelPlugin(
name=expected_plugin_name, description=expected_plugin_description, functions=[func1, func2, func3]
)
for func in plugin:
assert func.metadata in [func1.metadata, func2.metadata, func3.metadata]
# region Properties
def test_get_functions_metadata(mock_function):
expected_plugin_name = "test_plugin"
expected_plugin_description = "A unit test plugin"
native_function = KernelFunction.from_method(method=mock_function, plugin_name=expected_plugin_name)
plugin = KernelPlugin(name=expected_plugin_name, description=expected_plugin_description, functions=native_function)
metadatas = plugin.get_functions_metadata()
assert len(metadatas) == 1
assert metadatas[0] == native_function.metadata
# region Class Methods
def test_from_directory():
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_plugins")
plugin = KernelPlugin.from_directory("TestMixedPlugin", plugins_directory)
assert plugin is not None
assert len(plugin.functions) == 3
assert plugin.name == "TestMixedPlugin"
assert plugin.get("TestFunctionYaml") is not None
assert plugin.get("echoAsync") is not None
assert plugin.get("TestFunction") is not None
def test_from_directory_parent_directory_does_not_exist():
# import plugins
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_plugins_fail")
# path to plugins directory
with raises(PluginInitializationError, match="Plugin directory does not exist"):
KernelPlugin.from_directory("TestPlugin", plugins_directory)
def test_from_python_fail():
with raises(PluginInitializationError, match="No class found in file"):
KernelPlugin.from_python_file(
"TestNativePluginNoClass",
os.path.join(
os.path.dirname(__file__),
"../../assets",
"test_native_plugins",
"TestNativePluginNoClass",
"native_function.py",
),
)
def test_from_python_in_directory_fail():
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_native_plugins")
# path to plugins directory
with raises(PluginInitializationError, match="No functions found in folder"):
KernelPlugin.from_directory("TestNativePluginNoClass", plugins_directory)
def test_from_yaml_in_directory_fail():
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_plugins")
# path to plugins directory
with raises(PluginInitializationError, match="No functions found in folder"):
KernelPlugin.from_directory("TestFunctionBadYaml", plugins_directory)
def test_from_directory_other():
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_plugins")
# path to plugins directory
with raises(PluginInitializationError, match="No functions found in folder"):
KernelPlugin.from_directory("TestNoFunction", plugins_directory)
def test_from_directory_with_args():
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets", "test_native_plugins")
# path to plugins directory
plugin = KernelPlugin.from_directory(
"TestNativePluginArgs",
plugins_directory,
class_init_arguments={"TestNativeEchoBotPlugin": {"static_input": "prefix "}},
)
result = plugin["echo"].method(text="test")
assert result == "prefix test"
def test_from_object_function(decorated_native_function):
plugin = KernelPlugin.from_object("TestPlugin", {"getLightStatusFunc": decorated_native_function})
assert plugin is not None
assert len(plugin.functions) == 1
assert plugin.functions.get("getLightStatus") is not None
def test_from_object_class(custom_plugin_class):
plugin = KernelPlugin.from_object("TestPlugin", custom_plugin_class())
assert plugin is not None
assert len(plugin.functions) == 1
assert plugin.functions.get("getLightStatus") is not None
def test_from_openapi():
openapi_spec_file = os.path.join(
os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml"
)
plugin = KernelPlugin.from_openapi(
plugin_name="TestOpenAPIPlugin",
openapi_document_path=openapi_spec_file,
)
assert plugin is not None
assert plugin.name == "TestOpenAPIPlugin"
assert plugin.functions.get("GetSecret") is not None
assert plugin.functions.get("SetSecret") is not None
def test_custom_spec_from_openapi():
openapi_spec_file = os.path.join(
os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml"
)
parser = OpenApiParser()
openapi_spec = parser.parse(openapi_spec_file)
plugin = KernelPlugin.from_openapi(
plugin_name="TestOpenAPIPlugin",
openapi_parsed_spec=openapi_spec,
)
assert plugin is not None
assert plugin.name == "TestOpenAPIPlugin"
assert plugin.functions.get("GetSecret") is not None
assert plugin.functions.get("SetSecret") is not None
def test_from_openapi_missing_document_and_parsed_spec_throws():
with raises(PluginInitializationError):
KernelPlugin.from_openapi(
plugin_name="TestOpenAPIPlugin",
openapi_document_path=None,
)
# region Static Methods
def test_parse_or_copy_fail():
with raises(ValueError):
KernelPlugin._parse_or_copy(None, "test")