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:
Aditya Gudimella
2023-08-04 23:46:17 +00:00
committed by GitHub
co-authored by Abby Harrison Abby Harrison
parent e8342c60a1
commit 77eeff2cd6
16 changed files with 355 additions and 274 deletions
@@ -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
+1 -1
View File
@@ -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
@@ -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: