mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: Adgudime/python/serialization/skill definition (#2329)
This is part 4 of the Serialization work for semantic kernel. Corresponding issue: https://github.com/microsoft/semantic-kernel/issues/131 ### Motivation and Context While there are no user facing changes, the ReadOnlySkillCollection has been modified to remove circular dependencies that were causing issues with serialization. <!-- Thank you for your contribution to the semantic-kernel repo! Please help reviewers and future users, providing the following information: 1. Why is this change required? 2. What problem does it solve? 3. What scenario does it contribute to? 4. If it fixes an open issue, please link to the issue here. --> ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄 --------- Co-authored-by: Abby Harrison <[email protected]> Co-authored-by: Abby Harrison <[email protected]>
This commit is contained in:
co-authored by
Abby Harrison
Abby Harrison
parent
e8342c60a1
commit
77eeff2cd6
@@ -1,8 +1,8 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from abc import abstractmethod
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Callable, Optional
|
||||
from typing import TYPE_CHECKING, Callable, Optional, TypeVar
|
||||
|
||||
from semantic_kernel.connectors.ai.complete_request_settings import (
|
||||
CompleteRequestSettings,
|
||||
@@ -13,6 +13,7 @@ from semantic_kernel.connectors.ai.text_completion_client_base import (
|
||||
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.sk_context import SKContext
|
||||
from semantic_kernel.sk_pydantic import PydanticField
|
||||
from semantic_kernel.skill_definition.function_view import FunctionView
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -21,7 +22,10 @@ if TYPE_CHECKING:
|
||||
)
|
||||
|
||||
|
||||
class SKFunctionBase(ABC):
|
||||
SKFunctionT = TypeVar("SKFunctionT", bound="SKFunctionBase")
|
||||
|
||||
|
||||
class SKFunctionBase(PydanticField):
|
||||
FUNCTION_PARAM_NAME_REGEX = r"^[0-9A-Za-z_]*$"
|
||||
FUNCTION_NAME_REGEX = r"^[0-9A-Za-z_]*$"
|
||||
SKILL_NAME_REGEX = r"^[0-9A-Za-z_]*$"
|
||||
|
||||
@@ -142,7 +142,8 @@ class ActionPlanner:
|
||||
skill, fun = generated_plan["plan"]["function"].split(".")
|
||||
function_ref = self._context.skills.get_function(skill, fun)
|
||||
self._logger.info(
|
||||
f"ActionPlanner has picked {skill}.{fun}. Reference to this function found in context: {function_ref}"
|
||||
f"ActionPlanner has picked {skill}.{fun}. Reference to this function"
|
||||
f" found in context: {function_ref}"
|
||||
)
|
||||
plan = Plan(description=goal, function=function_ref)
|
||||
else:
|
||||
@@ -150,8 +151,9 @@ class ActionPlanner:
|
||||
generated_plan["plan"]["function"]
|
||||
)
|
||||
self._logger.info(
|
||||
f"ActionPlanner has picked {generated_plan['plan']['function']}. \
|
||||
Reference to this function found in context: {function_ref}"
|
||||
f"ActionPlanner has picked {generated_plan['plan']['function']}. "
|
||||
" Reference to this function found in context:"
|
||||
f" {function_ref}"
|
||||
)
|
||||
plan = Plan(description=goal, function=function_ref)
|
||||
|
||||
@@ -253,13 +255,13 @@ class ActionPlanner:
|
||||
functions_view = context.skills.get_functions_view()
|
||||
available_functions = []
|
||||
|
||||
for functions in functions_view._native_functions.values():
|
||||
for functions in functions_view.native_functions.values():
|
||||
for func in functions:
|
||||
if func.skill_name.lower() == self._skill_name.lower():
|
||||
continue
|
||||
available_functions.append(self._create_function_string(func))
|
||||
|
||||
for functions in functions_view._semantic_functions.values():
|
||||
for functions in functions_view.semantic_functions.values():
|
||||
for func in functions:
|
||||
if func.skill_name.lower() == self._skill_name.lower():
|
||||
continue
|
||||
|
||||
@@ -119,8 +119,8 @@ class BasicPlanner:
|
||||
string for the prompt.
|
||||
"""
|
||||
# Get a dictionary of skill names to all native and semantic functions
|
||||
native_functions = kernel.skills.get_functions_view()._native_functions
|
||||
semantic_functions = kernel.skills.get_functions_view()._semantic_functions
|
||||
native_functions = kernel.skills.get_functions_view().native_functions
|
||||
semantic_functions = kernel.skills.get_functions_view().semantic_functions
|
||||
native_functions.update(semantic_functions)
|
||||
|
||||
# Create a mapping between all function names and their descriptions
|
||||
|
||||
@@ -393,7 +393,7 @@ class Plan(SKFunctionBase):
|
||||
# - Step Parameters (pull from variables or state by a key value)
|
||||
# - All other variables. These are carried over in case the function wants access to the ambient content.
|
||||
function_params = step.describe()
|
||||
for param in function_params._parameters:
|
||||
for param in function_params.parameters:
|
||||
if param.name.lower() == variables._main_key.lower():
|
||||
continue
|
||||
|
||||
|
||||
+10
-4
@@ -31,7 +31,10 @@ class SequentialPlannerFunctionViewExtension:
|
||||
function
|
||||
)
|
||||
|
||||
return f"{qualified_name}:\n description: {function.description}\n inputs:\n {inputs}"
|
||||
return (
|
||||
f"{qualified_name}:\n description: {function.description}\n inputs:\n "
|
||||
f" {inputs}"
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def to_fully_qualified_name(function: FunctionView):
|
||||
@@ -45,7 +48,10 @@ class SequentialPlannerFunctionViewExtension:
|
||||
for parameter in function.parameters
|
||||
]
|
||||
)
|
||||
return f"{function.name}:\n description: {function.description}\n inputs:\n{inputs}"
|
||||
return (
|
||||
f"{function.name}:\n description: {function.description}\n "
|
||||
f" inputs:\n{inputs}"
|
||||
)
|
||||
|
||||
|
||||
class SequentialPlannerSKContextExtension:
|
||||
@@ -97,8 +103,8 @@ class SequentialPlannerSKContextExtension:
|
||||
functions_view = context.skills.get_functions_view()
|
||||
|
||||
available_functions: List[FunctionView] = [
|
||||
*functions_view._semantic_functions.values(),
|
||||
*functions_view._native_functions.values(),
|
||||
*functions_view.semantic_functions.values(),
|
||||
*functions_view.native_functions.values(),
|
||||
]
|
||||
available_functions = itertools.chain.from_iterable(available_functions)
|
||||
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
|
||||
from typing import List
|
||||
|
||||
from semantic_kernel.sk_pydantic import SKBaseModel
|
||||
from semantic_kernel.skill_definition.parameter_view import ParameterView
|
||||
from semantic_kernel.utils.validation import validate_function_name
|
||||
|
||||
|
||||
class FunctionView:
|
||||
_name: str
|
||||
_skill_name: str
|
||||
_description: str
|
||||
_is_semantic: bool
|
||||
_is_asynchronous: bool
|
||||
_parameters: List[ParameterView]
|
||||
class FunctionView(SKBaseModel):
|
||||
name: str
|
||||
skill_name: str
|
||||
description: str
|
||||
is_semantic: bool
|
||||
parameters: List[ParameterView]
|
||||
is_asynchronous: bool = True
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -24,60 +25,11 @@ class FunctionView:
|
||||
is_asynchronous: bool = True,
|
||||
) -> None:
|
||||
validate_function_name(name)
|
||||
|
||||
self._name = name
|
||||
self._skill_name = skill_name
|
||||
self._description = description
|
||||
self._parameters = parameters
|
||||
self._is_semantic = is_semantic
|
||||
self._is_asynchronous = is_asynchronous
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def skill_name(self) -> str:
|
||||
return self._skill_name
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def parameters(self) -> List[ParameterView]:
|
||||
return self._parameters
|
||||
|
||||
@property
|
||||
def is_semantic(self) -> bool:
|
||||
return self._is_semantic
|
||||
|
||||
@property
|
||||
def is_asynchronous(self) -> bool:
|
||||
return self._is_asynchronous
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str) -> None:
|
||||
validate_function_name(value)
|
||||
|
||||
self._name = value
|
||||
|
||||
@skill_name.setter
|
||||
def skill_name(self, value: str) -> None:
|
||||
self._skill_name = value
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str) -> None:
|
||||
self._description = value
|
||||
|
||||
@parameters.setter
|
||||
def parameters(self, value: List[ParameterView]) -> None:
|
||||
self._parameters = value
|
||||
|
||||
@is_semantic.setter
|
||||
def is_semantic(self, value: bool) -> None:
|
||||
self._is_semantic = value
|
||||
|
||||
@is_asynchronous.setter
|
||||
def is_asynchronous(self, value: bool) -> None:
|
||||
self._is_asynchronous = value
|
||||
super().__init__(
|
||||
name=name,
|
||||
skill_name=skill_name,
|
||||
description=description,
|
||||
parameters=parameters,
|
||||
is_semantic=is_semantic,
|
||||
is_asynchronous=is_asynchronous,
|
||||
)
|
||||
|
||||
@@ -2,58 +2,61 @@
|
||||
|
||||
from typing import Dict, List
|
||||
|
||||
import pydantic as pdt
|
||||
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
from semantic_kernel.sk_pydantic import SKBaseModel
|
||||
from semantic_kernel.skill_definition.function_view import FunctionView
|
||||
|
||||
|
||||
class FunctionsView:
|
||||
_semantic_functions: Dict[str, List[FunctionView]]
|
||||
_native_functions: Dict[str, List[FunctionView]]
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._semantic_functions = {}
|
||||
self._native_functions = {}
|
||||
class FunctionsView(SKBaseModel):
|
||||
semantic_functions: Dict[str, List[FunctionView]] = pdt.Field(default_factory=dict)
|
||||
native_functions: Dict[str, List[FunctionView]] = pdt.Field(default_factory=dict)
|
||||
|
||||
def add_function(self, view: FunctionView) -> "FunctionsView":
|
||||
if view.is_semantic:
|
||||
if view.skill_name not in self._semantic_functions:
|
||||
self._semantic_functions[view.skill_name] = []
|
||||
self._semantic_functions[view.skill_name].append(view)
|
||||
if view.skill_name not in self.semantic_functions:
|
||||
self.semantic_functions[view.skill_name] = []
|
||||
self.semantic_functions[view.skill_name].append(view)
|
||||
else:
|
||||
if view.skill_name not in self._native_functions:
|
||||
self._native_functions[view.skill_name] = []
|
||||
self._native_functions[view.skill_name].append(view)
|
||||
if view.skill_name not in self.native_functions:
|
||||
self.native_functions[view.skill_name] = []
|
||||
self.native_functions[view.skill_name].append(view)
|
||||
|
||||
return self
|
||||
|
||||
def is_semantic(self, skill_name: str, function_name: str) -> bool:
|
||||
as_sf = self._semantic_functions.get(skill_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 = 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.",
|
||||
(
|
||||
f"There are 2 functions with the same name: {function_name}."
|
||||
"One is native and the other semantic."
|
||||
),
|
||||
)
|
||||
|
||||
return as_sf
|
||||
|
||||
def is_native(self, skill_name: str, function_name: str) -> bool:
|
||||
as_sf = self._semantic_functions.get(skill_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 = 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.",
|
||||
(
|
||||
f"There are 2 functions with the same name: {function_name}."
|
||||
"One is native and the other semantic."
|
||||
),
|
||||
)
|
||||
|
||||
return as_nf
|
||||
|
||||
@@ -1,41 +1,16 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from semantic_kernel.sk_pydantic import SKBaseModel
|
||||
from semantic_kernel.utils.validation import validate_function_param_name
|
||||
|
||||
|
||||
class ParameterView:
|
||||
_name: str
|
||||
_description: str
|
||||
_default_value: str
|
||||
class ParameterView(SKBaseModel):
|
||||
name: str
|
||||
description: str
|
||||
default_value: str
|
||||
|
||||
def __init__(self, name: str, description: str, default_value: str) -> None:
|
||||
validate_function_param_name(name)
|
||||
|
||||
self._name = name
|
||||
self._description = description
|
||||
self._default_value = default_value
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
return self._description
|
||||
|
||||
@property
|
||||
def default_value(self) -> str:
|
||||
return self._default_value
|
||||
|
||||
@name.setter
|
||||
def name(self, value: str) -> None:
|
||||
validate_function_param_name(value)
|
||||
self._name = value
|
||||
|
||||
@description.setter
|
||||
def description(self, value: str) -> None:
|
||||
self._description = value
|
||||
|
||||
@default_value.setter
|
||||
def default_value(self, value: str) -> None:
|
||||
self._default_value = value
|
||||
super().__init__(
|
||||
name=name, description=description, default_value=default_value
|
||||
)
|
||||
|
||||
@@ -1,56 +1,132 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Dict, Generic, Literal, Optional, Tuple, TypeVar
|
||||
|
||||
import pydantic as pdt
|
||||
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
from semantic_kernel.sk_pydantic import SKGenericModel
|
||||
from semantic_kernel.skill_definition.functions_view import FunctionsView
|
||||
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
|
||||
ReadOnlySkillCollectionBase,
|
||||
)
|
||||
from semantic_kernel.utils.null_logger import NullLogger
|
||||
from semantic_kernel.utils.static_property import static_property
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
|
||||
from semantic_kernel.skill_definition.functions_view import FunctionsView
|
||||
from semantic_kernel.skill_definition.skill_collection_base import (
|
||||
SkillCollectionBase,
|
||||
)
|
||||
|
||||
|
||||
class ReadOnlySkillCollection(ReadOnlySkillCollectionBase):
|
||||
_skill_collection: "SkillCollectionBase"
|
||||
SKFunctionT = TypeVar("SKFunctionT", bound="SKFunctionBase")
|
||||
|
||||
def __init__(self, skill_collection: "SkillCollectionBase") -> None:
|
||||
self._skill_collection = skill_collection
|
||||
|
||||
class ReadOnlySkillCollection(
|
||||
SKGenericModel, ReadOnlySkillCollectionBase, Generic[SKFunctionT]
|
||||
):
|
||||
data: Dict[str, Dict[str, SKFunctionT]]
|
||||
_log: Logger = pdt.PrivateAttr()
|
||||
|
||||
class Config:
|
||||
allow_mutation = False
|
||||
|
||||
def __init__(
|
||||
self, data: Dict[str, Dict[str, SKFunctionT]], log: Optional[Logger] = None
|
||||
) -> None:
|
||||
super().__init__(data=data)
|
||||
self._log = log or NullLogger()
|
||||
|
||||
def has_function(self, skill_name: Optional[str], function_name: str) -> bool:
|
||||
return self._skill_collection.has_function(skill_name, function_name)
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if s_name not in self.data:
|
||||
return False
|
||||
return f_name in self.data[s_name]
|
||||
|
||||
def has_semantic_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> bool:
|
||||
return self._skill_collection.has_semantic_function(skill_name, function_name)
|
||||
def has_semantic_function(self, skill_name: str, function_name: str) -> bool:
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name)
|
||||
if s_name not in self.data:
|
||||
return False
|
||||
if f_name not in self.data[s_name]:
|
||||
return False
|
||||
return self.data[s_name][f_name].is_semantic
|
||||
|
||||
def has_native_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> bool:
|
||||
return self._skill_collection.has_native_function(skill_name, function_name)
|
||||
def has_native_function(self, skill_name: str, function_name: str) -> bool:
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if s_name not in self.data:
|
||||
return False
|
||||
if f_name not in self.data[s_name]:
|
||||
return False
|
||||
return self.data[s_name][f_name].is_native
|
||||
|
||||
def get_semantic_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
self, skill_name: str, function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
return self._skill_collection.get_semantic_function(skill_name, function_name)
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name)
|
||||
if self.has_semantic_function(s_name, f_name):
|
||||
return self.data[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
)
|
||||
|
||||
def get_native_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
self, skill_name: str, function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
return self._skill_collection.get_native_function(skill_name, function_name)
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if self.has_native_function(s_name, f_name):
|
||||
return self.data[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
)
|
||||
|
||||
def get_functions_view(
|
||||
self, include_semantic: bool = True, include_native: bool = True
|
||||
) -> "FunctionsView":
|
||||
return self._skill_collection.get_functions_view(
|
||||
include_semantic, include_native
|
||||
)
|
||||
) -> FunctionsView:
|
||||
result = FunctionsView()
|
||||
|
||||
for skill in self.data.values():
|
||||
for function in skill.values():
|
||||
if include_semantic and function.is_semantic:
|
||||
result.add_function(function.describe())
|
||||
elif include_native and function.is_native:
|
||||
result.add_function(function.describe())
|
||||
|
||||
return result
|
||||
|
||||
def get_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
return self._skill_collection.get_function(skill_name, function_name)
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if self.has_function(s_name, f_name):
|
||||
return self.data[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
)
|
||||
|
||||
def _normalize_names(
|
||||
self,
|
||||
skill_name: Optional[str],
|
||||
function_name: str,
|
||||
allow_substitution: bool = False,
|
||||
) -> Tuple[str, str]:
|
||||
s_name, f_name = skill_name, function_name
|
||||
if s_name is None and allow_substitution:
|
||||
s_name = self.GLOBAL_SKILL
|
||||
|
||||
if s_name is None:
|
||||
raise ValueError("The skill name provided cannot be `None`")
|
||||
|
||||
s_name, f_name = s_name.lower(), f_name.lower()
|
||||
return s_name, f_name
|
||||
|
||||
@static_property
|
||||
def GLOBAL_SKILL() -> Literal["_GLOBAL_FUNCTIONS_"]:
|
||||
return "_GLOBAL_FUNCTIONS_"
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from semantic_kernel.sk_pydantic import PydanticField
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
|
||||
from semantic_kernel.skill_definition.functions_view import FunctionsView
|
||||
|
||||
|
||||
class ReadOnlySkillCollectionBase(ABC):
|
||||
class ReadOnlySkillCollectionBase(PydanticField, ABC):
|
||||
@abstractmethod
|
||||
def has_function(self, skill_name: Optional[str], function_name: str) -> bool:
|
||||
pass
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Optional
|
||||
|
||||
|
||||
def sk_function_context_parameter(
|
||||
*, name: str, description: str, default_value: Optional[str] = None
|
||||
*, name: str, description: str, default_value: str = ""
|
||||
):
|
||||
"""
|
||||
Decorator for SK function context parameters.
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
def sk_function(
|
||||
*,
|
||||
description: str = "",
|
||||
name: str = None,
|
||||
input_description: str = None,
|
||||
input_default_value: str = None
|
||||
name: str = "",
|
||||
input_description: str = "",
|
||||
input_default_value: str = "",
|
||||
):
|
||||
"""
|
||||
Decorator for SK functions.
|
||||
@@ -20,10 +20,10 @@ def sk_function(
|
||||
|
||||
def decorator(func):
|
||||
func.__sk_function__ = True
|
||||
func.__sk_function_description__ = description
|
||||
func.__sk_function_name__ = name if name else func.__name__
|
||||
func.__sk_function_input_description__ = input_description
|
||||
func.__sk_function_input_default_value__ = input_default_value
|
||||
func.__sk_function_description__ = description or ""
|
||||
func.__sk_function_name__ = name or func.__name__
|
||||
func.__sk_function_input_description__ = input_description or ""
|
||||
func.__sk_function_input_default_value__ = input_default_value or ""
|
||||
return func
|
||||
|
||||
return decorator
|
||||
|
||||
@@ -1,37 +1,68 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from logging import Logger
|
||||
from typing import TYPE_CHECKING, Dict, Literal, Optional, Tuple
|
||||
from typing import (
|
||||
TYPE_CHECKING,
|
||||
Dict,
|
||||
Generic,
|
||||
Optional,
|
||||
TypeVar,
|
||||
Union,
|
||||
)
|
||||
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
import pydantic as pdt
|
||||
|
||||
from semantic_kernel.sk_pydantic import SKGenericModel
|
||||
from semantic_kernel.skill_definition.functions_view import FunctionsView
|
||||
from semantic_kernel.skill_definition.read_only_skill_collection import (
|
||||
ReadOnlySkillCollection,
|
||||
)
|
||||
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
|
||||
ReadOnlySkillCollectionBase,
|
||||
)
|
||||
from semantic_kernel.skill_definition.skill_collection_base import SkillCollectionBase
|
||||
from semantic_kernel.utils.null_logger import NullLogger
|
||||
from semantic_kernel.utils.static_property import static_property
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
|
||||
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
|
||||
ReadOnlySkillCollectionBase,
|
||||
|
||||
SKFunctionT = TypeVar("SKFunctionT", bound="SKFunctionBase")
|
||||
|
||||
|
||||
class SkillCollection(SKGenericModel, SkillCollectionBase, Generic[SKFunctionT]):
|
||||
read_only_skill_collection_: ReadOnlySkillCollection = pdt.Field(
|
||||
alias="read_only_skill_collection"
|
||||
)
|
||||
_log: Logger = pdt.PrivateAttr()
|
||||
|
||||
|
||||
class SkillCollection(SkillCollectionBase):
|
||||
_skill_collection: Dict[str, Dict[str, "SKFunctionBase"]]
|
||||
_read_only_skill_collection: "ReadOnlySkillCollectionBase"
|
||||
_log: Logger
|
||||
def __init__(
|
||||
self,
|
||||
log: Optional[Logger] = None,
|
||||
skill_collection: Union[Dict[str, Dict[str, SKFunctionT]], None] = None,
|
||||
read_only_skill_collection_: Optional[ReadOnlySkillCollection] = None,
|
||||
) -> None:
|
||||
if skill_collection and read_only_skill_collection_:
|
||||
raise ValueError(
|
||||
"Only one of `skill_collection` and `read_only_skill_collection` can be"
|
||||
" provided"
|
||||
)
|
||||
elif not skill_collection and not read_only_skill_collection_:
|
||||
read_only_skill_collection = ReadOnlySkillCollection({})
|
||||
elif not read_only_skill_collection_:
|
||||
read_only_skill_collection = ReadOnlySkillCollection(skill_collection)
|
||||
else:
|
||||
read_only_skill_collection = read_only_skill_collection_
|
||||
super().__init__(read_only_skill_collection=read_only_skill_collection)
|
||||
self._log = log if log is not None else NullLogger()
|
||||
|
||||
@property
|
||||
def read_only_skill_collection(self) -> "ReadOnlySkillCollectionBase":
|
||||
return self._read_only_skill_collection
|
||||
def read_only_skill_collection(self) -> ReadOnlySkillCollectionBase:
|
||||
return self.read_only_skill_collection_
|
||||
|
||||
def __init__(self, log: Optional[Logger] = None) -> None:
|
||||
self._log = log if log is not None else NullLogger()
|
||||
self._read_only_skill_collection = ReadOnlySkillCollection(self)
|
||||
self._skill_collection = {}
|
||||
@property
|
||||
def skill_collection(self):
|
||||
return self.read_only_skill_collection_.data
|
||||
|
||||
def add_semantic_function(self, function: "SKFunctionBase") -> None:
|
||||
if function is None:
|
||||
@@ -40,114 +71,67 @@ class SkillCollection(SkillCollectionBase):
|
||||
s_name, f_name = function.skill_name, function.name
|
||||
s_name, f_name = s_name.lower(), f_name.lower()
|
||||
|
||||
if s_name not in self._skill_collection:
|
||||
self._skill_collection[s_name] = {}
|
||||
if s_name not in self.skill_collection:
|
||||
self.skill_collection[s_name] = {}
|
||||
|
||||
self._skill_collection[s_name][f_name] = function
|
||||
self.skill_collection[s_name][f_name] = function
|
||||
|
||||
def add_native_function(self, function: "SKFunctionBase") -> None:
|
||||
if function is None:
|
||||
raise ValueError("The function provided cannot be `None`")
|
||||
|
||||
s_name, f_name = function.skill_name, function.name
|
||||
s_name, f_name = self._normalize_names(s_name, f_name, True)
|
||||
s_name, f_name = self.read_only_skill_collection_._normalize_names(
|
||||
s_name, f_name, True
|
||||
)
|
||||
if s_name not in self.skill_collection:
|
||||
self.skill_collection[s_name] = {}
|
||||
|
||||
if s_name not in self._skill_collection:
|
||||
self._skill_collection[s_name] = {}
|
||||
|
||||
self._skill_collection[s_name][f_name] = function
|
||||
self.skill_collection[s_name][f_name] = function
|
||||
|
||||
def has_function(self, skill_name: Optional[str], function_name: str) -> bool:
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if s_name not in self._skill_collection:
|
||||
return False
|
||||
return f_name in self._skill_collection[s_name]
|
||||
return self.read_only_skill_collection_.has_function(skill_name, function_name)
|
||||
|
||||
def has_semantic_function(self, skill_name: str, function_name: str) -> bool:
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name)
|
||||
if s_name not in self._skill_collection:
|
||||
return False
|
||||
if f_name not in self._skill_collection[s_name]:
|
||||
return False
|
||||
return self._skill_collection[s_name][f_name].is_semantic
|
||||
def has_semantic_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> bool:
|
||||
return self.read_only_skill_collection_.has_semantic_function(
|
||||
skill_name, function_name
|
||||
)
|
||||
|
||||
def has_native_function(self, skill_name: str, function_name: str) -> bool:
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if s_name not in self._skill_collection:
|
||||
return False
|
||||
if f_name not in self._skill_collection[s_name]:
|
||||
return False
|
||||
return self._skill_collection[s_name][f_name].is_native
|
||||
def has_native_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> bool:
|
||||
return self.read_only_skill_collection_.has_native_function(
|
||||
skill_name, function_name
|
||||
)
|
||||
|
||||
def get_semantic_function(
|
||||
self, skill_name: str, function_name: str
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name)
|
||||
if self.has_semantic_function(s_name, f_name):
|
||||
return self._skill_collection[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
return self.read_only_skill_collection_.get_semantic_function(
|
||||
skill_name, function_name
|
||||
)
|
||||
|
||||
def get_native_function(
|
||||
self, skill_name: str, function_name: str
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if self.has_native_function(s_name, f_name):
|
||||
return self._skill_collection[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
return self.read_only_skill_collection_.get_native_function(
|
||||
skill_name, function_name
|
||||
)
|
||||
|
||||
def get_functions_view(
|
||||
self, include_semantic: bool = True, include_native: bool = True
|
||||
) -> FunctionsView:
|
||||
result = FunctionsView()
|
||||
|
||||
for skill in self._skill_collection.values():
|
||||
for function in skill.values():
|
||||
if include_semantic and function.is_semantic:
|
||||
result.add_function(function.describe())
|
||||
elif include_native and function.is_native:
|
||||
result.add_function(function.describe())
|
||||
|
||||
return result
|
||||
return self.read_only_skill_collection_.get_functions_view(
|
||||
include_semantic, include_native
|
||||
)
|
||||
|
||||
def get_function(
|
||||
self, skill_name: Optional[str], function_name: str
|
||||
) -> "SKFunctionBase":
|
||||
s_name, f_name = self._normalize_names(skill_name, function_name, True)
|
||||
if self.has_function(s_name, f_name):
|
||||
return self._skill_collection[s_name][f_name]
|
||||
|
||||
self._log.error(f"Function not available: {s_name}.{f_name}")
|
||||
raise KernelException(
|
||||
KernelException.ErrorCodes.FunctionNotAvailable,
|
||||
f"Function not available: {s_name}.{f_name}",
|
||||
)
|
||||
|
||||
def _normalize_names(
|
||||
self,
|
||||
skill_name: Optional[str],
|
||||
function_name: str,
|
||||
allow_substitution: bool = False,
|
||||
) -> Tuple[str, str]:
|
||||
s_name, f_name = skill_name, function_name
|
||||
if s_name is None and allow_substitution:
|
||||
s_name = self.GLOBAL_SKILL
|
||||
|
||||
if s_name is None:
|
||||
raise ValueError("The skill name provided cannot be `None`")
|
||||
|
||||
s_name, f_name = s_name.lower(), f_name.lower()
|
||||
return s_name, f_name
|
||||
return self.read_only_skill_collection_.get_function(skill_name, function_name)
|
||||
|
||||
@static_property
|
||||
def GLOBAL_SKILL() -> Literal["_GLOBAL_FUNCTIONS_"]:
|
||||
return "_GLOBAL_FUNCTIONS_"
|
||||
def GLOBAL_SKILL():
|
||||
return ReadOnlySkillCollection.GLOBAL_SKILL
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, TypeVar
|
||||
|
||||
from semantic_kernel.sk_pydantic import PydanticField
|
||||
from semantic_kernel.skill_definition.read_only_skill_collection_base import (
|
||||
ReadOnlySkillCollectionBase,
|
||||
)
|
||||
@@ -11,7 +12,10 @@ if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.sk_function_base import SKFunctionBase
|
||||
|
||||
|
||||
class SkillCollectionBase(ReadOnlySkillCollectionBase, ABC):
|
||||
SkillCollectionT = TypeVar("SkillCollectionT", bound="SkillCollectionBase")
|
||||
|
||||
|
||||
class SkillCollectionBase(ReadOnlySkillCollectionBase, PydanticField, ABC):
|
||||
@property
|
||||
@abstractmethod
|
||||
def read_only_skill_collection(self) -> ReadOnlySkillCollectionBase:
|
||||
|
||||
Reference in New Issue
Block a user