mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
### 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]>
232 lines
7.6 KiB
Python
232 lines
7.6 KiB
Python
# Copyright (c) Microsoft. All rights reserved.
|
|
|
|
"""A basic JSON-based planner for the Python Semantic Kernel"""
|
|
import json
|
|
|
|
import regex
|
|
|
|
from semantic_kernel.kernel import Kernel
|
|
from semantic_kernel.orchestration.context_variables import ContextVariables
|
|
|
|
|
|
class Plan:
|
|
"""A simple plan object for the Semantic Kernel"""
|
|
|
|
def __init__(self, prompt: str, goal: str, plan: str):
|
|
self.prompt = prompt
|
|
self.goal = goal
|
|
self.generated_plan = plan
|
|
|
|
def __str__(self):
|
|
return f"Prompt: {self.prompt}\nGoal: {self.goal}\nPlan: {self.generated_plan}"
|
|
|
|
def __repr__(self):
|
|
return str(self)
|
|
|
|
|
|
PROMPT = """
|
|
You are a planner for the Semantic Kernel.
|
|
Your job is to create a properly formatted JSON plan step by step, to satisfy the goal given.
|
|
Create a list of subtasks based off the [GOAL] provided.
|
|
Each subtask must be from within the [AVAILABLE FUNCTIONS] list. Do not use any functions that are not in the list.
|
|
Base your decisions on which functions to use from the description and the name of the function.
|
|
Sometimes, a function may take arguments. Provide them if necessary.
|
|
The plan should be as short as possible.
|
|
For example:
|
|
|
|
[AVAILABLE FUNCTIONS]
|
|
EmailConnector.LookupContactEmail
|
|
description: looks up the a contact and retrieves their email address
|
|
args:
|
|
- name: the name to look up
|
|
|
|
WriterPlugin.EmailTo
|
|
description: email the input text to a recipient
|
|
args:
|
|
- input: the text to email
|
|
- recipient: the recipient's email address. Multiple addresses may be included if separated by ';'.
|
|
|
|
WriterPlugin.Translate
|
|
description: translate the input to another language
|
|
args:
|
|
- input: the text to translate
|
|
- language: the language to translate to
|
|
|
|
WriterPlugin.Summarize
|
|
description: summarize input text
|
|
args:
|
|
- input: the text to summarize
|
|
|
|
FunPlugin.Joke
|
|
description: Generate a funny joke
|
|
args:
|
|
- input: the input to generate a joke about
|
|
|
|
[GOAL]
|
|
"Tell a joke about cars. Translate it to Spanish"
|
|
|
|
[OUTPUT]
|
|
{
|
|
"input": "cars",
|
|
"subtasks": [
|
|
{"function": "FunPlugin.Joke"},
|
|
{"function": "WriterPlugin.Translate", "args": {"language": "Spanish"}}
|
|
]
|
|
}
|
|
|
|
[AVAILABLE FUNCTIONS]
|
|
WriterPlugin.Brainstorm
|
|
description: Brainstorm ideas
|
|
args:
|
|
- input: the input to brainstorm about
|
|
|
|
EdgarAllenPoePlugin.Poe
|
|
description: Write in the style of author Edgar Allen Poe
|
|
args:
|
|
- input: the input to write about
|
|
|
|
WriterPlugin.EmailTo
|
|
description: Write an email to a recipient
|
|
args:
|
|
- input: the input to write about
|
|
- recipient: the recipient's email address.
|
|
|
|
WriterPlugin.Translate
|
|
description: translate the input to another language
|
|
args:
|
|
- input: the text to translate
|
|
- language: the language to translate to
|
|
|
|
[GOAL]
|
|
"Tomorrow is Valentine's day. I need to come up with a few date ideas.
|
|
She likes Edgar Allen Poe so write using his style.
|
|
E-mail these ideas to my significant other. Translate it to French."
|
|
|
|
[OUTPUT]
|
|
{
|
|
"input": "Valentine's Day Date Ideas",
|
|
"subtasks": [
|
|
{"function": "WriterPlugin.Brainstorm"},
|
|
{"function": "EdgarAllenPoePlugin.Poe"},
|
|
{"function": "WriterPlugin.EmailTo", "args": {"recipient": "significant_other"}},
|
|
{"function": "WriterPlugin.Translate", "args": {"language": "French"}}
|
|
]
|
|
}
|
|
|
|
[AVAILABLE FUNCTIONS]
|
|
{{$available_functions}}
|
|
|
|
[GOAL]
|
|
{{$goal}}
|
|
|
|
[OUTPUT]
|
|
"""
|
|
|
|
|
|
class BasicPlanner:
|
|
"""
|
|
Basic JSON-based planner for the Semantic Kernel.
|
|
"""
|
|
|
|
def _create_available_functions_string(self, kernel: Kernel) -> str:
|
|
"""
|
|
Given an instance of the Kernel, create the [AVAILABLE FUNCTIONS]
|
|
string for the prompt.
|
|
"""
|
|
# Get a dictionary of plugin names to all native and semantic functions
|
|
native_functions = kernel.plugins.get_functions_view().native_functions
|
|
semantic_functions = kernel.plugins.get_functions_view().semantic_functions
|
|
native_functions.update(semantic_functions)
|
|
|
|
# Create a mapping between all function names and their descriptions
|
|
# and also a mapping between function names and their parameters
|
|
all_functions = native_functions
|
|
plugin_names = list(all_functions.keys())
|
|
all_functions_descriptions_dict = {}
|
|
all_functions_params_dict = {}
|
|
|
|
for plugin_name in plugin_names:
|
|
for func in all_functions[plugin_name]:
|
|
key = plugin_name + "." + func.name
|
|
all_functions_descriptions_dict[key] = func.description
|
|
all_functions_params_dict[key] = func.parameters
|
|
|
|
# Create the [AVAILABLE FUNCTIONS] section of the prompt
|
|
available_functions_string = ""
|
|
for name in list(all_functions_descriptions_dict.keys()):
|
|
available_functions_string += name + "\n"
|
|
description = all_functions_descriptions_dict[name]
|
|
available_functions_string += "description: " + description + "\n"
|
|
available_functions_string += "args:\n"
|
|
|
|
# Add the parameters for each function
|
|
parameters = all_functions_params_dict[name]
|
|
for param in parameters:
|
|
if not param.description:
|
|
param_description = ""
|
|
else:
|
|
param_description = param.description
|
|
available_functions_string += "- " + param.name + ": " + param_description + "\n"
|
|
available_functions_string += "\n"
|
|
|
|
return available_functions_string
|
|
|
|
async def create_plan_async(
|
|
self,
|
|
goal: str,
|
|
kernel: Kernel,
|
|
prompt: str = PROMPT,
|
|
) -> Plan:
|
|
"""
|
|
Creates a plan for the given goal based off the functions that
|
|
are available in the kernel.
|
|
"""
|
|
|
|
# Create the semantic function for the planner with the given prompt
|
|
planner = kernel.create_semantic_function(prompt, max_tokens=1000, temperature=0.8)
|
|
|
|
available_functions_string = self._create_available_functions_string(kernel)
|
|
|
|
# Create the context for the planner
|
|
context = ContextVariables()
|
|
# Add the goal to the context
|
|
context["goal"] = goal
|
|
context["available_functions"] = available_functions_string
|
|
generated_plan = await planner.invoke_async(variables=context)
|
|
return Plan(prompt=prompt, goal=goal, plan=generated_plan)
|
|
|
|
async def execute_plan_async(self, plan: Plan, kernel: Kernel) -> str:
|
|
"""
|
|
Given a plan, execute each of the functions within the plan
|
|
from start to finish and output the result.
|
|
"""
|
|
|
|
# Filter out good JSON from the result in case additional text is present
|
|
json_regex = r"\{(?:[^{}]|(?R))*\}"
|
|
generated_plan_string = regex.search(json_regex, plan.generated_plan.result).group()
|
|
generated_plan = json.loads(generated_plan_string)
|
|
|
|
context = ContextVariables()
|
|
context["input"] = generated_plan["input"]
|
|
subtasks = generated_plan["subtasks"]
|
|
|
|
for subtask in subtasks:
|
|
plugin_name, function_name = subtask["function"].split(".")
|
|
sk_function = kernel.plugins.get_function(plugin_name, function_name)
|
|
|
|
# Get the arguments dictionary for the function
|
|
args = subtask.get("args", None)
|
|
if args:
|
|
for key, value in args.items():
|
|
context[key] = value
|
|
output = await sk_function.invoke_async(variables=context)
|
|
|
|
else:
|
|
output = await sk_function.invoke_async(variables=context)
|
|
|
|
# Override the input context variable with the output of the function
|
|
context["input"] = output.result
|
|
|
|
# At the very end, return the output of the last function
|
|
return output.result
|