mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: Pydantic-ify the KernelFunction class. Fix code paths related to tool_calls. (#4863)
### Motivation and Context Solves #4864 The KernelFunction was configured as a Python class without regards to using Pydantic fields and its validations. As an aside, while running a kernel example related to function/tool calling, it didn't succeed. <!-- 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 PR cleans up the KernelFunction by creating Pydantic fields. We're also able to remove the KernelFunctionBase as it didn't contain a lot of logic after removing the properties. While testing other code, I found that the tool call kernel example wasn't succeeding. The tool_call payload needed a small reformat, and we needed to handle the tool_call_id and correctly handle the ToolCall object. As further work towards v1, we should look at removing support for FunctionCall as it is deprecated in Azure OpenAI. We now see: ``` User:> What is 3+3? Mosscap:> Ah, the timeless question of arithmetic! The sum of 3 and 3 is undoubtedly a splendid number. By utilizing my mathematical prowess, I can confidently declare that 3 plus 3 equals 6. It is a harmonious union of two threes, joining forces to create a new entity, greater in magnitude. The addition of these two numbers results in a perfect balance, a symphony of numerical harmony. ``` <!-- 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]>
This commit is contained in:
co-authored by
Evan Mattson
parent
8931ab63b6
commit
7268d13ee8
@@ -216,7 +216,7 @@
|
||||
"source": [
|
||||
"async def setup_chat_with_memory(\n",
|
||||
" kernel: sk.Kernel,\n",
|
||||
") -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:\n",
|
||||
") -> Tuple[sk.KernelFunction, sk.KernelContext]:\n",
|
||||
" sk_prompt = \"\"\"\n",
|
||||
" ChatBot can have a conversation with you about any topic.\n",
|
||||
" It can give explicit instructions or say 'I don't know' if\n",
|
||||
@@ -276,7 +276,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:\n",
|
||||
"async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunction, context: sk.KernelContext) -> bool:\n",
|
||||
" try:\n",
|
||||
" user_input = input(\"User:> \")\n",
|
||||
" context[\"user_input\"] = user_input\n",
|
||||
@@ -537,7 +537,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.9.13"
|
||||
"version": "3.10.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
"source": [
|
||||
"async def setup_chat_with_memory(\n",
|
||||
" kernel: sk.Kernel,\n",
|
||||
") -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:\n",
|
||||
") -> Tuple[sk.KernelFunction, sk.KernelContext]:\n",
|
||||
" sk_prompt = \"\"\"\n",
|
||||
" ChatBot can have a conversation with you about any topic.\n",
|
||||
" It can give explicit instructions or say 'I don't know' if\n",
|
||||
@@ -355,7 +355,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:\n",
|
||||
"async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunction, context: sk.KernelContext) -> bool:\n",
|
||||
" try:\n",
|
||||
" user_input = input(\"User:> \")\n",
|
||||
" context[\"user_input\"] = user_input\n",
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_promp
|
||||
)
|
||||
from semantic_kernel.connectors.ai.open_ai.utils import (
|
||||
chat_completion_with_function_call,
|
||||
get_function_calling_object,
|
||||
get_tool_call_object,
|
||||
)
|
||||
from semantic_kernel.core_plugins.time_plugin import TimePlugin
|
||||
|
||||
@@ -76,7 +76,7 @@ chat_function = kernel.register_semantic_function("ChatBot", "Chat", function_co
|
||||
# to enable or disable function calling or set the function calling to a specific plugin.
|
||||
# see the openai_function_calling example for how to use this with a unrelated function definition
|
||||
filter = {"exclude_plugin": ["ChatBot"]}
|
||||
functions = get_function_calling_object(kernel, filter)
|
||||
functions = get_tool_call_object(kernel, filter)
|
||||
|
||||
|
||||
async def chat(context: sk.KernelContext) -> Tuple[bool, sk.KernelContext]:
|
||||
|
||||
@@ -11,7 +11,7 @@ from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_promp
|
||||
)
|
||||
from semantic_kernel.connectors.ai.open_ai.utils import (
|
||||
chat_completion_with_function_call,
|
||||
get_function_calling_object,
|
||||
get_tool_call_object,
|
||||
)
|
||||
from semantic_kernel.core_plugins import MathPlugin
|
||||
|
||||
@@ -30,8 +30,9 @@ you will return a full answer to me as soon as possible.
|
||||
|
||||
kernel = sk.Kernel()
|
||||
|
||||
# Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools.
|
||||
deployment_name, api_key, endpoint = sk.azure_openai_settings_from_dot_env()
|
||||
api_version = "2023-07-01-preview"
|
||||
api_version = "2023-12-01-preview"
|
||||
kernel.add_chat_service(
|
||||
"chat-gpt",
|
||||
sk_oai.AzureChatCompletion(
|
||||
@@ -61,10 +62,11 @@ prompt_config = sk.PromptTemplateConfig(
|
||||
max_tokens=2000,
|
||||
temperature=0.7,
|
||||
top_p=0.8,
|
||||
function_call="auto",
|
||||
functions=get_function_calling_object(kernel, {"exclude_plugin": ["ChatBot"]}),
|
||||
tool_choice="auto",
|
||||
tools=get_tool_call_object(kernel, {"exclude_plugin": ["ChatBot"]}),
|
||||
)
|
||||
)
|
||||
|
||||
prompt_template = OpenAIChatPromptTemplate("{{$user_input}}", kernel.prompt_template_engine, prompt_config)
|
||||
prompt_template.add_system_message(system_message)
|
||||
prompt_template.add_user_message("Hi there, who are you?")
|
||||
|
||||
@@ -42,7 +42,7 @@ async def search_memory_examples(kernel: sk.Kernel) -> None:
|
||||
|
||||
async def setup_chat_with_memory(
|
||||
kernel: sk.Kernel,
|
||||
) -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:
|
||||
) -> Tuple[sk.KernelFunction, sk.KernelContext]:
|
||||
"""
|
||||
When using Google PaLM to chat with memories, a chat prompt template is
|
||||
essential; otherwise, the kernel will send text prompts to the Google PaLM
|
||||
@@ -90,7 +90,7 @@ async def setup_chat_with_memory(
|
||||
return chat_func, context
|
||||
|
||||
|
||||
async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:
|
||||
async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunction, context: sk.KernelContext) -> bool:
|
||||
try:
|
||||
user_input = input("User:> ")
|
||||
context["user_input"] = user_input
|
||||
|
||||
@@ -33,7 +33,7 @@ async def search_memory_examples(kernel: sk.Kernel) -> None:
|
||||
|
||||
async def setup_chat_with_memory(
|
||||
kernel: sk.Kernel,
|
||||
) -> Tuple[sk.KernelFunctionBase, sk.KernelContext]:
|
||||
) -> Tuple[sk.KernelFunction, sk.KernelContext]:
|
||||
sk_prompt = """
|
||||
ChatBot can have a conversation with you about any topic.
|
||||
It can give explicit instructions or say 'I don't know' if
|
||||
@@ -68,7 +68,7 @@ async def setup_chat_with_memory(
|
||||
return chat_func, context
|
||||
|
||||
|
||||
async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunctionBase, context: sk.KernelContext) -> bool:
|
||||
async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunction, context: sk.KernelContext) -> bool:
|
||||
try:
|
||||
user_input = input("User:> ")
|
||||
context["user_input"] = user_input
|
||||
|
||||
@@ -4,7 +4,6 @@ from semantic_kernel import core_plugins, memory
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate
|
||||
from semantic_kernel.semantic_functions.prompt_template import PromptTemplate
|
||||
from semantic_kernel.semantic_functions.prompt_template_config import (
|
||||
@@ -50,7 +49,6 @@ __all__ = [
|
||||
"ChatPromptTemplate",
|
||||
"SemanticFunctionConfig",
|
||||
"ContextVariables",
|
||||
"KernelFunctionBase",
|
||||
"KernelContext",
|
||||
"memory",
|
||||
"core_plugins",
|
||||
|
||||
@@ -4,6 +4,7 @@ from typing import Optional
|
||||
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import (
|
||||
FunctionCall,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.open_ai.models.chat.tool_calls import ToolCall
|
||||
from semantic_kernel.models.chat.chat_message import ChatMessage
|
||||
|
||||
|
||||
@@ -13,5 +14,5 @@ class OpenAIChatMessage(ChatMessage):
|
||||
name: Optional[str] = None
|
||||
# TODO: handle tool_calls
|
||||
function_call: Optional[FunctionCall] = None
|
||||
tool_calls: Optional[dict] = None
|
||||
tool_calls: Optional[ToolCall] = None
|
||||
tool_call_id: Optional[str] = None
|
||||
|
||||
+9
-3
@@ -13,9 +13,11 @@ logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OpenAIChatPromptTemplate(ChatPromptTemplate[OpenAIChatMessage]):
|
||||
def add_function_response_message(self, name: str, content: Any) -> None:
|
||||
def add_function_response_message(self, name: str, content: Any, tool_call_id: Optional[str] = None) -> None:
|
||||
"""Add a function response message to the chat template."""
|
||||
self.messages.append(OpenAIChatMessage(role="function", name=name, fixed_content=str(content)))
|
||||
self.messages.append(
|
||||
OpenAIChatMessage(role="function", name=name, fixed_content=str(content), tool_call_id=tool_call_id)
|
||||
)
|
||||
|
||||
def add_tool_call_response_message(self, tool_call_id: str, content: Any) -> None:
|
||||
"""Add a tool call response message to the chat template."""
|
||||
@@ -51,13 +53,17 @@ class OpenAIChatPromptTemplate(ChatPromptTemplate[OpenAIChatMessage]):
|
||||
function_call = None
|
||||
tool_calls = kwargs.get("tool_calls")
|
||||
if tool_calls is not None:
|
||||
# TODO: update this when tool_calls is implemented
|
||||
# and allow for multiple tool calls
|
||||
ids = [tool_call.id for tool_call in tool_calls]
|
||||
if role == "assistant":
|
||||
self.messages.append(
|
||||
OpenAIChatMessage(
|
||||
role=role,
|
||||
fixed_content=message,
|
||||
name=name,
|
||||
tool_calls=tool_calls,
|
||||
tool_calls=tool_calls[0],
|
||||
tool_call_id=ids[0],
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
@@ -8,17 +8,45 @@ from openai.types.chat import ChatCompletion
|
||||
|
||||
from semantic_kernel import Kernel, KernelContext
|
||||
from semantic_kernel.connectors.ai.open_ai.models.chat.function_call import FunctionCall
|
||||
from semantic_kernel.connectors.ai.open_ai.models.chat.tool_calls import ToolCall
|
||||
from semantic_kernel.connectors.ai.open_ai.semantic_functions.open_ai_chat_prompt_template import (
|
||||
OpenAIChatPromptTemplate,
|
||||
)
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def _describe_function(function: KernelFunctionBase) -> Dict[str, str]:
|
||||
"""Create the object used for function_calling.
|
||||
def _describe_tool_call(function: KernelFunction) -> Dict[str, str]:
|
||||
"""Create the object used for the tool call.
|
||||
|
||||
Assumes that arguments for semantic functions are optional, for native functions required.
|
||||
"""
|
||||
func_view = function.describe()
|
||||
return {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": f"{func_view.plugin_name}-{func_view.name}",
|
||||
"description": func_view.description,
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
param.name: {
|
||||
"description": param.description,
|
||||
"type": param.type_ if param.type_ else "string",
|
||||
**({"enum": param.enum} if hasattr(param, "enum") else {}), # Added support for enum
|
||||
}
|
||||
for param in func_view.parameters
|
||||
},
|
||||
"required": [p.name for p in func_view.parameters if p.required],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _describe_function(function: KernelFunction) -> Dict[str, str]:
|
||||
"""Create the object used for function_calling.
|
||||
Assumes that arguments for semantic functions are optional, for native functions required.
|
||||
"""
|
||||
func_view = function.describe()
|
||||
@@ -35,8 +63,10 @@ def _describe_function(function: KernelFunctionBase) -> Dict[str, str]:
|
||||
}
|
||||
|
||||
|
||||
def get_function_calling_object(kernel: Kernel, filter: Dict[str, List[str]]) -> List[Dict[str, str]]:
|
||||
"""Create the object used for function_calling.
|
||||
def get_tool_call_object(kernel: Kernel, filter: Dict[str, List[str]]) -> List[Dict[str, str]]:
|
||||
"""Create the object used for a tool call.
|
||||
|
||||
This is the preferred method to create the tool call object.
|
||||
|
||||
args:
|
||||
kernel: the kernel.
|
||||
@@ -57,7 +87,39 @@ def get_function_calling_object(kernel: Kernel, filter: Dict[str, List[str]]) ->
|
||||
"exclude_function": ["plugin1-function1", "plugin2-function2"],
|
||||
}
|
||||
will return all functions except plugin1-function1 and plugin2-function2.
|
||||
caller_function_name: the name of the function that is calling the other functions.
|
||||
returns:
|
||||
a filtered list of dictionaries of the functions in the kernel that can be passed to the function calling api.
|
||||
"""
|
||||
return get_function_calling_object(kernel, filter, is_tool_call=True)
|
||||
|
||||
|
||||
def get_function_calling_object(
|
||||
kernel: Kernel, filter: Dict[str, List[str]], is_tool_call: Optional[bool] = False
|
||||
) -> List[Dict[str, str]]:
|
||||
"""Create the object used for a function call.
|
||||
|
||||
Note: although Azure has deprecated function calling, SK still supports it for the time being.
|
||||
|
||||
args:
|
||||
kernel: the kernel.
|
||||
filter: a dictionary with keys
|
||||
exclude_plugin, include_plugin, exclude_function, include_function
|
||||
and lists of the required filter.
|
||||
The function name should be in the format "plugin_name-function_name".
|
||||
Using exclude_plugin and include_plugin at the same time will raise an error.
|
||||
Using exclude_function and include_function at the same time will raise an error.
|
||||
If using include_* implies that all other function will be excluded.
|
||||
Example:
|
||||
filter = {
|
||||
"exclude_plugin": ["plugin1", "plugin2"],
|
||||
"include_function": ["plugin3-function1", "plugin4-function2"],
|
||||
}
|
||||
will return only plugin3-function1 and plugin4-function2.
|
||||
filter = {
|
||||
"exclude_function": ["plugin1-function1", "plugin2-function2"],
|
||||
}
|
||||
will return all functions except plugin1-function1 and plugin2-function2.
|
||||
is_tool_call: if True, the function will return a list of tool calls, otherwise a list of functions.
|
||||
returns:
|
||||
a filtered list of dictionaries of the functions in the kernel that can be passed to the function calling api.
|
||||
"""
|
||||
@@ -81,36 +143,53 @@ def get_function_calling_object(kernel: Kernel, filter: Dict[str, List[str]]) ->
|
||||
for (
|
||||
plugin_name,
|
||||
plugin,
|
||||
) in kernel.plugins.data.items():
|
||||
) in kernel.plugins.plugins.items():
|
||||
if plugin_name in exclude_plugin or (include_plugin and plugin_name not in include_plugin):
|
||||
continue
|
||||
for function_name, function in plugin.items():
|
||||
for function_name, function in plugin.functions.items():
|
||||
current_name = f"{plugin_name}-{function_name}"
|
||||
if current_name in exclude_function or (include_function and current_name not in include_function):
|
||||
continue
|
||||
result.append(_describe_function(function))
|
||||
result.append(_describe_tool_call(function) if is_tool_call else _describe_function(function))
|
||||
return result
|
||||
|
||||
|
||||
async def execute_function_call(kernel: Kernel, function_call: FunctionCall, log: Optional[Any] = None) -> str:
|
||||
if log:
|
||||
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
||||
result = await kernel.run(
|
||||
kernel.func(**function_call.split_name_dict()),
|
||||
input_vars=function_call.to_context_variables(),
|
||||
)
|
||||
logger.info(f"Function call result: {result}")
|
||||
async def execute(kernel: Kernel, func: KernelFunction, input_vars: ContextVariables) -> str:
|
||||
"""Execute a function and return the result.
|
||||
|
||||
Args:
|
||||
kernel (Kernel): the kernel to use.
|
||||
func (KernelFunction): the function to execute.
|
||||
input_vars (ContextVariables): the input variables.
|
||||
|
||||
Returns:
|
||||
str: the result of the execution.
|
||||
"""
|
||||
result = await kernel.run(func, input_vars=input_vars)
|
||||
logger.info(f"Execution result: {result}")
|
||||
return str(result)
|
||||
|
||||
|
||||
async def execute_function_call(kernel: Kernel, function_call: FunctionCall) -> str:
|
||||
"""Execute a function call and return the result."""
|
||||
func = kernel.func(**function_call.split_name_dict())
|
||||
input_vars = function_call.to_context_variables()
|
||||
return await execute(kernel, func, input_vars)
|
||||
|
||||
|
||||
async def execute_tool_call(kernel: Kernel, tool_call: ToolCall) -> str:
|
||||
"""Execute a tool call and return the result."""
|
||||
func = kernel.func(**tool_call.function.split_name_dict())
|
||||
input_vars = tool_call.function.to_context_variables()
|
||||
return await execute(kernel, func, input_vars)
|
||||
|
||||
|
||||
async def chat_completion_with_function_call(
|
||||
kernel: Kernel,
|
||||
context: KernelContext,
|
||||
chat_plugin_name: Optional[str] = None,
|
||||
chat_function_name: Optional[str] = None,
|
||||
chat_function: Optional[KernelFunctionBase] = None,
|
||||
*,
|
||||
log: Optional[Any] = None,
|
||||
chat_function: Optional[KernelFunction] = None,
|
||||
**kwargs: Dict[str, Any],
|
||||
) -> KernelContext:
|
||||
"""Perform a chat completion with auto-executing function calling.
|
||||
@@ -127,7 +206,7 @@ async def chat_completion_with_function_call(
|
||||
kernel: the kernel to use.
|
||||
context: the context to use.
|
||||
functions: the function calling object,
|
||||
make sure to use get_function_calling_object method to create it.
|
||||
make sure to use get_tool_call_object method to create it.
|
||||
Optional arguments:
|
||||
chat_plugin_name: the plugin name of the chat function.
|
||||
chat_function_name: the function name of the chat function.
|
||||
@@ -140,8 +219,6 @@ async def chat_completion_with_function_call(
|
||||
returns:
|
||||
the context with the result of the chat completion, just like a regular invoke/run_async.
|
||||
"""
|
||||
if log:
|
||||
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
||||
# check the number of function calls
|
||||
max_function_calls = kwargs.get("max_function_calls", 5)
|
||||
current_call_count = kwargs.get("current_call_count", 0)
|
||||
@@ -149,9 +226,9 @@ async def chat_completion_with_function_call(
|
||||
if chat_function is None:
|
||||
chat_function = kernel.func(plugin_name=chat_plugin_name, function_name=chat_function_name)
|
||||
assert isinstance(
|
||||
chat_function._chat_prompt_template, OpenAIChatPromptTemplate
|
||||
chat_function.chat_prompt_template, OpenAIChatPromptTemplate
|
||||
), "Please make sure to initialize your chat function with the OpenAIChatPromptTemplate class."
|
||||
settings = chat_function._chat_prompt_template.prompt_config.execution_settings
|
||||
settings = chat_function.chat_prompt_template.prompt_config.execution_settings
|
||||
if current_call_count >= max_function_calls:
|
||||
settings.functions = []
|
||||
context = await chat_function.invoke(
|
||||
@@ -159,13 +236,26 @@ async def chat_completion_with_function_call(
|
||||
# when the maximum number of function calls is reached, execute the chat function without Functions.
|
||||
settings=settings,
|
||||
)
|
||||
function_call = context.objects.pop("function_call", None)
|
||||
# if there is no function_call or if the content is not a FunctionCall object, return the context
|
||||
if function_call is None or not isinstance(function_call, FunctionCall):
|
||||
if not (results := context.objects.pop("results", None)):
|
||||
return context
|
||||
function_call = next(
|
||||
(
|
||||
fc
|
||||
for fc in (results[0].function_call or results[0].tool_calls or [None])
|
||||
if isinstance(fc, (FunctionCall, ToolCall))
|
||||
),
|
||||
None,
|
||||
)
|
||||
if function_call:
|
||||
execute_call = execute_tool_call if isinstance(function_call, ToolCall) else execute_function_call
|
||||
result = await execute_call(kernel, function_call)
|
||||
tool_call_id = function_call.id
|
||||
else:
|
||||
return context
|
||||
result = await execute_function_call(kernel, function_call)
|
||||
# add the result to the chat prompt template
|
||||
chat_function._chat_prompt_template.add_function_response_message(name=function_call.name, content=str(result))
|
||||
chat_function.chat_prompt_template.add_function_response_message(
|
||||
name=function_call.function.name, content=str(result), tool_call_id=tool_call_id
|
||||
)
|
||||
# request another completion
|
||||
return await chat_completion_with_function_call(
|
||||
kernel,
|
||||
|
||||
@@ -15,7 +15,7 @@ from semantic_kernel.connectors.ai.open_ai.const import (
|
||||
USER_AGENT,
|
||||
)
|
||||
from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition import kernel_function, kernel_function_context_parameter
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
@@ -251,7 +251,7 @@ def register_openapi_plugin(
|
||||
kernel: Kernel,
|
||||
plugin_name: str,
|
||||
openapi_document: str,
|
||||
) -> Dict[str, KernelFunctionBase]:
|
||||
) -> Dict[str, KernelFunction]:
|
||||
parser = OpenApiParser()
|
||||
parsed_doc = parser.parse(openapi_document)
|
||||
operations = parser.create_rest_api_operations(parsed_doc)
|
||||
|
||||
@@ -30,7 +30,6 @@ from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryB
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.plugin_definition.function_view import FunctionView
|
||||
from semantic_kernel.plugin_definition.kernel_plugin import KernelPlugin
|
||||
from semantic_kernel.plugin_definition.kernel_plugin_collection import (
|
||||
@@ -122,7 +121,7 @@ class Kernel(KernelBaseModel):
|
||||
super().__init__(plugins=plugins, prompt_template_engine=prompt_template_engine, memory=memory, **kwargs)
|
||||
|
||||
def add_plugin(
|
||||
self, plugin_name: str, functions: List[KernelFunctionBase], plugin: Optional[KernelPlugin] = None
|
||||
self, plugin_name: str, functions: List[KernelFunction], plugin: Optional[KernelPlugin] = None
|
||||
) -> None:
|
||||
"""
|
||||
Adds a plugin to the kernel's collection of plugins. If a plugin instance is provided,
|
||||
@@ -130,7 +129,7 @@ class Kernel(KernelBaseModel):
|
||||
|
||||
Args:
|
||||
plugin_name (str): The name of the plugin
|
||||
functions (List[KernelFunctionBase]): The functions to add to the plugin
|
||||
functions (List[KernelFunction]): The functions to add to the plugin
|
||||
plugin (Optional[KernelPlugin]): An optional pre-defined plugin instance
|
||||
"""
|
||||
if plugin is None:
|
||||
@@ -147,7 +146,7 @@ class Kernel(KernelBaseModel):
|
||||
plugin_name: Optional[str],
|
||||
function_name: str,
|
||||
function_config: SemanticFunctionConfig,
|
||||
) -> KernelFunctionBase:
|
||||
) -> KernelFunction:
|
||||
"""
|
||||
Creates a semantic function from the plugin name, function name and function config
|
||||
|
||||
@@ -157,7 +156,7 @@ class Kernel(KernelBaseModel):
|
||||
function_config (SemanticFunctionConfig): The function config
|
||||
|
||||
Returns:
|
||||
KernelFunctionBase: The created semantic function
|
||||
KernelFunction: The created semantic function
|
||||
|
||||
Raises:
|
||||
ValueError: If the plugin name or function name are invalid
|
||||
@@ -178,7 +177,7 @@ class Kernel(KernelBaseModel):
|
||||
self,
|
||||
plugin_name: Optional[str],
|
||||
kernel_function: Callable,
|
||||
) -> KernelFunctionBase:
|
||||
) -> KernelFunction:
|
||||
"""
|
||||
Creates a native function from the plugin name and kernel function
|
||||
|
||||
@@ -187,7 +186,7 @@ class Kernel(KernelBaseModel):
|
||||
kernel_function (Callable): The kernel function
|
||||
|
||||
Returns:
|
||||
KernelFunctionBase: The created native function
|
||||
KernelFunction: The created native function
|
||||
"""
|
||||
if not hasattr(kernel_function, "__kernel_function__"):
|
||||
raise KernelException(
|
||||
@@ -211,6 +210,7 @@ class Kernel(KernelBaseModel):
|
||||
|
||||
function = KernelFunction.from_native_method(kernel_function, plugin_name)
|
||||
self.add_plugin(plugin_name, [function])
|
||||
function.set_default_plugin_collection(self.plugins)
|
||||
|
||||
return function
|
||||
|
||||
@@ -319,9 +319,8 @@ class Kernel(KernelBaseModel):
|
||||
pipeline_step = 0
|
||||
for func in functions:
|
||||
while True:
|
||||
assert isinstance(func, KernelFunctionBase), (
|
||||
"All func arguments to Kernel.run*(inputs, func1, func2, ...) "
|
||||
"must be KernelFunctionBase instances"
|
||||
assert isinstance(func, KernelFunction), (
|
||||
"All func arguments to Kernel.run*(inputs, func1, func2, ...) " "must be KernelFunction instances"
|
||||
)
|
||||
|
||||
if context.error_occurred:
|
||||
@@ -393,7 +392,7 @@ class Kernel(KernelBaseModel):
|
||||
|
||||
return context
|
||||
|
||||
def func(self, plugin_name: str, function_name: str) -> KernelFunctionBase:
|
||||
def func(self, plugin_name: str, function_name: str) -> KernelFunction:
|
||||
if plugin_name not in self.plugins:
|
||||
raise ValueError(f"Plugin '{plugin_name}' not found")
|
||||
if function_name not in self.plugins[plugin_name]:
|
||||
@@ -711,7 +710,7 @@ class Kernel(KernelBaseModel):
|
||||
plugin_name: str,
|
||||
function_name: str,
|
||||
function_config: SemanticFunctionConfig,
|
||||
) -> KernelFunctionBase:
|
||||
) -> KernelFunction:
|
||||
function_type = function_config.prompt_template_config.type
|
||||
if not function_type == "completion":
|
||||
raise AIException(
|
||||
@@ -854,7 +853,7 @@ class Kernel(KernelBaseModel):
|
||||
plugin_name: Optional[str] = None,
|
||||
description: Optional[str] = None,
|
||||
**kwargs: Any,
|
||||
) -> "KernelFunctionBase":
|
||||
) -> "KernelFunction":
|
||||
function_name = function_name if function_name is not None else f"f_{generate_random_ascii_name()}"
|
||||
|
||||
config = PromptTemplateConfig(
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
|
||||
__all__ = [
|
||||
"KernelFunctionBase",
|
||||
"KernelFunction",
|
||||
]
|
||||
|
||||
@@ -152,7 +152,7 @@ class KernelContext(KernelBaseModel, Generic[SemanticTextMemoryT]):
|
||||
function_name {str} -- The function name.
|
||||
|
||||
Returns:
|
||||
KernelFunctionBase -- The function.
|
||||
KernelFunction -- The function.
|
||||
"""
|
||||
if self.plugins is None:
|
||||
raise ValueError("The plugin collection hasn't been set")
|
||||
|
||||
@@ -7,6 +7,13 @@ import sys
|
||||
from enum import Enum
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
from pydantic import Field, StringConstraints
|
||||
|
||||
if sys.version_info >= (3, 9):
|
||||
from typing import Annotated
|
||||
else:
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from semantic_kernel.connectors.ai.chat_completion_client_base import (
|
||||
ChatCompletionClientBase,
|
||||
)
|
||||
@@ -15,6 +22,7 @@ from semantic_kernel.connectors.ai.text_completion_client_base import (
|
||||
TextCompletionClientBase,
|
||||
)
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.memory.null_memory import NullMemory
|
||||
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
|
||||
from semantic_kernel.models.contents.chat_message_content import ChatMessageContent
|
||||
@@ -22,7 +30,6 @@ from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.delegate_handlers import DelegateHandlers
|
||||
from semantic_kernel.orchestration.delegate_inference import DelegateInference
|
||||
from semantic_kernel.orchestration.delegate_types import DelegateTypes
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.plugin_definition.function_view import FunctionView
|
||||
from semantic_kernel.plugin_definition.parameter_view import ParameterView
|
||||
from semantic_kernel.semantic_functions.chat_prompt_template import ChatPromptTemplate
|
||||
@@ -54,24 +61,93 @@ def store_results(chat_prompt: ChatPromptTemplate, results: List["ChatMessageCon
|
||||
return chat_prompt
|
||||
|
||||
|
||||
class KernelFunction(KernelFunctionBase):
|
||||
class KernelFunction(KernelBaseModel):
|
||||
"""
|
||||
Semantic Kernel function.
|
||||
|
||||
Attributes:
|
||||
plugin_name (str): The name of the plugin that contains this function. Must be upper/lower
|
||||
case letters and underscores with a minimum length of 1.
|
||||
description (Optional[str]): The description of the function.
|
||||
name (str): The name of the function. Must be upper/lower case letters and
|
||||
underscores with a minimum length of 1.
|
||||
is_semantic (bool): Whether the function is semantic.
|
||||
stream_function (Optional[Callable[..., Any]]): The stream function for the function.
|
||||
parameters (List[ParameterView]): The parameters for the function.
|
||||
delegate_type (DelegateTypes): The delegate type for the function.
|
||||
function (Callable[..., Any]): The function to call.
|
||||
plugins (Optional[KernelPluginCollection]): The collection of plugins.
|
||||
ai_service (Optional[Union[TextCompletionClientBase, ChatCompletionClientBase]]): The AI service.
|
||||
ai_prompt_execution_settings (PromptExecutionSettings): The AI prompt execution settings.
|
||||
chat_prompt_template (Optional[ChatPromptTemplate]): The chat prompt template.
|
||||
"""
|
||||
|
||||
# TODO: rebuild with proper pydantic fields
|
||||
_parameters: List[ParameterView]
|
||||
_delegate_type: DelegateTypes
|
||||
_function: Callable[..., Any]
|
||||
_plugin_collection: Optional["KernelPluginCollection"]
|
||||
_ai_service: Optional[Union[TextCompletionClientBase, ChatCompletionClientBase]]
|
||||
_ai_prompt_execution_settings: PromptExecutionSettings
|
||||
_chat_prompt_template: ChatPromptTemplate
|
||||
plugin_name: Annotated[str, StringConstraints(pattern=r"^[A-Za-z_]+$", min_length=1)]
|
||||
description: Optional[str] = Field(default=None)
|
||||
name: Annotated[str, StringConstraints(pattern=r"^[A-Za-z_]+$", min_length=1)]
|
||||
is_semantic: bool = Field(...)
|
||||
stream_function: Optional[Callable[..., Any]] = Field(default=None)
|
||||
parameters: List[ParameterView] = Field(...)
|
||||
delegate_type: DelegateTypes = Field(...)
|
||||
function: Callable[..., Any] = Field(...)
|
||||
plugins: Optional["KernelPluginCollection"] = Field(default=None)
|
||||
ai_service: Optional[Union[TextCompletionClientBase, ChatCompletionClientBase]] = Field(default=None)
|
||||
prompt_execution_settings: PromptExecutionSettings = Field(default_factory=PromptExecutionSettings)
|
||||
chat_prompt_template: Optional[ChatPromptTemplate] = Field(default=None)
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
delegate_type: DelegateTypes,
|
||||
delegate_function: Callable[..., Any],
|
||||
parameters: List[ParameterView],
|
||||
description: str,
|
||||
plugin_name: str,
|
||||
function_name: str,
|
||||
is_semantic: bool,
|
||||
delegate_stream_function: Optional[Callable[..., Any]] = None,
|
||||
**kwargs: Dict[str, Any],
|
||||
) -> None:
|
||||
"""
|
||||
Initializes a new instance of the KernelFunction class
|
||||
|
||||
Args:
|
||||
delegate_type (DelegateTypes): The delegate type for the function
|
||||
delegate_function (Callable[..., Any]): The delegate function for the function
|
||||
parameters (List[ParameterView]): The parameters for the function
|
||||
description (str): The description for the function
|
||||
plugin_name (str): The name of the plugin
|
||||
name (str): The name of the function
|
||||
is_semantic (bool): Whether the function is semantic
|
||||
delegate_stream_function (Optional[Callable[..., Any]]): The delegate stream function for the function
|
||||
kwargs (Dict[str, Any]): Additional keyword arguments
|
||||
"""
|
||||
chat_prompt_template = kwargs.pop("chat_prompt_template", None)
|
||||
|
||||
super().__init__(
|
||||
delegate_type=delegate_type,
|
||||
function=delegate_function,
|
||||
parameters=parameters,
|
||||
description=description,
|
||||
plugin_name=plugin_name,
|
||||
name=function_name,
|
||||
is_semantic=is_semantic,
|
||||
stream_function=delegate_stream_function,
|
||||
chat_prompt_template=chat_prompt_template,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def from_native_method(method, plugin_name="", log=None) -> "KernelFunction":
|
||||
if log:
|
||||
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
||||
def from_native_method(method: Callable[..., Any], plugin_name: str) -> "KernelFunction":
|
||||
"""
|
||||
Create a KernelFunction from a native method.
|
||||
|
||||
Args:
|
||||
method (Callable[..., Any]): The method to create the function from
|
||||
plugin_name (str): The name of the plugin
|
||||
|
||||
Returns:
|
||||
KernelFunction: The kernel function
|
||||
"""
|
||||
if method is None:
|
||||
raise ValueError("Method cannot be `None`")
|
||||
|
||||
@@ -126,10 +202,18 @@ class KernelFunction(KernelFunctionBase):
|
||||
plugin_name: str,
|
||||
function_name: str,
|
||||
function_config: SemanticFunctionConfig,
|
||||
log: Optional[Any] = None,
|
||||
) -> "KernelFunction":
|
||||
if log:
|
||||
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
||||
"""
|
||||
Create a KernelFunction from a semantic configuration.
|
||||
|
||||
Args:
|
||||
plugin_name (str): The name of the plugin
|
||||
function_name (str): The name of the function
|
||||
function_config (SemanticFunctionConfig): The function configuration
|
||||
|
||||
Returns:
|
||||
KernelFunction: The kernel function
|
||||
"""
|
||||
if function_config is None:
|
||||
raise ValueError("Function configuration cannot be `None`")
|
||||
|
||||
@@ -156,7 +240,8 @@ class KernelFunction(KernelFunctionBase):
|
||||
messages = await chat_prompt.render_messages(context)
|
||||
results = await client.complete_chat(messages, prompt_execution_settings)
|
||||
context.objects["results"] = results
|
||||
context.variables.update(str(results[0]))
|
||||
if results[0].content is not None:
|
||||
context.variables.update(str(results[0]))
|
||||
# TODO: most of this will be deleted once context is gone, just AIResponse object is then returned.
|
||||
chat_prompt = store_results(chat_prompt, results)
|
||||
except Exception as exc:
|
||||
@@ -207,93 +292,36 @@ class KernelFunction(KernelFunctionBase):
|
||||
chat_prompt_template=function_config.prompt_template if function_config.has_chat_prompt else None,
|
||||
)
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def plugin_name(self) -> str:
|
||||
return self._plugin_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_native(self) -> bool:
|
||||
return not self._is_semantic
|
||||
|
||||
@property
|
||||
def prompt_execution_settings(self) -> PromptExecutionSettings:
|
||||
return self._ai_prompt_execution_settings
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
delegate_type: DelegateTypes,
|
||||
delegate_function: Callable[..., Any],
|
||||
parameters: List[ParameterView],
|
||||
description: str,
|
||||
plugin_name: str,
|
||||
function_name: str,
|
||||
is_semantic: bool,
|
||||
log: Optional[Any] = None,
|
||||
delegate_stream_function: Optional[Callable[..., Any]] = None,
|
||||
**kwargs: Dict[str, Any],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
if log:
|
||||
logger.warning("The `log` parameter is deprecated. Please use the `logging` module instead.")
|
||||
self._delegate_type = delegate_type
|
||||
self._function = delegate_function
|
||||
self._parameters = parameters
|
||||
self._description = description
|
||||
self._plugin_name = plugin_name
|
||||
self._name = function_name
|
||||
self._is_semantic = is_semantic
|
||||
self._stream_function = delegate_stream_function
|
||||
self._plugin_collection = None
|
||||
self._ai_service = None
|
||||
self._ai_prompt_execution_settings = PromptExecutionSettings()
|
||||
self._chat_prompt_template = kwargs.get("chat_prompt_template", None)
|
||||
|
||||
def set_default_plugin_collection(self, plugins: "KernelPluginCollection") -> "KernelFunction":
|
||||
self._plugin_collection = plugins
|
||||
self.plugins = plugins
|
||||
return self
|
||||
|
||||
def set_ai_service(self, ai_service: Callable[[], TextCompletionClientBase]) -> "KernelFunction":
|
||||
if ai_service is None:
|
||||
raise ValueError("AI LLM service factory cannot be `None`")
|
||||
self._verify_is_semantic()
|
||||
self._ai_service = ai_service()
|
||||
self.ai_service = ai_service()
|
||||
return self
|
||||
|
||||
def set_chat_service(self, chat_service: Callable[[], ChatCompletionClientBase]) -> "KernelFunction":
|
||||
if chat_service is None:
|
||||
raise ValueError("Chat LLM service factory cannot be `None`")
|
||||
self._verify_is_semantic()
|
||||
self._ai_service = chat_service()
|
||||
self.ai_service = chat_service()
|
||||
return self
|
||||
|
||||
def set_ai_configuration(self, settings: PromptExecutionSettings) -> "KernelFunction":
|
||||
if settings is None:
|
||||
raise ValueError("AI LLM request settings cannot be `None`")
|
||||
self._verify_is_semantic()
|
||||
self._ai_prompt_execution_settings = settings
|
||||
self.prompt_execution_settings = settings
|
||||
return self
|
||||
|
||||
def set_chat_configuration(self, settings: PromptExecutionSettings) -> "KernelFunction":
|
||||
if settings is None:
|
||||
raise ValueError("Chat LLM request settings cannot be `None`")
|
||||
self._verify_is_semantic()
|
||||
self._ai_prompt_execution_settings = settings
|
||||
self.prompt_execution_settings = settings
|
||||
return self
|
||||
|
||||
def describe(self) -> FunctionView:
|
||||
@@ -302,7 +330,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
plugin_name=self.plugin_name,
|
||||
description=self.description,
|
||||
is_semantic=self.is_semantic,
|
||||
parameters=self._parameters,
|
||||
parameters=self.parameters,
|
||||
)
|
||||
|
||||
async def __call__(
|
||||
@@ -374,7 +402,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
context = KernelContext(
|
||||
variables=ContextVariables("") if variables is None else variables,
|
||||
memory=memory if memory is not None else NullMemory.instance,
|
||||
plugins=self._plugin_collection,
|
||||
plugins=self.plugins,
|
||||
)
|
||||
else:
|
||||
# If context is passed, we need to merge the variables
|
||||
@@ -398,7 +426,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
async def _invoke_semantic(self, context: "KernelContext", settings: PromptExecutionSettings, **kwargs):
|
||||
self._verify_is_semantic()
|
||||
self._ensure_context_has_plugins(context)
|
||||
new_context = await self._function(self._ai_service, settings or self._ai_prompt_execution_settings, context)
|
||||
new_context = await self.function(self.ai_service, settings or self.prompt_execution_settings, context)
|
||||
context.variables.merge_or_overwrite(new_context.variables)
|
||||
return context
|
||||
|
||||
@@ -407,16 +435,16 @@ class KernelFunction(KernelFunctionBase):
|
||||
|
||||
self._ensure_context_has_plugins(context)
|
||||
|
||||
delegate = DelegateHandlers.get_handler(self._delegate_type)
|
||||
delegate = DelegateHandlers.get_handler(self.delegate_type)
|
||||
# for python3.9 compatibility (staticmethod is not callable)
|
||||
if not hasattr(delegate, "__call__"):
|
||||
delegate = delegate.__func__
|
||||
new_context = await delegate(self._function, context)
|
||||
new_context = await delegate(self.function, context)
|
||||
|
||||
return new_context
|
||||
|
||||
def _verify_is_semantic(self) -> None:
|
||||
if self._is_semantic:
|
||||
if self.is_semantic:
|
||||
return
|
||||
|
||||
logger.error("The function is not semantic")
|
||||
@@ -426,7 +454,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
)
|
||||
|
||||
def _verify_is_native(self) -> None:
|
||||
if not self._is_semantic:
|
||||
if not self.is_semantic:
|
||||
return
|
||||
|
||||
logger.error("The function is not native")
|
||||
@@ -449,7 +477,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
context = KernelContext(
|
||||
variables=ContextVariables("") if variables is None else variables,
|
||||
memory=memory if memory is not None else NullMemory.instance,
|
||||
plugins=self._plugin_collection,
|
||||
plugins=self.plugins,
|
||||
)
|
||||
else:
|
||||
# If context is passed, we need to merge the variables
|
||||
@@ -479,8 +507,8 @@ class KernelFunction(KernelFunctionBase):
|
||||
async def _invoke_semantic_stream(self, context, settings):
|
||||
self._verify_is_semantic()
|
||||
self._ensure_context_has_plugins(context)
|
||||
async for stream_msg in self._stream_function(
|
||||
self._ai_service, settings or self._ai_prompt_execution_settings, context
|
||||
async for stream_msg in self.stream_function(
|
||||
self.ai_service, settings or self.prompt_execution_settings, context
|
||||
):
|
||||
yield stream_msg
|
||||
|
||||
@@ -495,7 +523,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
delegate = delegate.__func__
|
||||
|
||||
completion = ""
|
||||
async for partial in delegate(self._function, context):
|
||||
async for partial in delegate(self.function, context):
|
||||
completion += partial
|
||||
yield partial
|
||||
|
||||
@@ -505,7 +533,7 @@ class KernelFunction(KernelFunctionBase):
|
||||
if context.plugins is not None:
|
||||
return
|
||||
|
||||
context.plugins = self._plugin_collection
|
||||
context.plugins = self.plugins
|
||||
|
||||
def _trace_function_type_Call(self, type: Enum) -> None:
|
||||
logger.debug(f"Executing function type {type}: {type.name}")
|
||||
|
||||
@@ -1,142 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Any, Callable, Dict, Optional
|
||||
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.connectors.ai.text_completion_client_base import (
|
||||
TextCompletionClientBase,
|
||||
)
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.plugin_definition.function_view import FunctionView
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
|
||||
|
||||
class KernelFunctionBase(KernelBaseModel):
|
||||
@property
|
||||
@abstractmethod
|
||||
def name(self) -> str:
|
||||
"""
|
||||
Name of the function.
|
||||
|
||||
The name is used by the plugin collection and in
|
||||
prompt templates; e.g., {{pluginName.functionName}}
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def plugin_name(self) -> str:
|
||||
"""
|
||||
Name of the plugin that contains this function.
|
||||
|
||||
The name is used by the plugin collection and in
|
||||
prompt templates; e.g., {{pluginName.functionName}}"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def description(self) -> str:
|
||||
"""
|
||||
Function description.
|
||||
|
||||
The description is used in combination with embeddings
|
||||
when searching for relevant functions."""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_semantic(self) -> bool:
|
||||
"""
|
||||
Whether the function is semantic.
|
||||
|
||||
IMPORTANT: native functions might use semantic functions
|
||||
internally, so when this property is False, executing
|
||||
the function might still involve AI calls.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def is_native(self) -> bool:
|
||||
"""
|
||||
Whether the function is native.
|
||||
|
||||
IMPORTANT: native functions might use semantic functions
|
||||
internally, so when this property is True, executing
|
||||
the function might still involve AI calls.
|
||||
"""
|
||||
pass
|
||||
|
||||
@property
|
||||
@abstractmethod
|
||||
def prompt_execution_settings(self) -> PromptExecutionSettings:
|
||||
"""AI service settings"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def describe() -> FunctionView:
|
||||
"""
|
||||
Returns a description of the function,
|
||||
including its parameters
|
||||
|
||||
Returns:
|
||||
FunctionView -- The function description.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
async def invoke(
|
||||
self,
|
||||
input: Optional[str] = None,
|
||||
variables: ContextVariables = None,
|
||||
context: Optional["KernelContext"] = None,
|
||||
memory: Optional[SemanticTextMemoryBase] = None,
|
||||
settings: Optional[PromptExecutionSettings] = None,
|
||||
**kwargs: Dict[str, Any],
|
||||
) -> "KernelContext":
|
||||
"""
|
||||
Invokes the function with an explicit string input
|
||||
Keyword Arguments:
|
||||
input {str} -- The explicit string input (default: {None})
|
||||
variables {ContextVariables} -- The custom input
|
||||
context {KernelContext} -- The context to use
|
||||
memory: {SemanticTextMemoryBase} -- The memory to use
|
||||
settings {PromptExecutionSettings} -- LLM completion settings
|
||||
Returns:
|
||||
KernelContext -- The updated context, potentially a new one if
|
||||
context switching is implemented.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_ai_service(self, service_factory: Callable[[], TextCompletionClientBase]) -> "KernelFunctionBase":
|
||||
"""
|
||||
Sets the AI service used by the semantic function, passing in a factory
|
||||
method. The factory allows us to lazily instantiate the client and to
|
||||
properly handle its disposal
|
||||
|
||||
Arguments:
|
||||
service_factory -- AI service factory
|
||||
|
||||
Returns:
|
||||
KernelFunctionBase -- The function instance
|
||||
"""
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def set_ai_configuration(self, settings: PromptExecutionSettings) -> "KernelFunctionBase":
|
||||
"""
|
||||
Sets the AI completion settings used with LLM requests
|
||||
|
||||
Arguments:
|
||||
settings {PromptExecutionSettings} -- LLM completion settings
|
||||
|
||||
Returns:
|
||||
KernelFunctionBase -- The function instance
|
||||
"""
|
||||
pass
|
||||
@@ -11,7 +11,7 @@ import regex
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.planning.action_planner.action_planner_config import (
|
||||
ActionPlannerConfig,
|
||||
)
|
||||
@@ -37,7 +37,7 @@ class ActionPlanner:
|
||||
config: ActionPlannerConfig
|
||||
_stop_sequence: str = "#END-OF-PLAN"
|
||||
|
||||
_planner_function: KernelFunctionBase
|
||||
_planner_function: KernelFunction
|
||||
|
||||
_kernel: Kernel
|
||||
_prompt_template: str
|
||||
|
||||
@@ -17,7 +17,7 @@ from semantic_kernel.memory.null_memory import NullMemory
|
||||
from semantic_kernel.memory.semantic_text_memory_base import SemanticTextMemoryBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition.function_view import FunctionView
|
||||
from semantic_kernel.plugin_definition.kernel_plugin_collection import (
|
||||
KernelPluginCollection,
|
||||
@@ -26,10 +26,10 @@ from semantic_kernel.plugin_definition.kernel_plugin_collection import (
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Plan(KernelFunctionBase):
|
||||
class Plan:
|
||||
_state: ContextVariables = PrivateAttr()
|
||||
_steps: List["Plan"] = PrivateAttr()
|
||||
_function: KernelFunctionBase = PrivateAttr()
|
||||
_function: KernelFunction = PrivateAttr()
|
||||
_parameters: ContextVariables = PrivateAttr()
|
||||
_outputs: List[str] = PrivateAttr()
|
||||
_has_next_step: bool = PrivateAttr()
|
||||
@@ -98,7 +98,7 @@ class Plan(KernelFunctionBase):
|
||||
parameters: Optional[ContextVariables] = None,
|
||||
outputs: Optional[List[str]] = None,
|
||||
steps: Optional[List["Plan"]] = None,
|
||||
function: Optional[KernelFunctionBase] = None,
|
||||
function: Optional[KernelFunction] = None,
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self._name = "" if name is None else name
|
||||
@@ -122,7 +122,7 @@ class Plan(KernelFunctionBase):
|
||||
return cls(description=goal, plugin_name=cls.__name__)
|
||||
|
||||
@classmethod
|
||||
def from_function(cls, function: KernelFunctionBase) -> "Plan":
|
||||
def from_function(cls, function: KernelFunction) -> "Plan":
|
||||
plan = cls()
|
||||
plan.set_function(function)
|
||||
return plan
|
||||
@@ -184,11 +184,11 @@ class Plan(KernelFunctionBase):
|
||||
def set_ai_configuration(
|
||||
self,
|
||||
settings: PromptExecutionSettings,
|
||||
) -> KernelFunctionBase:
|
||||
) -> KernelFunction:
|
||||
if self._function is not None:
|
||||
self._function.set_ai_configuration(settings)
|
||||
|
||||
def set_ai_service(self, service: Callable[[], TextCompletionClientBase]) -> KernelFunctionBase:
|
||||
def set_ai_service(self, service: Callable[[], TextCompletionClientBase]) -> KernelFunction:
|
||||
if self._function is not None:
|
||||
self._function.set_ai_service(service)
|
||||
|
||||
@@ -215,7 +215,7 @@ class Plan(KernelFunctionBase):
|
||||
|
||||
return plan
|
||||
|
||||
def add_steps(self, steps: Union[List["Plan"], List[KernelFunctionBase]]) -> None:
|
||||
def add_steps(self, steps: Union[List["Plan"], List[KernelFunction]]) -> None:
|
||||
for step in steps:
|
||||
if type(step) is Plan:
|
||||
self._steps.append(step)
|
||||
@@ -233,7 +233,7 @@ class Plan(KernelFunctionBase):
|
||||
new_step.set_function(step)
|
||||
self._steps.append(new_step)
|
||||
|
||||
def set_function(self, function: KernelFunctionBase) -> None:
|
||||
def set_function(self, function: KernelFunction) -> None:
|
||||
self._function = function
|
||||
self._name = function.name
|
||||
self._plugin_name = function.plugin_name
|
||||
|
||||
@@ -25,7 +25,7 @@ from semantic_kernel.semantic_functions.semantic_function_config import (
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
|
||||
SEQUENTIAL_PLANNER_DEFAULT_DESCRIPTION = (
|
||||
"Given a request or command or goal generate a step by step plan to "
|
||||
@@ -47,7 +47,7 @@ class SequentialPlanner:
|
||||
|
||||
config: SequentialPlannerConfig
|
||||
_context: "KernelContext"
|
||||
_function_flow_function: "KernelFunctionBase"
|
||||
_function_flow_function: "KernelFunction"
|
||||
|
||||
def __init__(self, kernel: Kernel, config: SequentialPlannerConfig = None, prompt: str = None):
|
||||
assert isinstance(kernel, Kernel)
|
||||
|
||||
@@ -7,7 +7,7 @@ from xml.etree import ElementTree as ET
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.planning.plan import Plan
|
||||
from semantic_kernel.planning.planning_exception import PlanningException
|
||||
|
||||
@@ -23,8 +23,8 @@ class SequentialPlanParser:
|
||||
@staticmethod
|
||||
def get_plugin_function(
|
||||
context: KernelContext,
|
||||
) -> Callable[[str, str], Optional[KernelFunctionBase]]:
|
||||
def function(plugin_name: str, function_name: str) -> Optional[KernelFunctionBase]:
|
||||
) -> Callable[[str, str], Optional[KernelFunction]]:
|
||||
def function(plugin_name: str, function_name: str) -> Optional[KernelFunction]:
|
||||
try:
|
||||
return context.plugins[plugin_name][function_name]
|
||||
except KeyError:
|
||||
@@ -38,7 +38,7 @@ class SequentialPlanParser:
|
||||
def to_plan_from_xml(
|
||||
xml_string: str,
|
||||
goal: str,
|
||||
get_plugin_function: Callable[[str, str], Optional[KernelFunctionBase]],
|
||||
get_plugin_function: Callable[[str, str], Optional[KernelFunction]],
|
||||
allow_missing_functions: bool = False,
|
||||
):
|
||||
xml_string = "<xml>" + xml_string + "</xml>"
|
||||
|
||||
@@ -30,7 +30,7 @@ from semantic_kernel.semantic_functions.semantic_function_config import (
|
||||
)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -65,7 +65,7 @@ def is_null_or_empty(value: str) -> bool:
|
||||
class StepwisePlanner:
|
||||
config: StepwisePlannerConfig
|
||||
_context: "KernelContext"
|
||||
_function_flow_function: "KernelFunctionBase"
|
||||
_function_flow_function: "KernelFunction"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -373,7 +373,7 @@ class StepwisePlanner:
|
||||
function_name: str,
|
||||
prompt_template: str,
|
||||
config: PromptTemplateConfig = None,
|
||||
) -> "KernelFunctionBase":
|
||||
) -> "KernelFunction":
|
||||
template = PromptTemplate(prompt_template, kernel.prompt_template_engine, config)
|
||||
function_config = SemanticFunctionConfig(config, template)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ else:
|
||||
from pydantic import Field, StringConstraints
|
||||
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
|
||||
|
||||
class KernelPlugin(KernelBaseModel):
|
||||
@@ -22,24 +22,22 @@ class KernelPlugin(KernelBaseModel):
|
||||
name (str): The name of the plugin. The name can be upper/lower
|
||||
case letters and underscores.
|
||||
description (str): The description of the plugin.
|
||||
functions (Dict[str, KernelFunctionBase]): The functions in the plugin,
|
||||
functions (Dict[str, KernelFunction]): The functions in the plugin,
|
||||
indexed by their name.
|
||||
"""
|
||||
|
||||
name: Annotated[str, StringConstraints(pattern=r"^[A-Za-z_]+$", min_length=1)]
|
||||
description: Optional[str] = Field(default=None)
|
||||
functions: Optional[Dict[str, "KernelFunctionBase"]] = Field(default_factory=dict)
|
||||
functions: Optional[Dict[str, KernelFunction]] = Field(default_factory=dict)
|
||||
|
||||
def __init__(
|
||||
self, name: str, description: Optional[str] = None, functions: Optional[List[KernelFunctionBase]] = None
|
||||
):
|
||||
def __init__(self, name: str, description: Optional[str] = None, functions: Optional[List[KernelFunction]] = None):
|
||||
"""
|
||||
Initialize a new instance of the KernelPlugin class
|
||||
|
||||
Args:
|
||||
name (str): The name of the plugin.
|
||||
description (Optional[str]): The description of the plugin.
|
||||
functions (List[KernelFunctionBase]): The functions in the plugin.
|
||||
functions (List[KernelFunction]): The functions in the plugin.
|
||||
|
||||
Raises:
|
||||
ValueError: If the functions list contains duplicate function names.
|
||||
@@ -74,7 +72,7 @@ class KernelPlugin(KernelBaseModel):
|
||||
"""
|
||||
return function_name in self.functions.keys()
|
||||
|
||||
def __getitem__(self, name: str) -> "KernelFunctionBase":
|
||||
def __getitem__(self, name: str) -> "KernelFunction":
|
||||
"""Define the [] operator for the plugin
|
||||
|
||||
Args:
|
||||
@@ -92,13 +90,13 @@ class KernelPlugin(KernelBaseModel):
|
||||
|
||||
@classmethod
|
||||
def from_functions(
|
||||
cls, functions: List["KernelFunctionBase"], plugin_name: str, description: Optional[str] = None
|
||||
cls, functions: List["KernelFunction"], plugin_name: str, description: Optional[str] = None
|
||||
) -> "KernelPlugin":
|
||||
"""
|
||||
Creates a KernelPlugin from a KernelFunctionBase instance.
|
||||
Creates a KernelPlugin from a KernelFunction instance.
|
||||
|
||||
Args:
|
||||
functions (List[KernelFunctionBase]): The functions to create the plugin from.
|
||||
functions (List[KernelFunction]): The functions to create the plugin from.
|
||||
plugin_name (Optional[str]): The name of the plugin. If not specified,
|
||||
the name of the function will be used.
|
||||
description (Optional[str]): The description of the plugin.
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any, Dict, Iterable, List, Optional, TypeVar, Union
|
||||
from pydantic import Field
|
||||
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition.functions_view import FunctionsView
|
||||
from semantic_kernel.plugin_definition.kernel_plugin import KernelPlugin
|
||||
|
||||
@@ -76,13 +76,13 @@ class KernelPluginCollection(KernelBaseModel):
|
||||
raise ValueError(f"Plugin with name {plugin.name} already exists")
|
||||
self.plugins[plugin.name] = plugin
|
||||
|
||||
def add_plugin_from_functions(self, plugin_name: str, functions: List["KernelFunctionBase"]) -> None:
|
||||
def add_plugin_from_functions(self, plugin_name: str, functions: List[KernelFunction]) -> None:
|
||||
"""
|
||||
Add a function to a new plugin in the collection
|
||||
|
||||
Args:
|
||||
plugin_name (str): The name of the plugin to create.
|
||||
functions (List[KernelFunctionBase]): The functions to add to the plugin.
|
||||
functions (List[KernelFunction]): The functions to add to the plugin.
|
||||
|
||||
Raises:
|
||||
ValueError: If the function or plugin_name is None or invalid.
|
||||
@@ -95,12 +95,12 @@ class KernelPluginCollection(KernelBaseModel):
|
||||
plugin = KernelPlugin.from_functions(plugin_name=plugin_name, functions=functions)
|
||||
self.plugins[plugin_name] = plugin
|
||||
|
||||
def add_functions_to_plugin(self, functions: List["KernelFunctionBase"], plugin_name: str) -> None:
|
||||
def add_functions_to_plugin(self, functions: List[KernelFunction], plugin_name: str) -> None:
|
||||
"""
|
||||
Add functions to a plugin in the collection
|
||||
|
||||
Args:
|
||||
functions (List[KernelFunctionBase]): The function to add to the plugin.
|
||||
functions (List[KernelFunction]): The function to add to the plugin.
|
||||
plugin_name (str): The name of the plugin to add the function to.
|
||||
|
||||
Raises:
|
||||
@@ -201,7 +201,7 @@ class KernelPluginCollection(KernelBaseModel):
|
||||
for _, function in plugin.functions.items():
|
||||
if include_semantic and function.is_semantic:
|
||||
result.add_function(function.describe())
|
||||
elif include_native and function.is_native:
|
||||
elif include_native and not function.is_semantic:
|
||||
result.add_function(function.describe())
|
||||
|
||||
return result
|
||||
|
||||
@@ -115,7 +115,12 @@ class ChatPromptTemplate(PromptTemplate, Generic[ChatMessageT]):
|
||||
]:
|
||||
self.add_user_message(message=self.template)
|
||||
await asyncio.gather(*[message.render_message(context) for message in self.messages])
|
||||
return [message.as_dict() for message in self.messages]
|
||||
# Don't resend the assistant + tool_calls message as it will error
|
||||
return [
|
||||
message.as_dict()
|
||||
for message in self.messages
|
||||
if not (message.role == "assistant" and hasattr(message, "tool_calls"))
|
||||
]
|
||||
|
||||
def dump_messages(self) -> List[Dict[str, str]]:
|
||||
"""Return the messages as a list of dicts with role, content, name and function_call."""
|
||||
|
||||
@@ -5,7 +5,7 @@ from typing import Any, List, Optional, Tuple
|
||||
|
||||
import pydantic as pdt
|
||||
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition.kernel_plugin_collection import KernelPluginCollection
|
||||
from semantic_kernel.template_engine.blocks.block import Block
|
||||
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
|
||||
@@ -115,7 +115,7 @@ class CodeBlock(Block):
|
||||
|
||||
def _get_function_from_plugin_collection(
|
||||
self, plugins: KernelPluginCollection, f_block: FunctionIdBlock
|
||||
) -> Optional[KernelFunctionBase]:
|
||||
) -> Optional[KernelFunction]:
|
||||
"""
|
||||
Get the function from the plugin collection
|
||||
|
||||
|
||||
@@ -9,13 +9,13 @@ def test_can_be_instantiated():
|
||||
def test_can_be_imported():
|
||||
kernel = sk.Kernel()
|
||||
assert kernel.import_plugin(TextPlugin(), "text_plugin")
|
||||
assert kernel.plugins["text_plugin"]["trim"].is_native
|
||||
assert not kernel.plugins["text_plugin"]["trim"].is_semantic
|
||||
|
||||
|
||||
def test_can_be_imported_with_name():
|
||||
kernel = sk.Kernel()
|
||||
assert kernel.import_plugin(TextPlugin(), "text")
|
||||
assert kernel.plugins["text"]["trim"].is_native
|
||||
assert not kernel.plugins["text"]["trim"].is_semantic
|
||||
|
||||
|
||||
def test_can_trim():
|
||||
|
||||
@@ -5,7 +5,7 @@ import pytest
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.kernel_exception import KernelException
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition.kernel_function_decorator import kernel_function
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ async def test_register_valid_native_function():
|
||||
|
||||
registered_func = kernel.register_native_function("TestPlugin", decorated_native_function)
|
||||
|
||||
assert isinstance(registered_func, KernelFunctionBase)
|
||||
assert isinstance(registered_func, KernelFunction)
|
||||
assert kernel.plugins["TestPlugin"]["getLightStatus"] == registered_func
|
||||
func_result = await registered_func.invoke("testtest")
|
||||
assert func_result.result == "test"
|
||||
|
||||
@@ -30,17 +30,17 @@ def test_init_native_function_with_input_description():
|
||||
|
||||
native_function = KernelFunction.from_native_method(mock_method, "MockPlugin")
|
||||
|
||||
assert native_function._function == mock_method
|
||||
assert native_function._parameters[0].name == "input"
|
||||
assert native_function._parameters[0].description == "Mock input description"
|
||||
assert native_function._parameters[0].default_value == "default_input_value"
|
||||
assert native_function._parameters[0].type_ == "string"
|
||||
assert native_function._parameters[0].required is False
|
||||
assert native_function._parameters[1].name == "param1"
|
||||
assert native_function._parameters[1].description == "Param 1 description"
|
||||
assert native_function._parameters[1].default_value == "default_param1_value"
|
||||
assert native_function._parameters[1].type_ == "string"
|
||||
assert native_function._parameters[1].required is False
|
||||
assert native_function.function == mock_method
|
||||
assert native_function.parameters[0].name == "input"
|
||||
assert native_function.parameters[0].description == "Mock input description"
|
||||
assert native_function.parameters[0].default_value == "default_input_value"
|
||||
assert native_function.parameters[0].type_ == "string"
|
||||
assert native_function.parameters[0].required is False
|
||||
assert native_function.parameters[1].name == "param1"
|
||||
assert native_function.parameters[1].description == "Param 1 description"
|
||||
assert native_function.parameters[1].default_value == "default_param1_value"
|
||||
assert native_function.parameters[1].type_ == "string"
|
||||
assert native_function.parameters[1].required is False
|
||||
|
||||
|
||||
def test_init_native_function_without_input_description():
|
||||
@@ -63,12 +63,12 @@ def test_init_native_function_without_input_description():
|
||||
|
||||
native_function = KernelFunction.from_native_method(mock_method, "MockPlugin")
|
||||
|
||||
assert native_function._function == mock_method
|
||||
assert native_function._parameters[0].name == "param1"
|
||||
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_ == "string"
|
||||
assert native_function._parameters[0].required is True
|
||||
assert native_function.function == mock_method
|
||||
assert native_function.parameters[0].name == "param1"
|
||||
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_ == "string"
|
||||
assert native_function.parameters[0].required is True
|
||||
|
||||
|
||||
def test_init_native_function_from_kernel_function_decorator():
|
||||
@@ -89,12 +89,12 @@ def test_init_native_function_from_kernel_function_decorator():
|
||||
|
||||
native_function = KernelFunction.from_native_method(decorated_function, "MockPlugin")
|
||||
|
||||
assert native_function._function == 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_ == "string"
|
||||
assert native_function._parameters[0].required is False
|
||||
assert native_function.function == 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_ == "string"
|
||||
assert native_function.parameters[0].required is False
|
||||
|
||||
|
||||
def test_init_native_function_from_kernel_function_decorator_defaults():
|
||||
@@ -110,5 +110,5 @@ def test_init_native_function_from_kernel_function_decorator_defaults():
|
||||
|
||||
native_function = KernelFunction.from_native_method(decorated_function, "MockPlugin")
|
||||
|
||||
assert native_function._function == decorated_function
|
||||
assert len(native_function._parameters) == 0
|
||||
assert native_function.function == decorated_function
|
||||
assert len(native_function.parameters) == 0
|
||||
|
||||
@@ -4,10 +4,11 @@ from unittest.mock import MagicMock, Mock
|
||||
import pytest
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.memory.semantic_text_memory import SemanticTextMemoryBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.planning import ActionPlanner
|
||||
from semantic_kernel.planning.action_planner.action_planner_config import (
|
||||
ActionPlannerConfig,
|
||||
@@ -21,12 +22,14 @@ from semantic_kernel.plugin_definition.kernel_plugin_collection import (
|
||||
)
|
||||
|
||||
|
||||
def create_mock_function(function_view: FunctionView) -> Mock(spec=KernelFunctionBase):
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
def create_mock_function(function_view: FunctionView):
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
mock_function.describe.return_value = function_view
|
||||
mock_function.name = function_view.name
|
||||
mock_function.plugin_name = function_view.plugin_name
|
||||
mock_function.is_semantic = function_view.is_semantic
|
||||
mock_function.description = function_view.description
|
||||
mock_function.prompt_execution_settings = PromptExecutionSettings()
|
||||
return mock_function
|
||||
|
||||
|
||||
@@ -55,7 +58,7 @@ async def test_plan_creation():
|
||||
)
|
||||
|
||||
kernel = Mock(spec=Kernel)
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
memory = Mock(spec=SemanticTextMemoryBase)
|
||||
plugins = KernelPluginCollection()
|
||||
|
||||
@@ -186,7 +189,7 @@ async def test_empty_goal_throw():
|
||||
goal = ""
|
||||
|
||||
kernel = Mock(spec=Kernel)
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
memory = Mock(spec=SemanticTextMemoryBase)
|
||||
plugins = MagicMock(spec=KernelPluginCollection)
|
||||
|
||||
|
||||
@@ -4,11 +4,12 @@ from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.memory.semantic_text_memory import SemanticTextMemoryBase
|
||||
from semantic_kernel.orchestration.context_variables import ContextVariables
|
||||
from semantic_kernel.orchestration.kernel_context import KernelContext
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.planning.planning_exception import PlanningException
|
||||
from semantic_kernel.planning.sequential_planner.sequential_planner import (
|
||||
SequentialPlanner,
|
||||
@@ -22,10 +23,13 @@ from semantic_kernel.plugin_definition.kernel_plugin_collection import (
|
||||
|
||||
|
||||
def create_mock_function(function_view: FunctionView):
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
mock_function.describe.return_value = function_view
|
||||
mock_function.name = function_view.name
|
||||
mock_function.plugin_name = function_view.plugin_name
|
||||
mock_function.is_semantic = function_view.is_semantic
|
||||
mock_function.description = function_view.description
|
||||
mock_function.prompt_execution_settings = PromptExecutionSettings()
|
||||
return mock_function
|
||||
|
||||
|
||||
@@ -77,7 +81,7 @@ async def test_it_can_create_plan(goal):
|
||||
|
||||
return_context.variables.update(plan_string)
|
||||
|
||||
mock_function_flow_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function_flow_function = Mock(spec=KernelFunction)
|
||||
mock_function_flow_function.invoke.return_value = return_context
|
||||
|
||||
kernel.plugins = plugins
|
||||
@@ -130,7 +134,7 @@ async def test_invalid_xml_throws():
|
||||
|
||||
context = KernelContext.model_construct(variables=ContextVariables(), memory=memory, plugins=plugins)
|
||||
|
||||
mock_function_flow_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function_flow_function = Mock(spec=KernelFunction)
|
||||
mock_function_flow_function.invoke.return_value = return_context
|
||||
|
||||
kernel.plugins = plugins
|
||||
|
||||
@@ -4,8 +4,9 @@ from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.planning.planning_exception import PlanningException
|
||||
from semantic_kernel.planning.sequential_planner.sequential_planner_parser import (
|
||||
SequentialPlanParser,
|
||||
@@ -15,12 +16,14 @@ from semantic_kernel.plugin_definition.functions_view import FunctionsView
|
||||
from semantic_kernel.plugin_definition.kernel_plugin import KernelPlugin
|
||||
|
||||
|
||||
def create_mock_function(function_view: FunctionView) -> KernelFunctionBase:
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
def create_mock_function(function_view: FunctionView) -> KernelFunction:
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
mock_function.describe.return_value = function_view
|
||||
mock_function.name = function_view.name
|
||||
mock_function.plugin_name = function_view.plugin_name
|
||||
mock_function.description = function_view.description
|
||||
mock_function.is_semantic = function_view.is_semantic
|
||||
mock_function.prompt_execution_settings = PromptExecutionSettings()
|
||||
return mock_function
|
||||
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@ def test_create_plan_with_name_and_function():
|
||||
assert plan.function is test_function
|
||||
assert type(plan.parameters) is ContextVariables
|
||||
assert plan.is_semantic is test_function.is_semantic
|
||||
assert plan.is_native is test_function.is_native
|
||||
assert plan.is_native is not test_function.is_semantic
|
||||
assert plan.prompt_execution_settings == test_function.prompt_execution_settings
|
||||
assert plan.has_next_step is False
|
||||
assert plan.next_step_index == 0
|
||||
@@ -185,7 +185,7 @@ def test_add_step_to_plan():
|
||||
assert plan.function is test_function1
|
||||
assert type(plan.parameters) is ContextVariables
|
||||
assert plan.is_semantic is test_function1.is_semantic
|
||||
assert plan.is_native is test_function1.is_native
|
||||
assert plan.is_native is not test_function1.is_semantic
|
||||
assert plan.prompt_execution_settings == test_function1.prompt_execution_settings
|
||||
assert plan.has_next_step is True
|
||||
assert plan.next_step_index == 0
|
||||
|
||||
@@ -45,7 +45,7 @@ class TestCodeBlock:
|
||||
function = KernelFunction(
|
||||
delegate_type=DelegateTypes.InKernelContext,
|
||||
delegate_function=invoke,
|
||||
plugin_name="",
|
||||
plugin_name="test",
|
||||
function_name="funcName",
|
||||
description="",
|
||||
parameters=[],
|
||||
@@ -240,7 +240,7 @@ class TestCodeBlock:
|
||||
function = KernelFunction(
|
||||
delegate_type=DelegateTypes.InKernelContext,
|
||||
delegate_function=invoke,
|
||||
plugin_name="",
|
||||
plugin_name="test",
|
||||
function_name="funcName",
|
||||
description="",
|
||||
parameters=[],
|
||||
@@ -302,7 +302,7 @@ class TestCodeBlock:
|
||||
function = KernelFunction(
|
||||
delegate_type=DelegateTypes.InKernelContext,
|
||||
delegate_function=invoke,
|
||||
plugin_name="",
|
||||
plugin_name="test",
|
||||
function_name="funcName",
|
||||
description="",
|
||||
parameters=[],
|
||||
@@ -354,7 +354,7 @@ class TestCodeBlock:
|
||||
function = KernelFunction(
|
||||
delegate_type=DelegateTypes.InKernelContext,
|
||||
delegate_function=invoke,
|
||||
plugin_name="",
|
||||
plugin_name="test",
|
||||
function_name="funcName",
|
||||
description="",
|
||||
parameters=[],
|
||||
|
||||
@@ -126,7 +126,7 @@ async def test_it_renders_code_using_input(
|
||||
def my_function(cx: KernelContext) -> str:
|
||||
return f"F({cx.variables.input})"
|
||||
|
||||
func = KernelFunction.from_native_method(my_function)
|
||||
func = KernelFunction.from_native_method(my_function, "test")
|
||||
assert func is not None
|
||||
|
||||
variables.update("INPUT-BAR")
|
||||
@@ -146,7 +146,7 @@ async def test_it_renders_code_using_variables(
|
||||
def my_function(cx: KernelContext) -> str:
|
||||
return f"F({cx.variables.input})"
|
||||
|
||||
func = KernelFunction.from_native_method(my_function)
|
||||
func = KernelFunction.from_native_method(my_function, "test")
|
||||
assert func is not None
|
||||
|
||||
variables.set("myVar", "BAR")
|
||||
@@ -166,7 +166,7 @@ async def test_it_renders_code_using_variables_async(
|
||||
async def my_function(cx: KernelContext) -> str:
|
||||
return cx.variables.input
|
||||
|
||||
func = KernelFunction.from_native_method(my_function)
|
||||
func = KernelFunction.from_native_method(my_function, "test")
|
||||
assert func is not None
|
||||
|
||||
variables.set("myVar", "BAR")
|
||||
|
||||
@@ -5,14 +5,14 @@ from unittest.mock import AsyncMock, Mock
|
||||
import pytest
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.orchestration.kernel_function_base import KernelFunctionBase
|
||||
from semantic_kernel.orchestration.kernel_function import KernelFunction
|
||||
from semantic_kernel.plugin_definition.function_view import FunctionView
|
||||
from semantic_kernel.plugin_definition.kernel_plugin import KernelPlugin
|
||||
|
||||
|
||||
def create_mock_function(name) -> KernelFunctionBase:
|
||||
def create_mock_function(name) -> KernelFunction:
|
||||
function_view = FunctionView(name, "SummarizePlugin", "Summarize an input", [], True, True)
|
||||
mock_function = Mock(spec=KernelFunctionBase)
|
||||
mock_function = Mock(spec=KernelFunction)
|
||||
mock_function.describe.return_value = function_view
|
||||
mock_function.name = function_view.name
|
||||
mock_function.plugin_name = function_view.plugin_name
|
||||
|
||||
@@ -4,7 +4,6 @@ import pytest
|
||||
import typing_extensions as te
|
||||
from pydantic import Field, Json
|
||||
|
||||
from semantic_kernel import KernelFunctionBase
|
||||
from semantic_kernel.core_plugins.conversation_summary_plugin import (
|
||||
ConversationSummaryPlugin,
|
||||
)
|
||||
@@ -99,7 +98,7 @@ def kernel_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]:
|
||||
def my_function(cx: KernelContext) -> str:
|
||||
return f"F({cx.variables.input})"
|
||||
|
||||
return KernelFunction.from_native_method(my_function)
|
||||
return KernelFunction.from_native_method(my_function, "plugin")
|
||||
|
||||
def create_context_variables() -> ContextVariables:
|
||||
"""Return a context variables object."""
|
||||
@@ -177,7 +176,6 @@ PROTOCOLS = [
|
||||
|
||||
BASE_CLASSES = [
|
||||
SemanticTextMemoryBase,
|
||||
KernelFunctionBase,
|
||||
]
|
||||
|
||||
STATELESS_CLASSES = [
|
||||
|
||||
Reference in New Issue
Block a user