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
@@ -18,7 +18,7 @@ def test_add_semantic_function():
)
functions_view = FunctionsView()
functions_view.add_function(view)
semantic_functions = functions_view._semantic_functions.get("skill1")
semantic_functions = functions_view.semantic_functions.get("skill1")
assert len(semantic_functions) == 1
assert semantic_functions[0] == view
@@ -34,7 +34,7 @@ def test_add_native_function():
)
functions_view = FunctionsView()
functions_view.add_function(view)
native_functions = functions_view._native_functions.get("skill2")
native_functions = functions_view.native_functions.get("skill2")
assert len(native_functions) == 1
assert native_functions[0] == view
@@ -59,8 +59,8 @@ def test_add_multiple_functions():
functions_view = FunctionsView()
functions_view.add_function(semantic_function)
functions_view.add_function(native_function)
semantic_functions = functions_view._semantic_functions.get("skill1")
native_functions = functions_view._native_functions.get("skill2")
semantic_functions = functions_view.semantic_functions.get("skill1")
native_functions = functions_view.native_functions.get("skill2")
assert len(semantic_functions) == 1
assert semantic_functions[0] == semantic_function
assert len(native_functions) == 1
+76 -1
View File
@@ -5,6 +5,7 @@ import pydantic as pdt
import pytest
import typing_extensions as te
from semantic_kernel import SKFunctionBase
from semantic_kernel.core_skills.conversation_summary_skill import (
ConversationSummarySkill,
)
@@ -17,6 +18,17 @@ from semantic_kernel.core_skills.time_skill import TimeSkill
from semantic_kernel.core_skills.wait_skill import WaitSkill
from semantic_kernel.core_skills.web_search_engine_skill import WebSearchEngineSkill
from semantic_kernel.sk_pydantic import PydanticField, SKBaseModel
from semantic_kernel.skill_definition.function_view import FunctionView
from semantic_kernel.skill_definition.functions_view import FunctionsView
from semantic_kernel.skill_definition.parameter_view import ParameterView
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 import SkillCollection
from semantic_kernel.skill_definition.skill_collection_base import SkillCollectionBase
from semantic_kernel.template_engine.blocks.block import Block
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.code_block import CodeBlock
@@ -52,6 +64,37 @@ class _Serializable(t.Protocol):
@pytest.fixture()
def sk_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]:
"""Return a factory for various objects in semantic-kernel."""
def create_functions_view() -> FunctionsView:
"""Return a functions view."""
result = FunctionsView()
result.add_function(
FunctionView(
name="function1",
skill_name="skill1",
description="Native function",
parameters=[],
is_semantic=False,
is_asynchronous=True,
)
)
result.add_function(
FunctionView(
name="function1",
skill_name="skill1",
description="Semantic function",
parameters=[],
is_semantic=True,
is_asynchronous=True,
)
)
return result
def create_skill_collection() -> SkillCollection:
"""Return a skill collection."""
# TODO: Add a few skills to this collection.
return SkillCollection()
cls_obj_map = {
Block: Block("foo"),
CodeBlock: CodeBlock("foo"),
@@ -62,6 +105,17 @@ def sk_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]:
CodeTokenizer: CodeTokenizer(log=logging.getLogger("test")),
PromptTemplateEngine: PromptTemplateEngine(logger=logging.getLogger("test")),
TemplateTokenizer: TemplateTokenizer(log=logging.getLogger("test")),
ParameterView: ParameterView("foo", "bar", default_value="baz"),
FunctionView: FunctionView(
"foo",
"bar",
"baz",
[ParameterView("qux", "bar", "baz")],
True,
False,
),
FunctionsView: create_functions_view(),
SkillCollection: create_skill_collection(),
}
def constructor(cls: t.Type[_Serializable]) -> _Serializable:
@@ -88,6 +142,17 @@ PROTOCOLS = [
TextRenderer,
]
BASE_CLASSES = [
ReadOnlySkillCollectionBase,
SkillCollectionBase,
SKFunctionBase,
]
# Classes that don't need serialization
UNSERIALIZED_CLASSES = [
ReadOnlySkillCollection,
]
STATELESS_CLASSES = [
CodeTokenizer,
PromptTemplateEngine,
@@ -103,12 +168,22 @@ PYDANTIC_MODELS = [
TextBlock,
ValBlock,
VarBlock,
ParameterView,
FunctionView,
FunctionsView,
SkillCollection,
]
class TestUsageInPydanticFields:
@pytest.mark.parametrize(
"sk_type", PROTOCOLS + ENUMS + PYDANTIC_MODELS + STATELESS_CLASSES
"sk_type",
BASE_CLASSES
+ PROTOCOLS
+ ENUMS
+ PYDANTIC_MODELS
+ STATELESS_CLASSES
+ UNSERIALIZED_CLASSES,
)
def test_usage_as_optional_field(
self,