Files
semantic-kernel/python/semantic_kernel/template_engine/blocks/function_id_block.py
T
Evan MattsonandEvan Mattson 674af5468d Python: Rename skills to plugins. Update prompt template config to use execution settings to align with dotnet. (#4595)
### Motivation and Context

To better align with SK dotnet v1, Python skills have been renamed to
plugins. This removes the root samples skills directory as dotnet
supports the plugins and their configs. This the beginning of more work
to consolidate other files/names, per the backlog. I've run several
greps for files or file names containing "skill" (case insensitive), and
no results appear. Do alert if you find anything else that includes
"skill."

Fixes #3319 

<!-- 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

This is a large, breaking change PR as the replacement of "skills" to
"plugins" affects many files. It was difficult to break this up into
smaller chunks without leaving the SK Python repo in a bad state for
some time. Other updates include:

- Prompt template has been updated to use `execution_settings`, which
aligns with dotnet, from the previous `completion` that Python used.
- AzureOpenAI/OpenAI function calling has been updated to match the
current versions of tools/tool_choice. Parsing messages in the open_ai
utils respects this as it now looks for tool_calls and it configures the
function_call with the id (new), name, and argument).
- File renames, where the filename used to contain "skill" will look
like new code, but it's the same code, just with "plugin" in the name.
- Kernel examples and notebooks have been tested and are working. Unit
tests and integration tests are passing.

<!-- 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: Evan Mattson <[email protected]>
2024-01-19 16:47:30 +00:00

98 lines
3.1 KiB
Python

# Copyright (c) Microsoft. All rights reserved.
import logging
from re import match as re_match
from typing import Any, Optional, Tuple
import pydantic as pdt
from semantic_kernel.orchestration.context_variables import ContextVariables
from semantic_kernel.template_engine.blocks.block import Block
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
logger: logging.Logger = logging.getLogger(__name__)
class FunctionIdBlock(Block):
_plugin_name: str = pdt.PrivateAttr()
_function_name: str = pdt.PrivateAttr()
def __init__(self, content: Optional[str] = None, log: Optional[Any] = None):
super().__init__(content=content and content.strip())
if log:
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
function_name_parts = self.content.split(".")
if len(function_name_parts) > 2:
logger.error(f"Invalid function name `{self.content}`")
raise ValueError(
"A function name can contain at most one dot separating " "the plugin name from the function name"
)
if len(function_name_parts) == 2:
self._plugin_name = function_name_parts[0]
self._function_name = function_name_parts[1]
else:
self._plugin_name = ""
self._function_name = self.content
@property
def type(self) -> BlockTypes:
return BlockTypes.FUNCTION_ID
@property
def plugin_name(self) -> str:
return self._plugin_name
@plugin_name.setter
def plugin_name(self, value: str) -> None:
self._plugin_name = value
@property
def function_name(self) -> str:
return self._function_name
@function_name.setter
def function_name(self, value: str) -> None:
self._function_name = value
def is_valid(self) -> Tuple[bool, str]:
if self.content is None or len(self.content) == 0:
error_msg = "The function identifier is empty"
return False, error_msg
if not re_match(r"^[a-zA-Z0-9_.]*$", self.content):
# NOTE: this is not quite the same as
# utils.validation.validate_function_name
error_msg = (
f"The function identifier '{self.content}' contains invalid "
"characters. Only alphanumeric chars, underscore and a single "
"dot are allowed."
)
return False, error_msg
if self._has_more_than_one_dot(self.content):
error_msg = (
"The function identifier can contain max one '.' " "char separating plugin name from function name"
)
return False, error_msg
return True, ""
def render(self, _: Optional[ContextVariables] = None) -> str:
return self.content
def _has_more_than_one_dot(self, value: Optional[str]) -> bool:
if value is None or len(value) < 2:
return False
count = 0
for char in value:
if char == ".":
count += 1
if count > 1:
return True
return False