Python: Updated plugins (#5827)

### Motivation and Context

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

The loading of plugins and functions into Kernel was not fully
consistent and clear, this is now simplified.

Adding a plugin can now be done in two ways:
- multiple: `kernel.add_plugins[plugin1, plugin2]`
- single plugin: `kernel.add_plugin(plugin)`
- if the plugin here is a class, with methods that have the
kernel_function decorator then that is parsed into a plugin

Adding a function can now be done in three ways:
- multiple: `kernel.add_functions([func1, func2]` or
`kernel.add_function({'func1': func1})`
- single: `kernel.add_function('plugin_name', func1)`
- from a prompt: `kernel.add_function(function_name='func1',
prompt='test prompt', ...)`

In other words, all the different methods that were available have been
simplified, these methods also return the created plugin (or updated
plugin for the `add_functions` method. The `add_function` (singular)
method has a parameter `return_plugin` to control whether you get the
created or updated plugin, instead of the created function.

**An important callout:**

One big internal change that this introduces is that a function that
gets added to a plugin (new or existing) is automatically copied, the
metadata is deep-copied and the plugin_name in the metadata is updated,
so this sequence is valid now:

```python
@kernel_function(name='test')
def f(...):
   etc

func = KernelFunctionFromMethod(method=f)
func2 = kernel.add_function('plugin', func)
assert func != func2
```

Also closes: #5855

### Description

<!-- Describe your changes, the overall approach, the underlying design.
These notes will help understanding how your code works. Thanks! -->
Removed KernelPluginCollection
Updated KernelPlugin
   added from_... method to load from different sources
Updated KernelFunctionFromPrompt
   added from_yaml and from_directory methods.

Added and updated tests to 99% coverage of KernelPlugin and
KernelFunctionFromPrompt

### 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
- [ ] I didn't break anyone 😄
This commit is contained in:
Eduard van Valkenburg
2024-04-15 13:51:59 +00:00
committed by GitHub
parent a27a46dc4a
commit 6751f9d819
131 changed files with 5127 additions and 4922 deletions
+1 -1
View File
@@ -23,7 +23,7 @@ repos:
- id: black
files: \.py$
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.4
rev: v0.3.5
hooks:
- id: ruff
args: [ --fix, --exit-non-zero-on-fix ]
+2 -2
View File
@@ -87,7 +87,7 @@ prompt_template_config = sk.PromptTemplateConfig(
execution_settings=req_settings,
)
function = kernel.create_function_from_prompt(
function = kernel.add_function(
function_name="tldr_function",
plugin_name="tldr_plugin",
prompt_template_config=prompt_template_config,
@@ -107,7 +107,7 @@ if __name__ == "__main__":
```python
# Create a reusable function summarize function
summarize = kernel.create_function_from_prompt(
summarize = kernel.add_function(
function_name="tldr_function",
plugin_name="tldr_plugin",
prompt="{{$input}}\n\nOne line TLDR with the fewest words.",
+338 -342
View File
@@ -1,344 +1,340 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "3c93ac5b",
"metadata": {},
"source": [
"# Running Prompt Functions Inline"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "40201641",
"metadata": {},
"source": [
"The [previous notebook](./02-running-prompts-from-file.ipynb)\n",
"showed how to define a semantic function using a prompt template stored on a file.\n",
"\n",
"In this notebook, we'll show how to use the Semantic Kernel to define functions inline with your python code. This can be useful in a few scenarios:\n",
"\n",
"* Dynamically generating the prompt using complex rules at runtime\n",
"* Writing prompts by editing Python code instead of TXT files.\n",
"* Easily creating demos, like this document\n",
"\n",
"Prompt templates are defined using the SK template language, which allows to reference variables and functions. Read [this doc](https://aka.ms/sk/howto/configurefunction) to learn more about the design decisions for prompt templating. \n",
"\n",
"For now we'll use only the `{{$input}}` variable, and see more complex templates later.\n",
"\n",
"Almost all semantic function prompts have a reference to `{{$input}}`, which is the default way\n",
"a user can import content from the context variables."
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d90b0c13",
"metadata": {},
"source": [
"Prepare a semantic kernel instance first, loading also the AI service settings defined in the [Setup notebook](00-getting-started.ipynb):"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1da651d4",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.9.5b1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "68b770df",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.OpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3712b7c3",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"import semantic_kernel.connectors.ai.open_ai as sk_oai\n",
"from semantic_kernel.prompt_template.input_variable import InputVariable\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
"service_id = None\n",
"if selectedService == Service.OpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import OpenAITextCompletion\n",
"\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" service_id = \"oai_text_completion\"\n",
" kernel.add_service(\n",
" OpenAITextCompletion(\n",
" service_id=service_id, ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id\n",
" ),\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import AzureTextCompletion\n",
"\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" service_id = \"aoai_text_completion\"\n",
" kernel.add_service(\n",
" AzureTextCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n",
" )"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "589733c5",
"metadata": {},
"source": [
"Let's use a prompt to create a semantic function used to summarize content, allowing for some creativity and a sufficient number of tokens.\n",
"\n",
"The function will take in input the text to summarize."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae29c207",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"{{$input}}\n",
"Summarize the content above.\n",
"\"\"\"\n",
"\n",
"if selectedService == Service.OpenAI:\n",
" execution_settings = sk_oai.OpenAITextPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=\"gpt-3.5-turbo-instruct\",\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" execution_settings = sk_oai.OpenAITextPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=deployment,\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=prompt,\n",
" name=\"summarize\",\n",
" template_format=\"semantic-kernel\",\n",
" input_variables=[\n",
" InputVariable(name=\"input\", description=\"The user input\", is_required=True),\n",
" ],\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"summarize = kernel.create_function_from_prompt(\n",
" function_name=\"summarizeFunc\",\n",
" plugin_name=\"summarizePlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "f26b90c4",
"metadata": {},
"source": [
"Set up some content to summarize, here's an extract about Demo, an ancient Greek poet, taken from Wikipedia (https://en.wikipedia.org/wiki/Demo_(ancient_Greek_poet))."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "314557fb",
"metadata": {},
"outputs": [],
"source": [
"input_text = \"\"\"\n",
"Demo (ancient Greek poet)\n",
"From Wikipedia, the free encyclopedia\n",
"Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the Roman period, known for a single epigram, engraved upon the Colossus of Memnon, which bears her name. She speaks of herself therein as a lyric poetess dedicated to the Muses, but nothing is known of her life.[1]\n",
"Identity\n",
"Demo was evidently Greek, as her name, a traditional epithet of Demeter, signifies. The name was relatively common in the Hellenistic world, in Egypt and elsewhere, and she cannot be further identified. The date of her visit to the Colossus of Memnon cannot be established with certainty, but internal evidence on the left leg suggests her poem was inscribed there at some point in or after AD 196.[2]\n",
"Epigram\n",
"There are a number of graffiti inscriptions on the Colossus of Memnon. Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac couplets, entitled and presumably authored by \"Demo\" or \"Damo\" (the Greek inscription is difficult to read), is a dedication to the Muses.[2] The poem is traditionally published with the works of Balbilla, though the internal evidence suggests a different author.[1]\n",
"In the poem, Demo explains that Memnon has shown her special respect. In return, Demo offers the gift for poetry, as a gift to the hero. At the end of this epigram, she addresses Memnon, highlighting his divine status by recalling his strength and holiness.[2]\n",
"Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic dialect. The language indicates she was knowledgeable in Homeric poetry—'bearing a pleasant gift', for example, alludes to the use of that phrase throughout the Iliad and Odyssey.[a][2] \n",
"\"\"\""
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "bf0f2330",
"metadata": {},
"source": [
"...and run the summary function:"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b0e3b0c",
"metadata": {},
"outputs": [],
"source": [
"summary = await kernel.invoke(summarize, sk.KernelArguments(input=input_text))\n",
"\n",
"print(summary)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "1c2c1262",
"metadata": {},
"source": [
"# Using ChatCompletion for Semantic Plugins"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "29b59b28",
"metadata": {},
"source": [
"You can also use chat completion models (like `gpt-35-turbo` and `gpt4`) for creating plugins. Normally you would have to tweak the API to accommodate for a system and user role, but SK abstracts that away for you by using `kernel.add_service` and `AzureChatCompletion` or `OpenAIChatCompletion`"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4777f447",
"metadata": {},
"source": [
"Here's one more example of how to write an inline Semantic Function that gives a TLDR for a piece of text using a ChatCompletion model\n",
"\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5886aeb",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"service_id = None\n",
"if selectedService == Service.OpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
"\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" service_id = \"oai_chat_gpt\"\n",
" kernel.add_service(\n",
" OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n",
"\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" service_id = \"aoai_chat_completion\"\n",
" kernel.add_service(\n",
" AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea8128c8",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"\n",
"{{$input}}\n",
"\n",
"Give me the TLDR in 5 words or less.\n",
"\"\"\"\n",
"\n",
"text = \"\"\"\n",
" 1) A robot may not injure a human being or, through inaction,\n",
" allow a human being to come to harm.\n",
"\n",
" 2) A robot must obey orders given it by human beings except where\n",
" such orders would conflict with the First Law.\n",
"\n",
" 3) A robot must protect its own existence as long as such protection\n",
" does not conflict with the First or Second Law.\n",
"\"\"\"\n",
"\n",
"if selectedService == Service.OpenAI:\n",
" execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=\"gpt-3.5-turbo-1106\",\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=deployment,\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=prompt,\n",
" name=\"tldr\",\n",
" template_format=\"semantic-kernel\",\n",
" input_variables=[\n",
" InputVariable(name=\"input\", description=\"The user input\", is_required=True),\n",
" ],\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"tldr_function = kernel.create_function_from_prompt(\n",
" function_name=\"tldrFunction\",\n",
" plugin_name=\"tldrPlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")\n",
"\n",
"summary = await kernel.invoke(tldr_function, sk.KernelArguments(input=text))\n",
"\n",
"print(f\"Output: {summary}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "3c93ac5b",
"metadata": {},
"source": [
"# Running Prompt Functions Inline\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "40201641",
"metadata": {},
"source": [
"The [previous notebook](./02-running-prompts-from-file.ipynb)\n",
"showed how to define a semantic function using a prompt template stored on a file.\n",
"\n",
"In this notebook, we'll show how to use the Semantic Kernel to define functions inline with your python code. This can be useful in a few scenarios:\n",
"\n",
"- Dynamically generating the prompt using complex rules at runtime\n",
"- Writing prompts by editing Python code instead of TXT files.\n",
"- Easily creating demos, like this document\n",
"\n",
"Prompt templates are defined using the SK template language, which allows to reference variables and functions. Read [this doc](https://aka.ms/sk/howto/configurefunction) to learn more about the design decisions for prompt templating.\n",
"\n",
"For now we'll use only the `{{$input}}` variable, and see more complex templates later.\n",
"\n",
"Almost all semantic function prompts have a reference to `{{$input}}`, which is the default way\n",
"a user can import content from the context variables.\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d90b0c13",
"metadata": {},
"source": [
"Prepare a semantic kernel instance first, loading also the AI service settings defined in the [Setup notebook](00-getting-started.ipynb):\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1da651d4",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.9.5b1"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "68b770df",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.OpenAI"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3712b7c3",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
"service_id = None\n",
"if selectedService == Service.OpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import OpenAITextCompletion\n",
"\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" service_id = \"oai_text_completion\"\n",
" kernel.add_service(\n",
" OpenAITextCompletion(\n",
" service_id=service_id, ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id\n",
" ),\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import AzureTextCompletion\n",
"\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" service_id = \"aoai_text_completion\"\n",
" kernel.add_service(\n",
" AzureTextCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n",
" )"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "589733c5",
"metadata": {},
"source": [
"Let's use a prompt to create a semantic function used to summarize content, allowing for some creativity and a sufficient number of tokens.\n",
"\n",
"The function will take in input the text to summarize.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ae29c207",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"{{$input}}\n",
"Summarize the content above.\n",
"\"\"\"\n",
"\n",
"if selectedService == Service.OpenAI:\n",
" execution_settings = sk_oai.OpenAITextPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=\"gpt-3.5-turbo-instruct\",\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" execution_settings = sk_oai.OpenAITextPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=deployment,\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=prompt,\n",
" name=\"summarize\",\n",
" template_format=\"semantic-kernel\",\n",
" input_variables=[\n",
" InputVariable(name=\"input\", description=\"The user input\", is_required=True),\n",
" ],\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"summarize = kernel.add_function(\n",
" function_name=\"summarizeFunc\",\n",
" plugin_name=\"summarizePlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "f26b90c4",
"metadata": {},
"source": [
"Set up some content to summarize, here's an extract about Demo, an ancient Greek poet, taken from Wikipedia (https://en.wikipedia.org/wiki/Demo_(ancient_Greek_poet)).\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "314557fb",
"metadata": {},
"outputs": [],
"source": [
"input_text = \"\"\"\n",
"Demo (ancient Greek poet)\n",
"From Wikipedia, the free encyclopedia\n",
"Demo or Damo (Greek: Δεμώ, Δαμώ; fl. c. AD 200) was a Greek woman of the Roman period, known for a single epigram, engraved upon the Colossus of Memnon, which bears her name. She speaks of herself therein as a lyric poetess dedicated to the Muses, but nothing is known of her life.[1]\n",
"Identity\n",
"Demo was evidently Greek, as her name, a traditional epithet of Demeter, signifies. The name was relatively common in the Hellenistic world, in Egypt and elsewhere, and she cannot be further identified. The date of her visit to the Colossus of Memnon cannot be established with certainty, but internal evidence on the left leg suggests her poem was inscribed there at some point in or after AD 196.[2]\n",
"Epigram\n",
"There are a number of graffiti inscriptions on the Colossus of Memnon. Following three epigrams by Julia Balbilla, a fourth epigram, in elegiac couplets, entitled and presumably authored by \"Demo\" or \"Damo\" (the Greek inscription is difficult to read), is a dedication to the Muses.[2] The poem is traditionally published with the works of Balbilla, though the internal evidence suggests a different author.[1]\n",
"In the poem, Demo explains that Memnon has shown her special respect. In return, Demo offers the gift for poetry, as a gift to the hero. At the end of this epigram, she addresses Memnon, highlighting his divine status by recalling his strength and holiness.[2]\n",
"Demo, like Julia Balbilla, writes in the artificial and poetic Aeolic dialect. The language indicates she was knowledgeable in Homeric poetry—'bearing a pleasant gift', for example, alludes to the use of that phrase throughout the Iliad and Odyssey.[a][2] \n",
"\"\"\""
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "bf0f2330",
"metadata": {},
"source": [
"...and run the summary function:\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "7b0e3b0c",
"metadata": {},
"outputs": [],
"source": [
"summary = await kernel.invoke(summarize, sk.KernelArguments(input=input_text))\n",
"\n",
"print(summary)"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "1c2c1262",
"metadata": {},
"source": [
"# Using ChatCompletion for Semantic Plugins\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "29b59b28",
"metadata": {},
"source": [
"You can also use chat completion models (like `gpt-35-turbo` and `gpt4`) for creating plugins. Normally you would have to tweak the API to accommodate for a system and user role, but SK abstracts that away for you by using `kernel.add_service` and `AzureChatCompletion` or `OpenAIChatCompletion`\n"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "4777f447",
"metadata": {},
"source": [
"Here's one more example of how to write an inline Semantic Function that gives a TLDR for a piece of text using a ChatCompletion model\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c5886aeb",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"service_id = None\n",
"if selectedService == Service.OpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n",
"\n",
" api_key, org_id = sk.openai_settings_from_dot_env()\n",
" service_id = \"oai_chat_gpt\"\n",
" kernel.add_service(\n",
" OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n",
"\n",
" deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()\n",
" service_id = \"aoai_chat_completion\"\n",
" kernel.add_service(\n",
" AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n",
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ea8128c8",
"metadata": {},
"outputs": [],
"source": [
"prompt = \"\"\"\n",
"{{$input}}\n",
"\n",
"Give me the TLDR in 5 words or less.\n",
"\"\"\"\n",
"\n",
"text = \"\"\"\n",
" 1) A robot may not injure a human being or, through inaction,\n",
" allow a human being to come to harm.\n",
"\n",
" 2) A robot must obey orders given it by human beings except where\n",
" such orders would conflict with the First Law.\n",
"\n",
" 3) A robot must protect its own existence as long as such protection\n",
" does not conflict with the First or Second Law.\n",
"\"\"\"\n",
"\n",
"if selectedService == Service.OpenAI:\n",
" execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=\"gpt-3.5-turbo-1106\",\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"elif selectedService == Service.AzureOpenAI:\n",
" execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(\n",
" service_id=service_id,\n",
" ai_model_id=deployment,\n",
" max_tokens=2000,\n",
" temperature=0.7,\n",
" )\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=prompt,\n",
" name=\"tldr\",\n",
" template_format=\"semantic-kernel\",\n",
" input_variables=[\n",
" InputVariable(name=\"input\", description=\"The user input\", is_required=True),\n",
" ],\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"tldr_function = kernel.add_function(\n",
" function_name=\"tldrFunction\",\n",
" plugin_name=\"tldrPlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")\n",
"\n",
"summary = await kernel.invoke(tldr_function, sk.KernelArguments(input=text))\n",
"\n",
"print(f\"Output: {summary}\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+11 -15
View File
@@ -8,15 +8,15 @@
"source": [
"# Creating a basic chat experience with kernel arguments\n",
"\n",
"In this example, we show how you can build a simple chat bot by sending and updating the kernel arguments with your requests. \n",
"In this example, we show how you can build a simple chat bot by sending and updating the kernel arguments with your requests.\n",
"\n",
"We introduce the Kernel Arguments object which in this demo functions similarly as a key-value store that you can use when running the kernel.\n",
"\n",
"The chat history is local (i.e. in your computer's RAM) and not persisted anywhere beyond the life of this Jupyter session.\n",
"\n",
"In future examples, we will show how to persist the chat history on disk so that you can bring it into your applications. \n",
"In future examples, we will show how to persist the chat history on disk so that you can bring it into your applications.\n",
"\n",
"In this chat scenario, as the user talks back and forth with the bot, the chat context gets populated with the history of the conversation. During each new run of the kernel, the kernel arguments and chat history can provide the AI with its variables' content. "
"In this chat scenario, as the user talks back and forth with the bot, the chat context gets populated with the history of the conversation. During each new run of the kernel, the kernel arguments and chat history can provide the AI with its variables' content.\n"
]
},
{
@@ -50,10 +50,6 @@
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"import semantic_kernel.connectors.ai.open_ai as sk_oai\n",
"from semantic_kernel.prompt_template.input_variable import InputVariable\n",
"from semantic_kernel.contents.chat_history import ChatHistory\n",
"from semantic_kernel.functions.kernel_arguments import KernelArguments\n",
"\n",
"kernel = sk.Kernel()\n",
"\n",
@@ -82,7 +78,7 @@
"id": "7971783d",
"metadata": {},
"source": [
"Let's define a prompt outlining a dialogue chat bot."
"Let's define a prompt outlining a dialogue chat bot.\n"
]
},
{
@@ -107,7 +103,7 @@
"id": "61716b16",
"metadata": {},
"source": [
"Register your semantic function"
"Register your semantic function\n"
]
},
{
@@ -143,7 +139,7 @@
" execution_settings=execution_settings,\n",
")\n",
"\n",
"chat_function = kernel.create_function_from_prompt(\n",
"chat_function = kernel.add_function(\n",
" function_name=\"chat\",\n",
" plugin_name=\"chatPlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
@@ -167,7 +163,7 @@
"id": "6e8a676f",
"metadata": {},
"source": [
"Initialize the Kernel Arguments"
"Initialize the Kernel Arguments\n"
]
},
{
@@ -186,7 +182,7 @@
"id": "4ce7c497",
"metadata": {},
"source": [
"Chat with the Bot"
"Chat with the Bot\n"
]
},
{
@@ -206,7 +202,7 @@
"id": "a5b03748",
"metadata": {},
"source": [
"Update the history with the output"
"Update the history with the output\n"
]
},
{
@@ -225,7 +221,7 @@
"id": "23a2eb02",
"metadata": {},
"source": [
"Keep Chatting!"
"Keep Chatting!\n"
]
},
{
@@ -295,7 +291,7 @@
"id": "c30bac97",
"metadata": {},
"source": [
"After chatting for a while, we have built a growing history, which we are attaching to each prompt and which contains the full conversation. Let's take a look!"
"After chatting for a while, we have built a growing history, which we are attaching to each prompt and which contains the full conversation. Let's take a look!\n"
]
},
{
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+202 -206
View File
@@ -1,208 +1,204 @@
{
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Using Hugging Face With Plugins\n",
"\n",
"In this notebook, we demonstrate using Hugging Face models for Plugins using both SemanticMemory and text completions. \n",
"\n",
"SK supports downloading models from the Hugging Face that can perform the following tasks: text-generation, text2text-generation, summarization, and sentence-similarity. You can search for models by task at https://huggingface.co/models."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.9.5b1\n",
"\n",
"# Note that additional dependencies are required for the Hugging Face connectors:\n",
"!python -m pip install torch==2.0.0\n",
"!python -m pip install transformers==^4.28.1\n",
"!python -m pip install sentence-transformers==^2.2.2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": [
"import semantic_kernel as sk\n",
"import semantic_kernel.connectors.ai.hugging_face as sk_hf\n",
"from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "753ab756",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.HuggingFace"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will create a kernel and add both text completion and embedding services. \n",
"\n",
"For text completion, we are choosing GPT2. This is a text-generation model. (Note: text-generation will repeat the input in the output, text2text-generation will not.)\n",
"For embeddings, we are using sentence-transformers/all-MiniLM-L6-v2. Vectors generated for this model are of length 384 (compared to a length of 1536 from OpenAI ADA).\n",
"\n",
"The following step may take a few minutes when run for the first time as the models will be downloaded to your local machine."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure LLM service\n",
"if selectedService == Service.HuggingFace:\n",
" # Feel free to update this model to any other model available on Hugging Face\n",
" text_service_id = \"HuggingFaceM4/tiny-random-LlamaForCausalLM\"\n",
" kernel.add_service(\n",
" service=sk_hf.HuggingFaceTextCompletion(\n",
" service_id=text_service_id, ai_model_id=text_service_id, task=\"text-generation\"\n",
" ),\n",
" )\n",
" embed_service_id = \"sentence-transformers/all-MiniLM-L6-v2\"\n",
" embedding_svc = sk_hf.HuggingFaceTextEmbedding(service_id=embed_service_id, ai_model_id=embed_service_id)\n",
" kernel.add_service(\n",
" service=embedding_svc,\n",
" )\n",
" memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_svc)\n",
" kernel.import_plugin_from_object(sk.core_plugins.TextMemoryPlugin(memory), \"TextMemoryPlugin\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "2a7e7ca4",
"metadata": {},
"source": [
"### Add Memories and Define a plugin to use them\n",
"\n",
"Most models available on huggingface.co are not as powerful as OpenAI GPT-3+. Your plugins will likely need to be simpler to accommodate this."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d096504c",
"metadata": {},
"outputs": [],
"source": [
"collection_id = \"generic\"\n",
"\n",
"await memory.save_information(collection=collection_id, id=\"info1\", text=\"Sharks are fish.\")\n",
"await memory.save_information(collection=collection_id, id=\"info2\", text=\"Whales are mammals.\")\n",
"await memory.save_information(collection=collection_id, id=\"info3\", text=\"Penguins are birds.\")\n",
"await memory.save_information(collection=collection_id, id=\"info4\", text=\"Dolphins are mammals.\")\n",
"await memory.save_information(collection=collection_id, id=\"info5\", text=\"Flies are insects.\")\n",
"\n",
"# Define prompt function using SK prompt template language\n",
"my_prompt = \"\"\"I know these animal facts: \n",
"- {{recall 'fact about sharks'}}\n",
"- {{recall 'fact about whales'}} \n",
"- {{recall 'fact about penguins'}} \n",
"- {{recall 'fact about dolphins'}} \n",
"- {{recall 'fact about flies'}}\n",
"Now, tell me something about: {{$request}}\"\"\"\n",
"\n",
"execution_settings = sk_hf.HuggingFacePromptExecutionSettings(\n",
" service_id=text_service_id,\n",
" ai_model_id=text_service_id,\n",
" max_tokens=45,\n",
" temperature=0.5,\n",
" top_p=0.5,\n",
")\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=my_prompt,\n",
" name=\"text_complete\",\n",
" template_format=\"semantic-kernel\",\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"my_function = kernel.create_function_from_prompt(\n",
" function_name=\"text_complete\",\n",
" plugin_name=\"TextCompletionPlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "2calf857",
"metadata": {},
"source": [
"Let's now see what the completion looks like! Remember, \"gpt2\" is nowhere near as large as ChatGPT, so expect a much simpler answer."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"output = await kernel.invoke(\n",
" my_function,\n",
" request=\"What are whales?\",\n",
")\n",
"\n",
"output = str(output).strip()\n",
"\n",
"query_result1 = await memory.search(\n",
" collection=collection_id, query=\"What are sharks?\", limit=1, min_relevance_score=0.3\n",
")\n",
"\n",
"print(f\"The queried result for 'What are sharks?' is {query_result1[0].text}\")\n",
"\n",
"print(f\"{text_service_id} completed prompt with: '{output}'\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
"cells": [
{
"attachments": {},
"cell_type": "markdown",
"id": "68e1c158",
"metadata": {},
"source": [
"# Using Hugging Face With Plugins\n",
"\n",
"In this notebook, we demonstrate using Hugging Face models for Plugins using both SemanticMemory and text completions.\n",
"\n",
"SK supports downloading models from the Hugging Face that can perform the following tasks: text-generation, text2text-generation, summarization, and sentence-similarity. You can search for models by task at https://huggingface.co/models.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a77bdf89",
"metadata": {},
"outputs": [],
"source": [
"!python -m pip install semantic-kernel==0.9.5b1\n",
"\n",
"# Note that additional dependencies are required for the Hugging Face connectors:\n",
"!python -m pip install torch==2.0.0\n",
"!python -m pip install transformers==^4.28.1\n",
"!python -m pip install sentence-transformers==^2.2.2"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "508ad44f",
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": null,
"id": "753ab756",
"metadata": {},
"outputs": [],
"source": [
"from services import Service\n",
"\n",
"# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n",
"selectedService = Service.HuggingFace"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "d8ddffc1",
"metadata": {},
"source": [
"First, we will create a kernel and add both text completion and embedding services.\n",
"\n",
"For text completion, we are choosing GPT2. This is a text-generation model. (Note: text-generation will repeat the input in the output, text2text-generation will not.)\n",
"For embeddings, we are using sentence-transformers/all-MiniLM-L6-v2. Vectors generated for this model are of length 384 (compared to a length of 1536 from OpenAI ADA).\n",
"\n",
"The following step may take a few minutes when run for the first time as the models will be downloaded to your local machine.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8f8dcbc6",
"metadata": {},
"outputs": [],
"source": [
"kernel = sk.Kernel()\n",
"\n",
"# Configure LLM service\n",
"if selectedService == Service.HuggingFace:\n",
" # Feel free to update this model to any other model available on Hugging Face\n",
" text_service_id = \"HuggingFaceM4/tiny-random-LlamaForCausalLM\"\n",
" kernel.add_service(\n",
" service=sk_hf.HuggingFaceTextCompletion(\n",
" service_id=text_service_id, ai_model_id=text_service_id, task=\"text-generation\"\n",
" ),\n",
" )\n",
" embed_service_id = \"sentence-transformers/all-MiniLM-L6-v2\"\n",
" embedding_svc = sk_hf.HuggingFaceTextEmbedding(service_id=embed_service_id, ai_model_id=embed_service_id)\n",
" kernel.add_service(\n",
" service=embedding_svc,\n",
" )\n",
" memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_svc)\n",
" kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), \"TextMemoryPlugin\")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "2a7e7ca4",
"metadata": {},
"source": [
"### Add Memories and Define a plugin to use them\n",
"\n",
"Most models available on huggingface.co are not as powerful as OpenAI GPT-3+. Your plugins will likely need to be simpler to accommodate this.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d096504c",
"metadata": {},
"outputs": [],
"source": [
"collection_id = \"generic\"\n",
"\n",
"await memory.save_information(collection=collection_id, id=\"info1\", text=\"Sharks are fish.\")\n",
"await memory.save_information(collection=collection_id, id=\"info2\", text=\"Whales are mammals.\")\n",
"await memory.save_information(collection=collection_id, id=\"info3\", text=\"Penguins are birds.\")\n",
"await memory.save_information(collection=collection_id, id=\"info4\", text=\"Dolphins are mammals.\")\n",
"await memory.save_information(collection=collection_id, id=\"info5\", text=\"Flies are insects.\")\n",
"\n",
"# Define prompt function using SK prompt template language\n",
"my_prompt = \"\"\"I know these animal facts: \n",
"- {{recall 'fact about sharks'}}\n",
"- {{recall 'fact about whales'}} \n",
"- {{recall 'fact about penguins'}} \n",
"- {{recall 'fact about dolphins'}} \n",
"- {{recall 'fact about flies'}}\n",
"Now, tell me something about: {{$request}}\"\"\"\n",
"\n",
"execution_settings = sk_hf.HuggingFacePromptExecutionSettings(\n",
" service_id=text_service_id,\n",
" ai_model_id=text_service_id,\n",
" max_tokens=45,\n",
" temperature=0.5,\n",
" top_p=0.5,\n",
")\n",
"\n",
"prompt_template_config = sk.PromptTemplateConfig(\n",
" template=my_prompt,\n",
" name=\"text_complete\",\n",
" template_format=\"semantic-kernel\",\n",
" execution_settings=execution_settings,\n",
")\n",
"\n",
"my_function = kernel.add_function(\n",
" function_name=\"text_complete\",\n",
" plugin_name=\"TextCompletionPlugin\",\n",
" prompt_template_config=prompt_template_config,\n",
")"
]
},
{
"attachments": {},
"cell_type": "markdown",
"id": "2calf857",
"metadata": {},
"source": [
"Let's now see what the completion looks like! Remember, \"gpt2\" is nowhere near as large as ChatGPT, so expect a much simpler answer.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "628c843e",
"metadata": {},
"outputs": [],
"source": [
"output = await kernel.invoke(\n",
" my_function,\n",
" request=\"What are whales?\",\n",
")\n",
"\n",
"output = str(output).strip()\n",
"\n",
"query_result1 = await memory.search(\n",
" collection=collection_id, query=\"What are sharks?\", limit=1, min_relevance_score=0.3\n",
")\n",
"\n",
"print(f\"The queried result for 'What are sharks?' is {query_result1[0].text}\")\n",
"\n",
"print(f\"{text_service_id} completed prompt with: '{output}'\")"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -32,7 +32,7 @@ async def main():
)
# Import the ConversationSummaryPlugin
kernel.import_plugin_from_object(
kernel.add_plugin(
ConversationSummaryPlugin(kernel=kernel, prompt_template_config=prompt_template_config),
plugin_name="ConversationSummaryPlugin",
)
@@ -58,7 +58,7 @@ async def main():
)
# Create the function
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt=prompt,
plugin_name="Summarize_Conversation",
function_name="Chat",
@@ -31,7 +31,7 @@ async def main():
)
# Import the ConversationSummaryPlugin
kernel.import_plugin_from_object(
kernel.add_plugin(
ConversationSummaryPlugin(kernel=kernel, prompt_template_config=prompt_template_config),
plugin_name="ConversationSummaryPlugin",
)
@@ -56,7 +56,7 @@ async def main():
)
# Run the prompt
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt=prompt,
plugin_name="Summarize_Conversation",
function_name="Chat",
@@ -52,7 +52,7 @@ async def main():
# use_chat: True to use chat completion, False to use text completion
kernel = add_service(kernel=kernel, use_chat=True)
light_plugin = kernel.import_plugin_from_object(
light_plugin = kernel.add_plugin(
LightPlugin(),
plugin_name="LightPlugin",
)
@@ -14,7 +14,7 @@ class Math:
Description: MathPlugin provides a set of functions to make Math calculations.
Usage:
kernel.import_plugin_from_object(MathPlugin(), plugin_name="math")
kernel.add_plugin(MathPlugin(), plugin_name="math")
Examples:
{{math.Add}} => Returns the sum of input and amount (provided in the KernelArguments)
@@ -21,9 +21,7 @@ async def main():
prompt = f"What is the intent of this request? {request}"
print("0.0 Initial prompt")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_zero", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_zero", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -33,9 +31,7 @@ async def main():
You can choose between SendEmail, SendMessage, CompleteTask, CreateDocument."""
print("1.0 Make the prompt more specific")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_one", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_one", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -47,9 +43,7 @@ async def main():
Intent: """
print("2.0 Add structure to the output with formatting")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_two", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_two", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -80,9 +74,7 @@ async def main():
## Intent"""
print("2.1 Add structure to the output with formatting (using Markdown and JSON)")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_two_one", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_two_one", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -101,9 +93,7 @@ async def main():
Intent: """
print("3.0 Provide examples with few-shot prompting")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_three", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_three", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -123,9 +113,7 @@ async def main():
Intent: """
print("4.0 Tell the AI what to do to avoid doing something wrong")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_four", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_four", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request)
print(result)
print("-------------------------")
@@ -150,9 +138,7 @@ async def main():
Intent: """
print("5.0 Provide context to the AI")
print("-------------------------")
prompt_function = kernel.create_function_from_prompt(
function_name="sample_five", plugin_name="sample_plugin", prompt=prompt
)
prompt_function = kernel.add_function(function_name="sample_five", plugin_name="sample_plugin", prompt=prompt)
result = await kernel.invoke(prompt_function, request=request, history=history)
print(result)
print("-------------------------")
@@ -45,7 +45,7 @@ async def main():
)
# Import the ConversationSummaryPlugin
kernel.import_plugin_from_object(
kernel.add_plugin(
ConversationSummaryPlugin(kernel=kernel, prompt_template_config=prompt_template_config),
plugin_name="ConversationSummaryPlugin",
)
@@ -40,7 +40,7 @@ async def main():
)
# Run the prompt
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt=prompt,
plugin_name="Summarize_Conversation",
function_name="Chat",
@@ -18,7 +18,7 @@ async def main():
kernel = add_service(kernel=kernel, use_chat=True)
# Import the TimePlugin
time = kernel.import_plugin_from_object(TimePlugin(), "TimePlugin")
time = kernel.add_plugin(TimePlugin(), "TimePlugin")
# Import the WriterPlugin from the plugins directory.
script_directory = os.path.dirname(__file__)
@@ -18,9 +18,9 @@ async def main():
OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id)
)
kernel.import_plugin_from_object(MathPlugin(), "math")
kernel.import_plugin_from_object(TimePlugin(), "time")
kernel.import_plugin_from_object(TextPlugin(), "text")
kernel.add_plugin(MathPlugin(), "math")
kernel.add_plugin(TimePlugin(), "time")
kernel.add_plugin(TextPlugin(), "text")
# create an instance of action planner.
planner = ActionPlanner(kernel, service_id)
@@ -48,7 +48,7 @@ req_settings.auto_invoke_kernel_functions = True
## The third method is the most specific as the returned request settings class is the one that is registered for the service and has some fields already filled in, like the service_id and ai_model_id. # noqa: E501 E266
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt=system_message + """{{$chat_history}}{{$user_input}}""",
function_name="chat",
plugin_name="chat",
@@ -35,7 +35,7 @@ req_settings.top_p = 0.8
req_settings.auto_invoke_kernel_functions = False
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt="""{{system_message}}{{#each chat_history}}{{#message role=role}}{{~content~}}{{/message}} {{/each}}""",
function_name="chat",
plugin_name="chat",
@@ -35,7 +35,7 @@ req_settings.top_p = 0.8
req_settings.auto_invoke_kernel_functions = False
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt="""{{system_message}}{% for item in chat_history %}{{ message(item) }}{% endfor %}""",
function_name="chat",
plugin_name="chat",
@@ -69,7 +69,7 @@ prompt_template_config = PromptTemplateConfig(
],
execution_settings={"default": req_settings},
)
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -11,9 +11,7 @@ from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_
AzureChatPromptExecutionSettings,
ExtraBody,
)
from semantic_kernel.connectors.ai.open_ai.utils import (
get_tool_call_object,
)
from semantic_kernel.connectors.ai.open_ai.utils import get_tool_call_object
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.core_plugins.time_plugin import TimePlugin
from semantic_kernel.functions.kernel_arguments import KernelArguments
@@ -59,7 +57,7 @@ plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
# the joke plugin in the FunPlugins is a semantic plugin and has the function calling disabled.
kernel.import_plugin_from_prompt_directory(plugins_directory, "FunPlugin")
# the math plugin is a core plugin and has the function calling enabled.
kernel.import_plugin_from_object(TimePlugin(), plugin_name="time")
kernel.add_plugin(TimePlugin(), plugin_name="time")
# enabling or disabling function calling is done by setting the tool_choice parameter for the completion.
# when the tool_choice parameter is set to "auto" the model will decide which function to use, if any.
@@ -81,7 +79,7 @@ history = ChatHistory()
history.add_user_message("Hi there, who are you?")
history.add_assistant_message("I am an AI assistant here to answer your questions.")
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -5,9 +5,7 @@ import logging
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.open_ai.contents.azure_chat_message_content import (
AzureChatMessageContent,
)
from semantic_kernel.connectors.ai.open_ai.contents.azure_chat_message_content import AzureChatMessageContent
from semantic_kernel.connectors.ai.open_ai.contents.function_call import FunctionCall
from semantic_kernel.connectors.ai.open_ai.contents.tool_calls import ToolCall
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
@@ -82,7 +80,7 @@ chat_history = ChatHistory()
chat_history.add_user_message("Hi there, who are you?")
chat_history.add_assistant_message("I am an AI assistant here to answer your questions.")
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -82,7 +82,7 @@ async def main() -> None:
)
memory = SemanticTextMemory(storage=acs_connector, embeddings_generator=embedding_gen)
kernel.import_plugin_from_object(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
print("Populating memory...")
await populate_memory(kernel)
@@ -31,8 +31,8 @@ async def main():
cur_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources")
kernel.import_native_plugin_from_directory(cur_dir, "email_plugin")
kernel.import_plugin_from_object(MathPlugin(), "MathPlugin")
kernel.import_plugin_from_object(TimePlugin(), "TimePlugin")
kernel.add_plugin(MathPlugin(), "MathPlugin")
kernel.add_plugin(TimePlugin(), "TimePlugin")
questions = [
"What is the current hour number, plus 5?",
@@ -63,7 +63,7 @@ async def example2(kernel: sk.Kernel, service_id: str):
question = "Who is the most followed person on TikTok right now? What's the exchange rate EUR:USD?"
print(question)
oracle = kernel.create_function_from_prompt(
oracle = kernel.add_function(
function_name="oracle",
plugin_name="OraclePlugin",
template=prompt,
@@ -111,7 +111,7 @@ async def main():
bing_connector = BingConnector(api_key=bing_api_key)
bing = WebSearchEnginePlugin(bing_connector)
kernel.import_plugin_from_object(bing, "bing")
kernel.add_plugin(bing, "bing")
await example1(kernel, "bing")
await example2(kernel, service_id)
@@ -26,7 +26,7 @@ async def main():
),
)
connector = BingConnector(api_key=os.getenv("BING_API_KEY"))
web_plugin = kernel.import_plugin_from_object(WebSearchEnginePlugin(connector), "WebSearch")
web_plugin = kernel.add_plugin(WebSearchEnginePlugin(connector), "WebSearch")
print("---------------- Question 1 -----------------\n")
@@ -46,7 +46,7 @@ async def main():
Answer:
"""
req_settings = kernel.get_service("chat-gpt").get_prompt_execution_settings_class()(service_id=service_id)
req_settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
req_settings.temperature = 0.2
prompt_template_config = sk.PromptTemplateConfig(
@@ -57,7 +57,7 @@ async def main():
)
question = "What is Semantic Kernel?"
qna = kernel.create_function_from_prompt(
qna = kernel.add_function(
function_name="qna",
plugin_name="WebSearch",
prompt_template_config=prompt_template_config,
@@ -56,7 +56,7 @@ chat_history = ChatHistory()
chat_history.add_user_message("Hi there, who are you?")
chat_history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure out what people need")
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -30,7 +30,7 @@ settings.max_tokens = 2000
settings.temperature = 0.7
settings.top_p = 0.8
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot",
function_name="Chat",
prompt="{{$chat_history}}{{$user_input}}",
@@ -3,7 +3,7 @@
import asyncio
import os
from functools import reduce
from typing import TYPE_CHECKING, Any, Dict, List, Union
from typing import TYPE_CHECKING, Any, Dict, List
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
@@ -12,7 +12,7 @@ from semantic_kernel.connectors.ai.open_ai.contents.open_ai_streaming_chat_messa
OpenAIStreamingChatMessageContent,
)
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIPromptExecutionSettings,
OpenAIChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.utils import get_tool_call_object
from semantic_kernel.contents.chat_history import ChatHistory
@@ -52,10 +52,10 @@ plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
# the joke plugin in the FunPlugins is a semantic plugin and has the function calling disabled.
# kernel.import_plugin_from_prompt_directory("chat", plugins_directory, "FunPlugin")
# the math plugin is a core plugin and has the function calling enabled.
kernel.import_plugin_from_object(MathPlugin(), plugin_name="math")
kernel.import_plugin_from_object(TimePlugin(), plugin_name="time")
kernel.add_plugin(MathPlugin(), plugin_name="math")
kernel.add_plugin(TimePlugin(), plugin_name="time")
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
prompt="{{$chat_history}}{{$user_input}}",
plugin_name="ChatBot",
function_name="Chat",
@@ -89,10 +89,10 @@ history.add_assistant_message("I am Mosscap, a chat bot. I'm trying to figure ou
arguments = KernelArguments(settings=execution_settings)
def print_tool_calls(message: Union[OpenAIChatMessageContent, OpenAIStreamingChatMessageContent]) -> None:
def print_tool_calls(message: OpenAIChatMessageContent) -> None:
# A helper method to pretty print the tool calls from the message.
# This is only triggered if auto invoke tool calls is disabled.
if isinstance(message, (OpenAIChatMessageContent, OpenAIStreamingChatMessageContent)):
if isinstance(message, OpenAIChatMessageContent):
tool_calls = message.tool_calls
formatted_tool_calls = []
for i, tool_call in enumerate(tool_calls, start=1):
@@ -113,7 +113,7 @@ async def handle_streaming(
chat_function: "KernelFunction",
user_input: str,
history: ChatHistory,
execution_settings: OpenAIPromptExecutionSettings,
execution_settings: OpenAIChatPromptExecutionSettings,
) -> None:
response = kernel.invoke_stream(
chat_function,
@@ -46,7 +46,7 @@ async def main():
),
)
chat = kernel.create_function_from_prompt(
chat = kernel.add_function(
function_name="chat",
plugin_name="ChatBot",
prompt_template_config=prompt_template_config,
@@ -51,7 +51,7 @@ async def setup_chat_with_memory(
},
)
chat_func = kernel.create_function_from_prompt(
chat_func = kernel.add_function(
function_name="chat_with_memory",
plugin_name="TextMemoryPlugin",
prompt_template_config=prompt_template_config,
@@ -91,7 +91,7 @@ async def main() -> None:
kernel.add_service(palm_chat_completion)
memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=palm_text_embed)
kernel.import_plugin_from_object(TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin")
print("Populating memory...")
await populate_memory(memory)
@@ -50,7 +50,7 @@ prompt_template_config = sk.PromptTemplateConfig(
execution_settings=req_settings,
)
chat_func = kernel.create_function_from_prompt(
chat_func = kernel.add_function(
plugin_name="PiratePlugin", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -6,8 +6,10 @@ from dotenv import load_dotenv
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.search_engine import GoogleConnector
from semantic_kernel.core_plugins import WebSearchEnginePlugin
from semantic_kernel.functions.kernel_arguments import KernelArguments
load_dotenv()
@@ -36,15 +38,15 @@ async def main():
)
# Import the WebSearchEnginePlugin and pass the Google Connector to it.
web_plugin = kernel.import_plugin_from_object(WebSearchEnginePlugin(connector), "WebSearch")
web_plugin = kernel.add_plugin(WebSearchEnginePlugin(connector), "WebSearch")
# The search query
prompt = "Who is Leonardo DiCaprio's current girlfriend?"
search = web_plugin["searchAsync"]
prompt = "Who is Leonardo DiCaprio's current girlfriend?"
# By default, only one search result is provided
result = await search.invoke(prompt)
print(result)
result = await search.invoke(kernel, query=prompt)
print(str(result))
"""
Output:
@@ -61,19 +63,22 @@ async def main():
Answer:
"""
qna = kernel.create_semantic_function(prompt, temperature=0.2)
context = kernel.create_new_context()
qna = kernel.add_function(
plugin_name="qa",
function_name="qna",
prompt=prompt,
prompt_execution_settings=PromptExecutionSettings(temperature=0.2),
)
"""
Two context parameters can be passed to the search engine plugin.
- num_results controls the number of results returned by the web search.
- offset controls the number of results to omit.
"""
context["num_results"] = "10"
context["offset"] = "0"
arguments = KernelArguments(num_results="10", offset="0")
result = await qna.invoke(context=context)
print(result)
result = await qna.invoke(kernel, arguments)
print(str(result))
"""
Output:
@@ -3,10 +3,9 @@ import logging
import semantic_kernel as sk
from samples.utils import Colors
from semantic_kernel.connectors.ai.open_ai import (
AzureChatCompletion,
OpenAIChatCompletion,
)
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
def get_grounding_text():
@@ -48,17 +47,17 @@ interment of his friend he conducted her to Geneva and placed her under the prot
after this event Caroline became his wife."""
def setup(use_azure: bool = False):
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
kernel = sk.Kernel(log=logger)
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
useAzureOpenAI = use_azure
def setup(use_azure: bool = False, plugin_name: str = "GroundingPlugin"):
kernel = Kernel()
# Configure AI service used by the kernel
if useAzureOpenAI:
if use_azure:
deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env()
service_id = ("chat_completion",)
service_id = "chat_completion"
kernel.add_service(
AzureChatCompletion(
service_id=service_id,
@@ -66,7 +65,6 @@ def setup(use_azure: bool = False):
endpoint=endpoint,
api_key=api_key,
api_version="2023-12-01-preview",
log=logger,
),
)
else:
@@ -79,11 +77,9 @@ def setup(use_azure: bool = False):
# note: using plugins from the samples folder
plugins_directory = "../samples/plugins/"
grounding_semantic_functions = kernel.import_plugin_from_prompt_directory(
service_id, plugins_directory, "GroundingPlugin"
)
kernel.add_plugin(parent_directory=plugins_directory, plugin_name=plugin_name)
return kernel, grounding_semantic_functions
return kernel
def get_summary_text():
@@ -93,37 +89,47 @@ def get_summary_text():
return summary_text
async def run_entity_extraction(kernel, semantic_functions, summary_text):
context = kernel.create_new_context()
context["topic"] = "people and places"
context["example_entities"] = "John, Jane, mother, brother, Paris, Rome"
async def run_entity_extraction(kernel: Kernel, plugin_name: str, summary_text: str):
arguments = KernelArguments(
topic="people and places", example_entities="John, Jane, mother, brother, Paris, Rome", input=summary_text
)
extraction_result = semantic_functions["ExtractEntities"](summary_text, context=context)
extraction_result = await kernel.invoke(
plugin_name=plugin_name, function_name="ExtractEntities", arguments=arguments
)
return extraction_result, context
return extraction_result
async def run_reference_check(semantic_functions, extraction_result, context):
context["reference_context"] = get_grounding_text()
grounding_result = semantic_functions["ReferenceCheckEntities"](extraction_result.result, context=context)
context["ungrounded_entities"] = grounding_result.result
return grounding_result, context
async def run_reference_check(kernel: Kernel, plugin_name: str, extraction_result):
grounding_result = await kernel.invoke(
plugin_name=plugin_name,
function_name="ReferenceCheckEntities",
input=str(extraction_result),
reference_context=get_grounding_text(),
)
return grounding_result
async def run_entity_excision(semantic_functions, summary_text, context):
excision_result = semantic_functions["ExciseEntities"](summary_text, context=context)
return excision_result, context
async def run_entity_excision(kernel: Kernel, plugin_name: str, summary_text, grounding_result):
excision_result = await kernel.invoke(
plugin_name=plugin_name,
function_name="ExciseEntities",
input=summary_text,
ungrounded_entities=grounding_result,
)
return excision_result
async def run_grounding(use_azure: bool = False):
kernel, semantic_functions = setup(use_azure)
print(f"\n{Colors.CBOLD}Groundingsness Checking Plugins\n{Colors.CEND}")
plugin_name = "GroundingPlugin"
kernel = setup(use_azure, plugin_name=plugin_name)
print(f"\n{Colors.CBOLD.value}Groundingsness Checking Plugins\n{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
print(
f"""{Colors.CGREEN}A well-known problem with large language models (LLMs) is that they make things up. These are sometimes called 'hallucinations' but a safer (and less anthropomorphic) term is 'ungrounded addition' - something in the text which cannot be firmly established. When attempting to establish whether or not something in an LLM response is 'true' we can either check for it in the supplied prompt (this is called 'narrow grounding') or use our general knowledge ('broad grounding'). Note that narrow grounding can lead to things being classified as 'true, but ungrounded.' For example "I live in Switzerland" is **not** _narrowly_ grounded in "I live in Geneva" even though it must be true (it **is** _broadly_ grounded). # noqa: E501
f"""{Colors.CGREEN.value}A well-known problem with large language models (LLMs) is that they make things up. These are sometimes called 'hallucinations' but a safer (and less anthropomorphic) term is 'ungrounded addition' - something in the text which cannot be firmly established. When attempting to establish whether or not something in an LLM response is 'true' we can either check for it in the supplied prompt (this is called 'narrow grounding') or use our general knowledge ('broad grounding'). Note that narrow grounding can lead to things being classified as 'true, but ungrounded.' For example "I live in Switzerland" is **not** _narrowly_ grounded in "I live in Geneva" even though it must be true (it **is** _broadly_ grounded).
In this sample we run a simple grounding pipeline, to see if a summary text has any ungrounded additions as compared to the original, and use this information to improve the summary text. This can be done in three stages: # noqa: E501
In this sample we run a simple grounding pipeline, to see if a summary text has any ungrounded additions as compared to the original, and use this information to improve the summary text. This can be done in three stages:
1. Make a list of the entities in the summary text
1. Check to see if these entities appear in the original (grounding) text
@@ -132,14 +138,14 @@ In this sample we run a simple grounding pipeline, to see if a summary text has
What is an 'entity' in this context? In its simplest form, it's a named object such as a person or place (so 'Dean' or 'Seattle'). However, the idea could be a _claim_ which relates concepts (such as 'Dean lives near Seattle'). In this sample, we will keep to the simpler case of named objects.""" # noqa: E501
)
print(f"\nThe grounding text: \n{Colors.CGREY}{get_grounding_text()}{Colors.CEND}")
print(f"\nThe grounding text: \n{Colors.CGREY.value}{get_grounding_text()}{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
summary_text = get_summary_text()
print(f"Summary text: \n{Colors.CBLUE}{summary_text}{Colors.CEND}")
print(f"Summary text: \n{Colors.CBLUE.value}{summary_text}{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
print(
f"""{Colors.CGREEN}Some things to note:
f"""{Colors.CGREEN.value}Some things to note:
- The implied residence of Geneva has been changed to Milan
- Lucerne has been changed to Zurich
@@ -153,28 +159,28 @@ The grounding plugin has three stages:
2. Perform a reference check against the grounding text
3. Excise any entities which failed the reference check from the summary
Now, let us start calling individual semantic functions.{Colors.CEND}"""
Now, let us start calling individual semantic functions.{Colors.CEND.value}"""
)
print(f"\n{ '-'*80 }\n")
print(
f"{Colors.CGREEN}First we run the extraction function on the summary, this results in all the extracted entities.{Colors.CEND}" # noqa: E501
f"{Colors.CGREEN.value}First we run the extraction function on the summary, this results in all the extracted entities.{Colors.CEND.value}" # noqa: E501
)
extraction_result, context = await run_entity_extraction(kernel, semantic_functions, summary_text)
print(f"Extraction result: \n{Colors.CBLUE}{extraction_result.result}{Colors.CEND}")
extraction_result = await run_entity_extraction(kernel, plugin_name, summary_text)
print(f"Extraction result: \n{Colors.CBLUE.value}{str(extraction_result)}{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
print(
f"{Colors.CGREEN}Next we run the reference check function on the summary, this loads the grounding text as part of it in order to know the 'truth'. This returns a list of ungrounded entities.{Colors.CEND}" # noqa: E501
f"{Colors.CGREEN.value}Next we run the reference check function on the summary, this loads the grounding text as part of it in order to know the 'truth'. This returns a list of ungrounded entities.{Colors.CEND.value}" # noqa: E501
)
grounding_result, context = await run_reference_check(semantic_functions, extraction_result, context)
print(f"Grounding result: \n{Colors.CBLUE}{grounding_result.result}{Colors.CEND}")
grounding_result = await run_reference_check(kernel, plugin_name, extraction_result)
print(f"Grounding result: \n{Colors.CBLUE.value}{str(grounding_result)}{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
print(
f"{Colors.CGREEN}Finally we run the excision function on the summary, this removes the ungrounded entities from the summary.{Colors.CEND}" # noqa: E501
f"{Colors.CGREEN.value}Finally we run the excision function on the summary, this removes the ungrounded entities from the summary.{Colors.CEND.value}" # noqa: E501
)
excision_result, context = await run_entity_excision(semantic_functions, summary_text, context)
print(f"The final summary text: \n{Colors.CBLUE}{excision_result.result}{Colors.CEND}")
excision_result = await run_entity_excision(kernel, plugin_name, summary_text, grounding_result)
print(f"The final summary text: \n{Colors.CBLUE.value}{str(excision_result)}{Colors.CEND.value}")
print(f"\n{ '-'*80 }\n")
print(f"{Colors.CBOLD}Finished!{Colors.CEND}")
print(f"{Colors.CBOLD.value}Finished!{Colors.CEND.value}")
if __name__ == "__main__":
@@ -6,9 +6,7 @@ import os
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.kernel import Kernel
from semantic_kernel.utils.settings import (
openai_settings_from_dot_env,
)
from semantic_kernel.utils.settings import openai_settings_from_dot_env
async def main():
@@ -27,7 +25,7 @@ async def main():
chat_history = ChatHistory(system_message="Assistant is a large language model")
cur_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources")
plugin = kernel.import_plugin_from_prompt_directory(cur_dir, "sample_plugins")
plugin = kernel.add_plugin(plugin_name="sample_plugins", parent_directory=cur_dir)
result = await kernel.invoke(plugin["Parrot"], count=2, user_message="I love parrots.", chat_history=chat_history)
print(result)
@@ -46,12 +46,10 @@ async def setup_chat_with_memory(
prompt_template_config = PromptTemplateConfig(
template=prompt,
execution_settings={
service_id: kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)
},
execution_settings={service_id: kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)},
)
chat_func = kernel.create_function_from_prompt(
chat_func = kernel.add_function(
function_name="chat_with_memory",
plugin_name="TextMemoryPlugin",
prompt_template_config=prompt_template_config,
@@ -94,7 +92,7 @@ async def main() -> None:
kernel.add_service(embedding_gen)
memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_gen)
kernel.import_plugin_from_object(TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin")
print("Populating memory...")
await populate_memory(memory)
@@ -29,10 +29,8 @@ async def main():
)
cur_dir = os.path.join(os.path.dirname(os.path.realpath(__file__)), "resources")
kernel.import_native_plugin_from_directory(cur_dir, "email_plugin")
kernel.import_plugin_from_object(MathPlugin(), "MathPlugin")
kernel.import_plugin_from_object(TimePlugin(), "TimePlugin")
kernel.add_plugin(parent_directory=cur_dir, plugin_name="email_plugin")
kernel.add_plugins({"MathPlugin": MathPlugin(), "TimePlugin": TimePlugin()})
questions = [
"What is the current hour number, plus 5?",
@@ -52,7 +50,7 @@ async def main():
print(f"Q: {question}\nA: {result.final_answer}\n")
# Uncomment the following line to view the planner's process for completing the request
# print(f"Chat history: {result.chat_history}\n")
# print(f"\nChat history: {result.chat_history}\n")
if __name__ == "__main__":
@@ -8,6 +8,7 @@ import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.kernel import Kernel
from semantic_kernel.prompt_template.input_variable import InputVariable
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
@@ -31,7 +32,7 @@ def _prepare_input_chat(chat: ChatHistory):
return "".join([f"{msg.role}: {msg.content}\n" for msg in chat])
async def chat_request_example(kernel, api_key, org_id):
async def chat_request_example(kernel: Kernel, api_key, org_id):
service_id = "chat_service"
openai_chat_completion = sk_oai.OpenAIChatCompletion(
service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id
@@ -70,7 +71,7 @@ async def chat_request_example(kernel, api_key, org_id):
]
# Model will try its best to avoid using any of the above words
settings = kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)
settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
settings = _config_ban_tokens(settings, keys)
prompt_template_config = PromptTemplateConfig(
@@ -90,7 +91,7 @@ async def chat_request_example(kernel, api_key, org_id):
chat.add_user_message("Hi there, who are you?")
chat.add_assistant_message("I am an AI assistant here to answer your questions.")
chat_function = kernel.create_function_from_prompt(
chat_function = kernel.add_function(
plugin_name="ChatBot", function_name="Chat", prompt_template_config=prompt_template_config
)
@@ -111,7 +112,7 @@ async def chat_request_example(kernel, api_key, org_id):
return chat, banned_words
async def text_complete_request_example(kernel, api_key, org_id):
async def text_complete_request_example(kernel: Kernel, api_key, org_id):
service_id = "text_service"
openai_text_completion = sk_oai.OpenAITextCompletion(
service_id=service_id, ai_model_id="gpt-3.5-turbo-instruct", api_key=api_key, org_id=org_id
@@ -159,7 +160,7 @@ async def text_complete_request_example(kernel, api_key, org_id):
]
# Model will try its best to avoid using any of the above words
settings = kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)
settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)
settings = _config_ban_tokens(settings, keys)
prompt_template_config = PromptTemplateConfig(
@@ -178,7 +179,7 @@ async def text_complete_request_example(kernel, api_key, org_id):
chat.add_user_message("The best pie flavor to have in autumn is")
text_function = kernel.create_function_from_prompt(
text_function = kernel.add_function(
plugin_name="TextBot", function_name="TextCompletion", prompt_template_config=prompt_template_config
)
@@ -209,7 +210,7 @@ def _format_output(chat, banned_words) -> None:
async def main() -> None:
kernel = sk.Kernel()
kernel = Kernel()
api_key, org_id = sk.openai_settings_from_dot_env()
print("Chat completion example:")
@@ -146,7 +146,7 @@ async def main():
http_client = httpx.AsyncClient()
plugin = await kernel.import_plugin_from_openai(
plugin = await kernel.add_plugin_from_openai(
plugin_name="AzureKeyVaultPlugin",
plugin_str=openai_spec,
execution_parameters=OpenAIFunctionExecutionParameters(
@@ -6,12 +6,11 @@ from semantic_kernel.kernel import Kernel
async def main():
# This is an example of how to import a plugin from OpenAI and invoke a function from the plugin
# It does not require authentication
kernel = Kernel()
plugin = await kernel.import_plugin_from_openai(
plugin = await kernel.add_plugin_from_openai(
plugin_name="Klarna",
plugin_url="https://www.klarna.com/.well-known/ai-plugin.json",
)
@@ -31,7 +31,7 @@ async def main():
# note: using plugins from the samples folder
plugins_directory = os.path.join(__file__, "../../../../samples/plugins")
plugin = kernel.import_plugin_from_prompt_directory(service_id, plugins_directory, "FunPlugin")
plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="FunPlugin")
arguments = KernelArguments(input="time travel to dinosaur age", style="super silly")
@@ -24,7 +24,7 @@ async def main():
kernel.add_service(embedding_gen)
memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=embedding_gen)
kernel.import_plugin_from_object(TextMemoryPlugin(memory), "memory")
kernel.add_plugin(TextMemoryPlugin(memory), "memory")
await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000")
@@ -15,7 +15,7 @@ class EmailPlugin:
Description: EmailPlugin provides a set of functions to send emails.
Usage:
kernel.import_plugin_from_object(EmailPlugin(), plugin_name="email")
kernel.add_plugin(EmailPlugin(), plugin_name="email")
Examples:
{{email.SendEmail}} => Sends an email with the provided subject and body.
@@ -5,13 +5,8 @@ import asyncio
from dotenv import dotenv_values
import semantic_kernel as sk
from semantic_kernel.connectors.ai.open_ai import (
AzureChatCompletion,
AzureTextEmbedding,
)
from semantic_kernel.connectors.memory.azure_cognitive_search import (
AzureCognitiveSearchMemoryStore,
)
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureTextEmbedding
from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin
from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory
@@ -69,7 +64,7 @@ async def main() -> None:
)
memory = SemanticTextMemory(storage=acs_connector, embeddings_generator=embedding_gen)
kernel.import_plugin_from_object(TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin")
print("Populating memory...")
await populate_memory(memory)
@@ -96,11 +91,11 @@ Remember, just answer Grounded or Ungrounded or Unclear: """.strip()
user_input = "Do I live in Seattle?"
print(f"Question: {user_input}")
req_settings = kernel.get_service("dv").get_prompt_execution_settings_class()(service_id="dv")
chat_func = kernel.create_function_from_prompt(
req_settings = kernel.get_prompt_execution_settings_from_service_id(service_id="dv")
chat_func = kernel.add_function(
function_name="rag", plugin_name="RagPlugin", prompt=sk_prompt_rag, prompt_execution_settings=req_settings
)
self_critique_func = kernel.create_function_from_prompt(
self_critique_func = kernel.add_function(
function_name="self_critique_rag",
plugin_name="RagPlugin",
prompt=sk_prompt_rag_sc,
@@ -18,9 +18,9 @@ async def main():
kernel.add_service(
OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id)
)
kernel.import_plugin_from_object(MathPlugin(), "math")
kernel.import_plugin_from_object(TimePlugin(), "time")
kernel.import_plugin_from_object(TextPlugin(), "text")
kernel.add_plugin(MathPlugin(), "math")
kernel.add_plugin(TimePlugin(), "time")
kernel.add_plugin(TextPlugin(), "text")
# create an instance of sequential planner.
planner = SequentialPlanner(service_id=service_id, kernel=kernel)
@@ -4,9 +4,7 @@ import asyncio
import semantic_kernel as sk
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.core_plugins import (
TimePlugin,
)
from semantic_kernel.core_plugins import TimePlugin
from semantic_kernel.prompt_template.kernel_prompt_template import KernelPromptTemplate
from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig
@@ -23,7 +21,7 @@ async def main():
sk_oai.OpenAIChatCompletion(service_id=service_id, ai_model_id=model, api_key=api_key, org_id=org_id),
)
kernel.import_plugin_from_object(TimePlugin(), "time")
kernel.add_plugin(TimePlugin(), "time")
function_definition = """
Today is: {{time.Date}}
@@ -40,7 +38,7 @@ async def main():
rendered_prompt = await prompt_template.render(kernel, arguments=None)
print(rendered_prompt)
kind_of_day = kernel.create_function_from_prompt(
kind_of_day = kernel.add_function(
plugin_name="TimePlugin",
template=function_definition,
execution_settings=sk_oai.OpenAIChatPromptExecutionSettings(service_id=service_id, max_tokens=100),
@@ -151,13 +151,10 @@ def get_function_calling_object(
if exclude_function:
exclude_function = [function for function in exclude_function]
result = []
for (
plugin_name,
plugin,
) in kernel.plugins.plugins.items():
for plugin_name, plugin in kernel.plugins.items():
if plugin_name in exclude_plugin or (include_plugin and plugin_name not in include_plugin):
continue
for function in plugin.functions.values():
for function in plugin:
if function.fully_qualified_name in exclude_function or (
include_function and function.fully_qualified_name not in include_function
):
@@ -3,6 +3,7 @@
from __future__ import annotations
import logging
from typing import Any
from semantic_kernel.exceptions.function_exceptions import PluginInitializationError
@@ -13,7 +14,7 @@ class OpenAIUtils:
"""Utility functions for OpenAI plugins."""
@staticmethod
def parse_openai_manifest_for_openapi_spec_url(plugin_json):
def parse_openai_manifest_for_openapi_spec_url(plugin_json: dict[str, Any]) -> str:
"""Extract the OpenAPI Spec URL from the plugin JSON."""
try:
@@ -3,6 +3,5 @@
from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import (
OpenAPIFunctionExecutionParameters,
)
from semantic_kernel.connectors.openapi_plugin.openapi_manager import OpenAPIPlugin
__all__ = ["OpenAPIPlugin", "OpenAPIFunctionExecutionParameters"]
__all__ = ["OpenAPIFunctionExecutionParameters"]
@@ -7,10 +7,13 @@ import logging
import sys
from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated
from urllib.parse import urljoin, urlparse, urlunparse
import aiohttp
@@ -20,12 +23,9 @@ from openapi_core.contrib.requests import RequestsOpenAPIRequest
from openapi_core.exceptions import OpenAPIError
from prance import ResolvingParser
from semantic_kernel.connectors.ai.open_ai.const import (
USER_AGENT,
)
from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.exceptions import ServiceInvalidRequestError
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_plugin import KernelPlugin
if TYPE_CHECKING:
from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import (
@@ -211,7 +211,7 @@ class OpenApiParser:
def create_rest_api_operations(
self,
parsed_document: Any,
execution_settings: "OpenAIFunctionExecutionParameters" | "OpenAPIFunctionExecutionParameters" | None = None,
execution_settings: "OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None" = None,
) -> Dict[str, RestApiOperation]:
"""Create the REST API Operations from the parsed OpenAPI document.
@@ -303,69 +303,61 @@ class OpenApiRunner:
return await response.text()
class OpenAPIPlugin:
@staticmethod
def create(
plugin_name: str,
openapi_document_path: str,
execution_settings: "OpenAIFunctionExecutionParameters" | "OpenAPIFunctionExecutionParameters" | None = None,
) -> KernelPlugin:
"""Creates an OpenAPI plugin
def create_functions_from_openapi(
plugin_name: str,
openapi_document_path: str,
execution_settings: "OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None" = None,
) -> list[KernelFunctionFromMethod]:
"""Creates the functions from OpenAPI document.
Args:
plugin_name: The name of the plugin
openapi_document_path: The OpenAPI document path, it must be a file path to the spec.
execution_settings: The execution settings
Args:
plugin_name: The name of the plugin
openapi_document_path: The OpenAPI document path, it must be a file path to the spec.
execution_settings: The execution settings
Returns:
The KernelPlugin
"""
parser = OpenApiParser()
parsed_doc = parser.parse(openapi_document_path)
operations = parser.create_rest_api_operations(parsed_doc, execution_settings=execution_settings)
Returns:
list[KernelFunctionFromMethod]: the operations as functions
"""
parser = OpenApiParser()
parsed_doc = parser.parse(openapi_document_path)
operations = parser.create_rest_api_operations(parsed_doc, execution_settings=execution_settings)
auth_callback = None
if execution_settings and execution_settings.auth_callback:
auth_callback = execution_settings.auth_callback
openapi_runner = OpenApiRunner(parsed_openapi_document=parsed_doc, auth_callback=auth_callback)
auth_callback = None
if execution_settings and execution_settings.auth_callback:
auth_callback = execution_settings.auth_callback
openapi_runner = OpenApiRunner(parsed_openapi_document=parsed_doc, auth_callback=auth_callback)
plugin = {}
return [
_create_function_from_operation(openapi_runner, operation, plugin_name) for operation in operations.values()
]
def create_run_operation_function(runner: OpenApiRunner, operation: RestApiOperation):
@kernel_function(
description=operation.summary if operation.summary else operation.description,
name=operation.id,
)
async def run_openapi_operation(
path_params: Annotated[dict | str | None, "A dictionary of path parameters"] = None,
query_params: Annotated[dict | str | None, "A dictionary of query parameters"] = None,
headers: Annotated[dict | str | None, "A dictionary of headers"] = None,
request_body: Annotated[dict | str | None, "A dictionary of the request body"] = None,
) -> str:
response = await runner.run_operation(
operation,
path_params=(
json.loads(path_params)
if isinstance(path_params, str)
else path_params if path_params else None
),
query_params=(
json.loads(query_params)
if isinstance(query_params, str)
else query_params if query_params else None
),
headers=json.loads(headers) if isinstance(headers, str) else headers if headers else None,
request_body=(
json.loads(request_body)
if isinstance(request_body, str)
else request_body if request_body else None
),
)
return response
return run_openapi_operation
def _create_function_from_operation(runner: OpenApiRunner, operation: RestApiOperation, plugin_name: str | None = None):
logger.info(f"Registering OpenAPI operation: {plugin_name}.{operation.id}")
for operation_id, operation in operations.items():
logger.info(f"Registering OpenAPI operation: {plugin_name}.{operation_id}")
plugin[operation_id] = create_run_operation_function(openapi_runner, operation)
return plugin
@kernel_function(
description=operation.summary if operation.summary else operation.description,
name=operation.id,
)
async def run_openapi_operation(
path_params: Annotated[dict | str | None, "A dictionary of path parameters"] = None,
query_params: Annotated[dict | str | None, "A dictionary of query parameters"] = None,
headers: Annotated[dict | str | None, "A dictionary of headers"] = None,
request_body: Annotated[dict | str | None, "A dictionary of the request body"] = None,
) -> str:
response = await runner.run_operation(
operation,
path_params=(
json.loads(path_params) if isinstance(path_params, str) else path_params if path_params else None
),
query_params=(
json.loads(query_params) if isinstance(query_params, str) else query_params if query_params else None
),
headers=json.loads(headers) if isinstance(headers, str) else headers if headers else None,
request_body=(
json.loads(request_body) if isinstance(request_body, str) else request_body if request_body else None
),
)
return response
return KernelFunctionFromMethod(run_openapi_operation, plugin_name=plugin_name)
@@ -45,7 +45,7 @@ class ConversationSummaryPlugin:
:param return_key: The key to use for the return value.
"""
self.return_key = return_key
self._summarizeConversationFunction = kernel.create_function_from_prompt(
self._summarizeConversationFunction = kernel.add_function(
prompt=ConversationSummaryPlugin._summarize_conversation_prompt_template,
plugin_name=ConversationSummaryPlugin.__name__,
function_name="SummarizeConversation",
@@ -73,9 +73,7 @@ class ConversationSummaryPlugin:
:return: KernelArguments with the summarized conversation result in key self.return_key.
"""
from semantic_kernel.text import text_chunker
from semantic_kernel.text.function_extension import (
aggregate_chunked_results,
)
from semantic_kernel.text.function_extension import aggregate_chunked_results
lines = text_chunker._split_text_lines(input, ConversationSummaryPlugin._max_tokens, True)
paragraphs = text_chunker._split_text_paragraph(lines, ConversationSummaryPlugin._max_tokens)
@@ -7,7 +7,6 @@ from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
from semantic_kernel.functions.kernel_parameter_metadata import KernelParameterMetadata
from semantic_kernel.functions.kernel_plugin import KernelPlugin
from semantic_kernel.functions.kernel_plugin_collection import KernelPluginCollection
__all__ = [
"FunctionResult",
@@ -17,5 +16,4 @@ __all__ = [
"KernelFunctionMetadata",
"KernelParameterMetadata",
"KernelPlugin",
"KernelPluginCollection",
]
@@ -1,6 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
@@ -9,7 +10,7 @@ if TYPE_CHECKING:
class KernelArguments(dict):
def __init__(
self,
settings: Optional[Union["PromptExecutionSettings", List["PromptExecutionSettings"]]] = None,
settings: "PromptExecutionSettings" | list["PromptExecutionSettings"] | None = None,
**kwargs: Any,
):
"""Initializes a new instance of the KernelArguments class,
@@ -19,11 +20,11 @@ class KernelArguments(dict):
just adds the execution_settings as a dict, with service_id and the settings.
Arguments:
settings {Optional[Union[PromptExecutionSettings, List[PromptExecutionSettings]]]} --
settings (PromptExecutionSettings | List[PromptExecutionSettings] | None) --
The settings for the execution.
If a list is given, make sure all items in the list have a unique service_id
as that is used as the key for the dict.
**kwargs {Dict[str, Any]} -- The arguments for the function invocation, works similar to a regular dict.
**kwargs (dict[str, Any]) -- The arguments for the function invocation, works similar to a regular dict.
"""
super().__init__(**kwargs)
settings_dict = {}
@@ -32,4 +33,4 @@ class KernelArguments(dict):
settings_dict = {s.service_id: s for s in settings}
else:
settings_dict = {settings.service_id: settings}
self.execution_settings: Optional[Dict[str, "PromptExecutionSettings"]] = settings_dict
self.execution_settings: dict[str, "PromptExecutionSettings"] | None = settings_dict
@@ -1,7 +1,9 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
from abc import abstractmethod
from copy import copy, deepcopy
from typing import TYPE_CHECKING, Any, AsyncIterable, Callable, Dict, List, Optional, Union
from semantic_kernel.functions.function_result import FunctionResult
@@ -227,3 +229,18 @@ class KernelFunction(KernelBaseModel):
except Exception as e:
logger.error(f"Error occurred while invoking function {self.name}: {e}")
yield FunctionResult(function=self.metadata, value=None, metadata={"exception": e, "arguments": arguments})
def function_copy(self, plugin_name: str | None = None) -> "KernelFunction":
"""Copy the function, can also override the plugin_name.
Args:
plugin_name (str): The new plugin name.
Returns:
KernelFunction: The copied function.
"""
cop: KernelFunction = copy(self)
cop.metadata = deepcopy(self.metadata)
if plugin_name:
cop.metadata.plugin_name = plugin_name
return cop
@@ -1,8 +1,11 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import logging
import os
from typing import TYPE_CHECKING, Any, AsyncIterable, Dict, List, Optional, Union
import yaml
from pydantic import Field, ValidationError, model_validator
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
@@ -30,6 +33,8 @@ if TYPE_CHECKING:
logger: logging.Logger = logging.getLogger(__name__)
PROMPT_FILE_NAME = "skprompt.txt"
CONFIG_FILE_NAME = "config.json"
PROMPT_RETURN_PARAM = KernelParameterMetadata(
name="return",
description="The completion result",
@@ -48,7 +53,7 @@ class KernelFunctionFromPrompt(KernelFunction):
def __init__(
self,
function_name: str,
plugin_name: str,
plugin_name: Optional[str] = None,
description: Optional[str] = None,
prompt: Optional[str] = None,
template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME,
@@ -319,3 +324,80 @@ through prompt_template_config or in the prompt_template."
if parameter.name not in arguments and parameter.default not in {None, "", False, 0}:
arguments[parameter.name] = parameter.default
return arguments
@classmethod
def from_yaml(cls, yaml_str: str, plugin_name: str | None = None) -> "KernelFunctionFromPrompt":
"""Creates a new instance of the KernelFunctionFromPrompt class from a YAML string."""
try:
data = yaml.safe_load(yaml_str)
except yaml.YAMLError as exc: # pragma: no cover
raise FunctionInitializationError(f"Invalid YAML content: {yaml_str}, error: {exc}") from exc
if not isinstance(data, dict):
raise FunctionInitializationError(f"The YAML content must represent a dictionary, got {yaml_str}")
try:
prompt_template_config = PromptTemplateConfig(**data)
except ValidationError as exc:
raise FunctionInitializationError(
f"Error initializing PromptTemplateConfig: {exc} from yaml data: {data}"
) from exc
return cls(
function_name=prompt_template_config.name,
plugin_name=plugin_name,
description=prompt_template_config.description,
prompt_template_config=prompt_template_config,
template_format=prompt_template_config.template_format,
)
@classmethod
def from_directory(cls, path: str, plugin_name: str | None = None) -> "KernelFunctionFromPrompt":
"""Creates a new instance of the KernelFunctionFromPrompt class from a directory.
The directory needs to contain:
- A prompt file named `skprompt.txt`
- A config file named `config.json`
Returns:
KernelFunctionFromPrompt: The kernel function from prompt
"""
prompt_path = os.path.join(path, PROMPT_FILE_NAME)
config_path = os.path.join(path, CONFIG_FILE_NAME)
prompt_exists = os.path.exists(prompt_path)
config_exists = os.path.exists(config_path)
if not config_exists and not prompt_exists:
raise FunctionInitializationError(
f"{PROMPT_FILE_NAME} and {CONFIG_FILE_NAME} files are required to create a "
f"function from a directory, path: {str(path)}."
)
elif not config_exists:
raise FunctionInitializationError(
f"{CONFIG_FILE_NAME} files are required to create a function from a directory, "
f"path: {str(path)}, prompt file is there."
)
elif not prompt_exists:
raise FunctionInitializationError(
f"{PROMPT_FILE_NAME} files are required to create a function from a directory, "
f"path: {str(path)}, config file is there."
)
function_name = os.path.basename(path)
with open(config_path, "r") as config_file:
prompt_template_config = PromptTemplateConfig.from_json(config_file.read())
prompt_template_config.name = function_name
with open(prompt_path, "r") as prompt_file:
prompt_template_config.template = prompt_file.read()
prompt_template = TEMPLATE_FORMAT_MAP[prompt_template_config.template_format]( # type: ignore
prompt_template_config=prompt_template_config
)
return cls(
function_name=function_name,
plugin_name=plugin_name,
prompt_template=prompt_template,
prompt_template_config=prompt_template_config,
template_format=prompt_template_config.template_format,
description=prompt_template_config.description,
)
+473 -79
View File
@@ -1,129 +1,523 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import importlib
import inspect
import json
import logging
import os
import sys
from typing import TYPE_CHECKING, Dict, List, Optional, Union
from collections.abc import Callable, Iterable
from glob import glob
from types import MethodType
from typing import TYPE_CHECKING, Any, ItemsView
from semantic_kernel.connectors.openapi_plugin.openapi_manager import create_functions_from_openapi
from semantic_kernel.exceptions.function_exceptions import FunctionInitializationError
if sys.version_info >= (3, 9):
from typing import Annotated
from typing import Annotated # pragma: no cover
else:
from typing_extensions import Annotated
from typing_extensions import Annotated # pragma: no cover
import httpx
from pydantic import Field, StringConstraints
from semantic_kernel.exceptions import FunctionInvalidNameError
from semantic_kernel.connectors.openai_plugin.openai_authentication_config import OpenAIAuthenticationConfig
from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import (
OpenAIFunctionExecutionParameters,
)
from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils
from semantic_kernel.connectors.utils.document_loader import DocumentLoader
from semantic_kernel.exceptions import PluginInitializationError
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.functions.types import KERNEL_FUNCTION_TYPE
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.utils.validation import PLUGIN_NAME_REGEX
if TYPE_CHECKING:
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import (
OpenAPIFunctionExecutionParameters,
)
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
logger = logging.getLogger(__name__)
class KernelPlugin(KernelBaseModel):
"""
Represents a Kernel Plugin with functions.
This class behaves mostly like a dictionary, with functions as values and their names as keys.
When you add a function, through `.set` or `__setitem__`, the function is copied, the metadata is deep-copied
and the name of the plugin is set in the metadata and added to the dict of functions.
This is done in the same way as a normal dict, so a existing key will be overwritten.
Attributes:
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, KernelFunction]): The functions in the plugin,
indexed by their name.
Methods:
set, __setitem__ (key: str, value: KernelFunction): Set a function in the plugin.
get (key: str, default: KernelFunction | None = None): Get a function from the plugin.
__getitem__ (key: str): Get a function from the plugin.
__contains__ (key: str): Check if a function is in the plugin.
__iter__ (): Iterate over the functions in the plugin.
update(*args: Any, **kwargs: Any): Update the plugin with the functions from another.
setdefault(key: str, value: KernelFunction | None): Set a default value for a key.
get_functions_metadata(): Get the metadata for the functions in the plugin.
Class methods:
from_object(plugin_name: str, plugin_instance: Any | dict[str, Any], description: str | None = None):
Create a plugin from a existing object, like a custom class with annotated functions.
from_directory(plugin_name: str, parent_directory: str, description: str | None = None):
Create a plugin from a directory, parsing:
.py files, .yaml files and directories with skprompt.txt and config.json files.
from_openapi(
plugin_name: str,
openapi_document_path: str,
execution_settings: OpenAPIFunctionExecutionParameters | None = None,
description: str | None = None):
Create a plugin from an OpenAPI document.
from_openai(
plugin_name: str,
plugin_url: str | None = None,
plugin_str: str | None = None,
execution_parameters: OpenAIFunctionExecutionParameters | None = None,
description: str | None = None):
Create a plugin from the Open AI manifest.
"""
name: Annotated[str, StringConstraints(pattern=PLUGIN_NAME_REGEX, min_length=1)]
description: Optional[str] = Field(default=None)
functions: Optional[Dict[str, "KernelFunction"]] = Field(default_factory=dict)
description: str | None = None
functions: dict[str, KernelFunction] = Field(default_factory=dict)
def __init__(
self,
name: str,
description: Optional[str] = None,
functions: Optional[Union[List["KernelFunction"], Dict[str, "KernelFunction"]]] = None,
description: str | None = None,
functions: (
KERNEL_FUNCTION_TYPE
| KernelPlugin
| list[KERNEL_FUNCTION_TYPE | KernelPlugin]
| dict[str, KERNEL_FUNCTION_TYPE]
| None
) = None,
):
"""
Initialize a new instance of the KernelPlugin class
"""Create a KernelPlugin
Args:
name (str): The name of the plugin.
description (Optional[str]): The description of the plugin.
functions (List[KernelFunction]): The functions in the plugin.
Attributes:
name (str): The name of the plugin. The name can be upper/lower
case letters and underscores.
description (str, optional): The description of the plugin.
functions (
KernelFunction |
Callable |
list[KernelFunction | Callable | KernelPlugin] |
dict[str, KernelFunction | Callable] |
KernelPlugin |
None):
The functions in the plugin, will be rewritten to a dictionary of functions.
Raises:
ValueError: If the functions list contains duplicate function names.
ValueError: If the functions are not of the correct type.
PydanticError: If the name is not a valid plugin name.
"""
functions_dict = {}
if functions is not None:
if isinstance(functions, list):
for function in functions:
if function.name in functions_dict:
raise FunctionInvalidNameError(f"Duplicate function name detected: {function.name}")
functions_dict[function.name] = function
super().__init__(
name=name,
description=description,
functions=self._validate_functions(functions=functions, plugin_name=name),
)
# region Dict-like methods
def __setitem__(self, key: str, value: KernelFunction) -> None:
self.functions[key] = KernelPlugin._parse_or_copy(value, self.name)
def set(self, key: str, value: KernelFunction) -> None:
"""Set a function in the plugin.
Args:
key (str): The name of the function.
value (KernelFunction): The function to set.
"""
self[key] = value
def __getitem__(self, key: str) -> KernelFunction:
return self.functions[key]
def get(self, key: str, default: KernelFunction | None = None) -> KernelFunction | None:
return self.functions.get(key, default)
def update(self, *args: Any, **kwargs: KernelFunction) -> None:
"""Update the plugin with the functions from another.
Args:
*args: The functions to update the plugin with, can be a dict, list or KernelPlugin.
**kwargs: The kernel functions to update the plugin with.
"""
if len(args) > 1:
raise TypeError("update expected at most 1 arguments, got %d" % len(args))
if args:
other = args[0]
if isinstance(other, KernelPlugin):
other = other.functions
if not isinstance(other, (dict, list)):
raise TypeError(f"Expected dict, KernelPlugin or list as arg, got {type(other)}")
if isinstance(other, dict):
for key in other:
self[key] = other[key]
else:
functions_dict = functions
super().__init__(name=name, description=description, functions=functions_dict)
for item in other:
if isinstance(item, (KernelFunction, Callable)):
item = KernelPlugin._parse_or_copy(item, self.name)
self[item.name] = item
elif isinstance(item, KernelPlugin):
for key in item.functions:
self[key] = item.functions[key]
if kwargs:
for key in kwargs:
self[key] = kwargs[key]
def __len__(self) -> int:
"""
Gets the number of functions in the plugin.
def setdefault(self, key: str, value: KernelFunction | None = None):
if key not in self.functions:
if value is None:
raise ValueError("Value must be provided for new key.")
self[key] = value
return self[key]
Returns:
The number of functions in the plugin.
def __iter__(self) -> Iterable[KernelFunction]:
for function in self.functions.values():
yield function
"""
return len(self.functions)
def __contains__(self, key: str) -> bool:
return key in self.functions
def __contains__(self, function_name: str) -> bool:
"""
Checks if the plugin contains a function with the specified name.
# endregion
# region Properties
Args:
function_name (str): The name of the function.
Returns:
True if the plugin contains a function with the specified name, False otherwise.
"""
return function_name in self.functions.keys()
def __getitem__(self, name: str) -> "KernelFunction":
"""Define the [] operator for the plugin
Args:
name (str): The name of the function to retrieve.
Returns:
The function if it exists, None otherwise.
Raises:
KeyError: If the function does not exist.
"""
if name not in self.functions:
raise KeyError(f"Function {name} not found.")
return self.functions[name]
@classmethod
def from_functions(
cls, functions: List["KernelFunction"], plugin_name: str, description: Optional[str] = None
) -> "KernelPlugin":
"""
Creates a KernelPlugin from a KernelFunction instance.
Args:
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.
Returns:
A KernelPlugin instance.
"""
return cls(name=plugin_name, description=description, functions=functions)
def get_functions_metadata(self) -> List["KernelFunctionMetadata"]:
def get_functions_metadata(self) -> list["KernelFunctionMetadata"]:
"""
Get the metadata for the functions in the plugin.
Returns:
A list of KernelFunctionMetadata instances.
"""
return [func.metadata for func in self.functions.values()]
return [func.metadata for func in self]
# endregion
# region Class Methods
@classmethod
def from_object(
cls, plugin_name: str, plugin_instance: Any | dict[str, Any], description: str | None = None
) -> "KernelPlugin":
"""
Creates a plugin that wraps the specified target object and imports it into the kernel's plugin collection
Args:
plugin_instance (Any | dict[str, Any]): The plugin instance. This can be a custom class or a
dictionary of classes that contains methods with the kernel_function decorator for one or
several methods. See `TextMemoryPlugin` as an example.
plugin_name (str): The name of the plugin. Allows chars: upper, lower ASCII and underscores.
Returns:
KernelPlugin: The imported plugin of type KernelPlugin.
"""
functions: list[KernelFunction] = []
candidates: list[tuple[str, MethodType]] | ItemsView[str, Any] = []
if isinstance(plugin_instance, dict):
candidates = plugin_instance.items()
else:
candidates = inspect.getmembers(plugin_instance, inspect.ismethod)
# Read every method from the plugin instance
functions = [
KernelFunctionFromMethod(method=candidate, plugin_name=plugin_name)
for _, candidate in candidates
if hasattr(candidate, "__kernel_function__")
]
return cls(name=plugin_name, description=description, functions=functions) # type: ignore
@classmethod
def from_directory(
cls,
plugin_name: str,
parent_directory: str,
description: str | None = None,
class_init_arguments: dict[str, dict[str, Any]] | None = None,
) -> "KernelPlugin":
"""Create a plugin from a specified directory.
This method does not recurse into subdirectories beyond one level deep from the specified plugin directory.
For YAML files, function names are extracted from the content of the YAML files themselves (the name property).
For directories, the function name is assumed to be the name of the directory. Each KernelFunction object is
initialized with data parsed from the associated files and added to a list of functions that are then assigned
to the created KernelPlugin object.
A .py file is parsed and a plugin created,
the functions within as then combined with any other functions found.
The python file needs to contain a class with one or more kernel_function decorated methods.
If this class has a `__init__` method, it will be called with the arguments provided in the
`class_init_arguments` dictionary, the key needs to be the same as the name of the class,
with the value being a dictionary of arguments to pass to the class (using kwargs).
Example:
Assuming a plugin directory structure as follows:
MyPlugins/
|--- pluginA.yaml
|--- pluginB.yaml
|--- native_function.py
|--- Directory1/
|--- skprompt.txt
|--- config.json
|--- Directory2/
|--- skprompt.txt
|--- config.json
Calling `KernelPlugin.from_directory("MyPlugins", "/path/to")` will create a KernelPlugin object named
"MyPlugins", containing KernelFunction objects for `pluginA.yaml`, `pluginB.yaml`,
`Directory1`, and `Directory2`, each initialized with their respective configurations.
And functions for anything within native_function.py.
Args:
plugin_name (str): The name of the plugin, this is the name of the directory within the parent directory
parent_directory (str): The parent directory path where the plugin directory resides
description (str | None): The description of the plugin
class_init_arguments (dict[str, dict[str, Any]] | None): The class initialization arguments
Returns:
KernelPlugin: The created plugin of type KernelPlugin.
Raises:
PluginInitializationError: If the plugin directory does not exist.
PluginInvalidNameError: If the plugin name is invalid.
"""
plugin_directory = os.path.abspath(os.path.join(parent_directory, plugin_name))
if not os.path.exists(plugin_directory):
raise PluginInitializationError(f"Plugin directory does not exist: {plugin_name}")
functions: list[KernelFunction] = []
for object in glob(os.path.join(plugin_directory, "*")):
logger.debug(f"Found object: {object}")
if os.path.isdir(object):
if os.path.basename(object).startswith("__"):
continue
try:
functions.append(KernelFunctionFromPrompt.from_directory(path=object))
except FunctionInitializationError:
logger.warning(f"Failed to create function from directory: {object}")
elif object.endswith(".yaml") or object.endswith(".yml"):
with open(object, "r") as file:
try:
functions.append(KernelFunctionFromPrompt.from_yaml(file.read()))
except FunctionInitializationError:
logger.warning(f"Failed to create function from YAML file: {object}")
elif object.endswith(".py"):
try:
functions.extend(
cls.from_python_file(
plugin_name=plugin_name,
py_file=object,
description=description,
class_init_arguments=class_init_arguments,
)
)
except PluginInitializationError:
logger.warning(f"Failed to create function from Python file: {object}")
else:
logger.warning(f"Unknown file found: {object}")
if not functions:
raise PluginInitializationError(f"No functions found in folder: {parent_directory}/{plugin_name}")
return cls(name=plugin_name, description=description, functions=functions)
@classmethod
def from_openapi(
cls,
plugin_name: str,
openapi_document_path: str,
execution_settings: "OpenAPIFunctionExecutionParameters | None" = None,
description: str | None = None,
) -> "KernelPlugin":
"""Create a plugin from an OpenAPI document.
Args:
plugin_name (str): The name of the plugin
plugin_url (str | None): The URL of the plugin
plugin_str (str | None): The JSON string of the plugin
execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters
description (str | None): The description of the plugin
Returns:
KernelPlugin: The created plugin
Raises:
PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided
"""
if not openapi_document_path:
raise PluginInitializationError("OpenAPI document path is required.")
return cls(
name=plugin_name,
description=description,
functions=create_functions_from_openapi(
plugin_name=plugin_name,
openapi_document_path=openapi_document_path,
execution_settings=execution_settings,
),
)
@classmethod
async def from_openai(
cls,
plugin_name: str,
plugin_url: str | None = None,
plugin_str: str | None = None,
execution_parameters: OpenAIFunctionExecutionParameters | None = None,
description: str | None = None,
) -> "KernelPlugin":
"""Create a plugin from the Open AI manifest.
Args:
plugin_name (str): The name of the plugin
plugin_url (str | None): The URL of the plugin
plugin_str (str | None): The JSON string of the plugin
execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters
Returns:
KernelPlugin: The created plugin
Raises:
PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided
"""
if execution_parameters is None:
execution_parameters = OpenAIFunctionExecutionParameters()
if plugin_str is not None:
# Load plugin from the provided JSON string/YAML string
openai_manifest = plugin_str
elif plugin_url is not None:
# Load plugin from the URL
http_client = execution_parameters.http_client if execution_parameters.http_client else httpx.AsyncClient()
openai_manifest = await DocumentLoader.from_uri(
url=plugin_url, http_client=http_client, auth_callback=None, user_agent=execution_parameters.user_agent
)
else:
raise PluginInitializationError("Either plugin_url or plugin_json must be provided.")
try:
plugin_json = json.loads(openai_manifest)
except json.JSONDecodeError as ex:
raise PluginInitializationError("Parsing of Open AI manifest for auth config failed.") from ex
openai_auth_config = OpenAIAuthenticationConfig(**plugin_json["auth"])
openapi_spec_url = OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json=plugin_json)
# Modify the auth callback in execution parameters if it's provided
if execution_parameters and execution_parameters.auth_callback:
initial_auth_callback = execution_parameters.auth_callback
async def custom_auth_callback(**kwargs: Any):
return await initial_auth_callback(plugin_name, openai_auth_config, **kwargs) # pragma: no cover
execution_parameters.auth_callback = custom_auth_callback
return cls(
name=plugin_name,
description=description,
functions=create_functions_from_openapi(
plugin_name=plugin_name,
openapi_document_path=openapi_spec_url,
execution_settings=execution_parameters,
),
)
@classmethod
def from_python_file(
cls,
plugin_name: str,
py_file: str,
description: str | None = None,
class_init_arguments: dict[str, dict[str, Any]] | None = None,
) -> "KernelPlugin":
module_name = os.path.basename(py_file).replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, py_file)
module = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(module)
for name, cls_instance in inspect.getmembers(module, inspect.isclass):
if cls_instance.__module__ != module_name:
continue
instance = getattr(module, name)(**class_init_arguments.get(name, {}) if class_init_arguments else {})
return cls.from_object(plugin_name=plugin_name, description=description, plugin_instance=instance)
raise PluginInitializationError(f"No class found in file: {py_file}")
# endregion
# region Internal Static Methods
@staticmethod
def _validate_functions(
functions: (
KERNEL_FUNCTION_TYPE
| list[KERNEL_FUNCTION_TYPE | KernelPlugin]
| dict[str, KERNEL_FUNCTION_TYPE]
| KernelPlugin
| None
),
plugin_name: str,
) -> dict[str, KernelFunction]:
"""Validates the functions and returns a dictionary of functions."""
if not functions or not plugin_name:
# if the plugin_name is not present, the validation will fail, so no point in parsing.
return {}
if isinstance(functions, dict):
return {
name: KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
for name, function in functions.items()
}
if isinstance(functions, KernelPlugin):
return {
name: function.function_copy(plugin_name=plugin_name) for name, function in functions.functions.items()
}
if isinstance(functions, KernelFunction):
return {functions.name: KernelPlugin._parse_or_copy(function=functions, plugin_name=plugin_name)}
if isinstance(functions, Callable):
function = KernelPlugin._parse_or_copy(function=functions, plugin_name=plugin_name)
return {function.name: function}
if isinstance(functions, list):
functions_dict: dict[str, KernelFunction] = {}
for function in functions:
if isinstance(function, (KernelFunction, Callable)):
function = KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
functions_dict[function.name] = function
elif isinstance(function, KernelPlugin): # type: ignore
functions_dict.update(
{
name: KernelPlugin._parse_or_copy(function=function, plugin_name=plugin_name)
for name, function in function.functions.items()
}
)
else:
raise ValueError(f"Invalid type for functions in list: {function} (type: {type(function)})")
return functions_dict
raise ValueError(f"Invalid type for supplied functions: {functions} (type: {type(functions)})")
@staticmethod
def _parse_or_copy(function: KERNEL_FUNCTION_TYPE, plugin_name: str) -> KernelFunction:
"""Handle the function and return a KernelFunction instance."""
if isinstance(function, KernelFunction):
return function.function_copy(plugin_name=plugin_name)
if isinstance(function, Callable):
return KernelFunctionFromMethod(method=function, plugin_name=plugin_name)
raise ValueError(f"Invalid type for function: {function} (type: {type(function)})")
# endregion
@@ -1,231 +0,0 @@
# Copyright (c) Microsoft. All rights reserved.
import logging
from typing import TYPE_CHECKING, Any, Dict, Iterable, List, TypeVar, Union
from semantic_kernel.exceptions import (
PluginInitializationError,
PluginInvalidNameError,
)
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
from semantic_kernel.functions.kernel_plugin import KernelPlugin
from semantic_kernel.kernel_pydantic import KernelBaseModel
# To support Python 3.8, need to use TypeVar since Iterable is not scriptable
KernelPluginType = TypeVar("KernelPluginType", bound=KernelPlugin)
if TYPE_CHECKING:
from semantic_kernel.functions.kernel_function import KernelFunction
logger = logging.getLogger(__name__)
class KernelPluginCollection(KernelBaseModel):
"""
The Kernel Plugin Collection class. This class is used to store a collection of plugins.
Attributes:
plugins (Dict[str, KernelPlugin]): The plugins in the collection, indexed by their name.
"""
plugins: Dict[str, "KernelPlugin"]
def __init__(self, plugins: Union[None, "KernelPluginCollection", Iterable[KernelPluginType]] = None):
"""
Initialize a new instance of the KernelPluginCollection class
Args:
plugins (Union[None, KernelPluginCollection, Iterable[KernelPlugin]]): The plugins to add
to the collection. If None, an empty collection is created. If a KernelPluginCollection,
the plugins are copied from the other collection. If an iterable of KernelPlugin,
the plugins are added to the collection.
Raises:
ValueError: If the plugins is not None, a KernelPluginCollection, or an iterable of KernelPlugin.
"""
if plugins is None:
plugins = {}
elif isinstance(plugins, KernelPluginCollection):
# Extract plugins from another KernelPluginCollection instance
plugins = {plugin.name: plugin for plugin in plugins.plugins.values()}
elif isinstance(plugins, Iterable):
# Process an iterable of plugins
plugins = self._process_plugins_iterable(plugins)
else:
raise PluginInitializationError("Invalid type for plugins")
super().__init__(plugins=plugins)
@staticmethod
def _process_plugins_iterable(plugins_input: Iterable[KernelPlugin]) -> Dict[str, "KernelPlugin"]:
plugins_dict = {}
for plugin in plugins_input:
if plugin is None:
raise PluginInvalidNameError("Plugin and plugin.name must not be None")
if plugin.name in plugins_dict:
raise PluginInvalidNameError(f"Duplicate plugin name detected: {plugin.name}")
plugins_dict[plugin.name] = plugin
return plugins_dict
def add(self, plugin: "KernelPlugin") -> None:
"""
Add a single plugin to the collection
Args:
plugin (KernelPlugin): The plugin to add to the collection.
Raises:
ValueError: If the plugin or plugin.name is None.
"""
if plugin.name in self.plugins.keys():
logger.warning(f'Overwriting plugin "{plugin.name}" in collection')
self.plugins[plugin.name] = plugin
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[KernelFunction]): The functions to add to the plugin.
Raises:
ValueError: If the function or plugin_name is None or invalid.
"""
if not functions or not plugin_name:
raise PluginInitializationError("Functions or plugin_name must not be None or empty")
plugin = KernelPlugin.from_functions(plugin_name=plugin_name, functions=functions)
self.plugins[plugin_name] = plugin
def add_functions_to_plugin(self, functions: List["KernelFunction"], plugin_name: str) -> None:
"""
Add functions to a plugin in the collection
Args:
functions (List[KernelFunction]): The function to add to the plugin.
plugin_name (str): The name of the plugin to add the function to.
Raises:
ValueError: If the functions or plugin_name is None or invalid.
ValueError: if the function already exists in the plugin.
"""
if not functions or not plugin_name:
raise PluginInitializationError("Functions and plugin_name must not be None or empty")
if plugin_name not in self.plugins:
self.plugins[plugin_name] = KernelPlugin(name=plugin_name, functions=functions)
return
for func in functions:
if func.name in self.plugins[plugin_name].functions:
logger.warning(f'Overwriting function "{func.name}" in collection')
self.plugins[plugin_name].functions[func.name] = func
def add_list_of_plugins(self, plugins: List["KernelPlugin"]) -> None:
"""
Add a list of plugins to the collection
Args:
plugins (List[KernelPlugin]): The plugins to add to the collection.
Raises:
ValueError: If the plugins list is None.
"""
if plugins is None:
raise PluginInitializationError("Plugins must not be None")
for plugin in plugins:
self.add(plugin)
def remove(self, plugin: "KernelPlugin") -> bool:
"""
Remove a plugin from the collection
Args:
plugin (KernelPlugin): The plugin to remove from the collection.
Returns:
True if the plugin was removed, False otherwise.
"""
if plugin is None or plugin.name is None:
return False
return self.plugins.pop(plugin.name, None) is not None
def remove_by_name(self, plugin_name: str) -> bool:
"""
Remove a plugin from the collection by name
Args:
plugin_name (str): The name of the plugin to remove from the collection.
Returns:
True if the plugin was removed, False otherwise.
"""
if plugin_name is None:
return False
return self.plugins.pop(plugin_name, None) is not None
def __getitem__(self, name):
"""Define the [] operator for the collection
Args:
name (str): The name of the plugin to retrieve.
Returns:
The plugin if it exists, None otherwise.
Raises:
KeyError: If the plugin does not exist.
"""
if name not in self.plugins:
raise KeyError(f"Plugin {name} not found.")
return self.plugins[name]
def clear(self):
"""Clear the collection of all plugins"""
self.plugins.clear()
def get_list_of_function_metadata(
self, include_prompt: bool = True, include_native: bool = True
) -> List[KernelFunctionMetadata]:
"""
Get a list of the function metadata in the plugin collection
Args:
include_prompt (bool): Whether to include semantic functions in the list.
include_native (bool): Whether to include native functions in the list.
Returns:
A list of KernelFunctionMetadata objects in the collection.
"""
if not self.plugins:
return []
return [
func.metadata
for plugin in self.plugins.values()
for func in plugin.functions.values()
if (include_prompt and func.is_prompt) or (include_native and not func.is_prompt)
]
def __iter__(self) -> Any:
"""Define an iterator for the collection"""
return iter(self.plugins.values())
def __len__(self) -> int:
"""Define the length of the collection"""
return len(self.plugins)
def __contains__(self, plugin_name: str) -> bool:
"""
Check if the collection contains a plugin
Args:
plugin_name (str): The name of the plugin to check for.
Returns:
True if the collection contains the plugin, False otherwise.
"""
if not plugin_name:
return False
return self.plugins.get(plugin_name) is not None
@@ -0,0 +1,7 @@
# Copyright (c) Microsoft. All rights reserved.
from typing import Any, Callable, Union
from semantic_kernel.functions.kernel_function import KernelFunction
KERNEL_FUNCTION_TYPE = Union[KernelFunction, Callable[..., Any]]
+301 -436
View File
@@ -1,55 +1,28 @@
# Copyright (c) Microsoft. All rights reserved.
from __future__ import annotations
import glob
import importlib
import inspect
import json
import logging
import os
from copy import copy
from types import MethodType
from typing import TYPE_CHECKING, Any, AsyncIterable, Callable, ItemsView, Literal, Type, TypeVar, Union
from typing import TYPE_CHECKING, Any, AsyncIterable, Callable, Literal, Type, TypeVar, Union
import httpx
import yaml
from pydantic import Field, field_validator
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.openai_plugin.openai_authentication_config import OpenAIAuthenticationConfig
from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import (
OpenAIFunctionExecutionParameters,
)
from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils
from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import (
OpenAPIFunctionExecutionParameters,
)
from semantic_kernel.connectors.openapi_plugin.openapi_manager import OpenAPIPlugin
from semantic_kernel.connectors.utils.document_loader import DocumentLoader
from semantic_kernel.contents.streaming_content_mixin import StreamingContentMixin
from semantic_kernel.events import FunctionInvokedEventArgs, FunctionInvokingEventArgs
from semantic_kernel.exceptions import (
FunctionInitializationError,
FunctionNameNotUniqueError,
KernelFunctionAlreadyExistsError,
KernelFunctionNotFoundError,
KernelInvokeException,
KernelPluginInvalidConfigurationError,
KernelPluginNotFoundError,
KernelServiceNotFoundError,
PluginInitializationError,
PluginInvalidNameError,
ServiceInvalidTypeError,
TemplateSyntaxError,
)
from semantic_kernel.functions.function_result import FunctionResult
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function import TEMPLATE_FORMAT_MAP, KernelFunction
from semantic_kernel.functions.kernel_function_from_method import KernelFunctionFromMethod
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
from semantic_kernel.functions.kernel_plugin import KernelPlugin
from semantic_kernel.functions.kernel_plugin_collection import KernelPluginCollection
from semantic_kernel.kernel_pydantic import KernelBaseModel
from semantic_kernel.prompt_template.const import KERNEL_TEMPLATE_FORMAT_NAME, TEMPLATE_FORMAT_TYPES
from semantic_kernel.prompt_template.prompt_template_base import PromptTemplateBase
@@ -58,12 +31,19 @@ from semantic_kernel.reliability.pass_through_without_retry import PassThroughWi
from semantic_kernel.reliability.retry_mechanism_base import RetryMechanismBase
from semantic_kernel.services.ai_service_client_base import AIServiceClientBase
from semantic_kernel.services.ai_service_selector import AIServiceSelector
from semantic_kernel.utils.validation import validate_plugin_name
if TYPE_CHECKING:
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import (
OpenAIFunctionExecutionParameters,
)
from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import (
OpenAPIFunctionExecutionParameters,
)
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.functions.types import KERNEL_FUNCTION_TYPE
T = TypeVar("T")
@@ -78,7 +58,7 @@ class Kernel(KernelBaseModel):
semantic/native functions, and manage plugins, memory, and AI services.
Attributes:
plugins (KernelPluginCollection | None): The collection of plugins to be used by the kernel
plugins (dict[str, KernelPlugin] | None): The plugins to be used by the kernel
services (dict[str, AIServiceClientBase]): The services to be used by the kernel
retry_mechanism (RetryMechanismBase): The retry mechanism to be used by the kernel
function_invoking_handlers (dict): The function invoking handlers
@@ -87,7 +67,7 @@ class Kernel(KernelBaseModel):
# region Init
plugins: KernelPluginCollection = Field(default_factory=KernelPluginCollection)
plugins: dict[str, KernelPlugin] = Field(default_factory=dict)
services: dict[str, AIServiceClientBase] = Field(default_factory=dict)
ai_service_selector: AIServiceSelector = Field(default_factory=AIServiceSelector)
retry_mechanism: RetryMechanismBase = Field(default_factory=PassThroughWithoutRetry)
@@ -100,7 +80,7 @@ class Kernel(KernelBaseModel):
def __init__(
self,
plugins: KernelPluginCollection | None = None,
plugins: KernelPlugin | dict[str, KernelPlugin] | list[KernelPlugin] | None = None,
services: AIServiceClientBase | list[AIServiceClientBase] | dict[str, AIServiceClientBase] | None = None,
ai_service_selector: AIServiceSelector | None = None,
**kwargs: Any,
@@ -109,10 +89,10 @@ class Kernel(KernelBaseModel):
Initialize a new instance of the Kernel class.
Args:
plugins (KernelPluginCollection | None): The collection of plugins to be used by the kernel
plugins (KernelPlugin | dict[str, KernelPlugin] | list[KernelPlugin] | None):
The plugins to be used by the kernel, will be rewritten to a dict with plugin name as key
services (AIServiceClientBase | list[AIServiceClientBase] | dict[str, AIServiceClientBase] | None:
The services to be used by the kernel,
will be rewritten to a dict with service_id as key
The services to be used by the kernel, will be rewritten to a dict with service_id as key
ai_service_selector (AIServiceSelector | None): The AI service selector to be used by the kernel,
default is based on order of execution settings.
**kwargs (Any): Additional fields to be passed to the Kernel model,
@@ -123,14 +103,27 @@ class Kernel(KernelBaseModel):
"""
args = {
"services": services,
"plugins": plugins,
**kwargs,
}
if ai_service_selector:
args["ai_service_selector"] = ai_service_selector
if plugins:
args["plugins"] = plugins
super().__init__(**args)
@field_validator("plugins", mode="before")
@classmethod
def rewrite_plugins(
cls, plugins: KernelPlugin | list[KernelPlugin] | dict[str, KernelPlugin] | None = None
) -> dict[str, KernelPlugin]:
"""Rewrite plugins to a dictionary."""
if not plugins:
return {}
if isinstance(plugins, KernelPlugin):
return {plugins.name: plugins}
if isinstance(plugins, list):
return {p.name: p for p in plugins}
return plugins
@field_validator("services", mode="before")
@classmethod
def rewrite_services(
@@ -151,7 +144,7 @@ class Kernel(KernelBaseModel):
async def invoke_stream(
self,
function: KernelFunction | None = None,
function: "KernelFunction" | None = None,
arguments: KernelArguments | None = None,
function_name: str | None = None,
plugin_name: str | None = None,
@@ -182,7 +175,7 @@ class Kernel(KernelBaseModel):
if not function:
if not function_name or not plugin_name:
raise KernelFunctionNotFoundError("No function(s) or function- and plugin-name provided")
function = self.func(plugin_name, function_name)
function = self.get_function(plugin_name, function_name)
function_invoking_args = self.on_function_invoking(function.metadata, arguments)
if function_invoking_args.is_cancel_requested:
@@ -227,7 +220,7 @@ class Kernel(KernelBaseModel):
async def invoke(
self,
function: KernelFunction | None = None,
function: "KernelFunction" | None = None,
arguments: KernelArguments | None = None,
function_name: str | None = None,
plugin_name: str | None = None,
@@ -255,7 +248,7 @@ class Kernel(KernelBaseModel):
if not function:
if not function_name or not plugin_name:
raise KernelFunctionNotFoundError("No function or plugin name provided")
function = self.func(plugin_name, function_name)
function = self.get_function(plugin_name, function_name)
function_invoking_args = self.on_function_invoking(function.metadata, arguments)
if function_invoking_args.is_cancel_requested:
logger.info(
@@ -343,6 +336,8 @@ class Kernel(KernelBaseModel):
if not prompt:
raise TemplateSyntaxError("The prompt is either null or empty.")
from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt
function = KernelFunctionFromPrompt(
function_name=function_name,
plugin_name=plugin_name,
@@ -401,201 +396,175 @@ class Kernel(KernelBaseModel):
del self.function_invoked_handlers[id(handler)]
# endregion
# region Plugins
# region Plugins & Functions
def add_plugin(self, plugin_name: str, functions: list[KernelFunction], plugin: KernelPlugin | None = None) -> None:
def add_plugin(
self,
plugin: KernelPlugin | Any | dict[str, Any] | None = None,
plugin_name: str | None = None,
parent_directory: str | None = None,
description: str | None = None,
class_init_arguments: dict[str, dict[str, Any]] | None = None,
) -> "KernelPlugin":
"""
Adds a plugin to the kernel's collection of plugins. If a plugin instance is provided,
Adds a plugin to the kernel's collection of plugins. If a plugin is provided,
it uses that instance instead of creating a new KernelPlugin.
See KernelPlugin.from_directory for more details on how the directory is parsed.
Args:
plugin_name (str): The name of the plugin
functions (list[KernelFunction]): The functions to add to the plugin
plugin (KernelPlugin | None): An optional pre-defined plugin instance
"""
if plugin is None:
# If no plugin instance is provided, create a new KernelPlugin
plugin = KernelPlugin(name=plugin_name, functions=functions)
if plugin_name in self.plugins:
self.plugins.add_functions_to_plugin(functions=functions, plugin_name=plugin_name)
else:
self.plugins.add(plugin)
def import_plugin_from_object(self, plugin_instance: Any | dict[str, Any], plugin_name: str) -> KernelPlugin:
"""
Creates a plugin that wraps the specified target object and imports it into the kernel's plugin collection
Args:
plugin_instance (Any | dict[str, Any]): The plugin instance. This can be a custom class or a
dictionary of classes that contains methods with the kernel_function decorator for one or
several methods. See `TextMemoryPlugin` as an example.
plugin_name (str): The name of the plugin. Allows chars: upper, lower ASCII and underscores.
plugin (KernelPlugin | Any | dict[str, Any]): The plugin to add.
This can be a KernelPlugin, in which case it is added straightaway and other parameters are ignored,
a custom class that contains methods with the kernel_function decorator
or a dictionary of functions with the kernel_function decorator for one or
several methods.
plugin_name (str | None): The name of the plugin, used if the plugin is not a KernelPlugin,
if the plugin is None and the parent_directory is set,
KernelPlugin.from_directory is called with those parameters,
see `KernelPlugin.from_directory` for details.
parent_directory (str | None): The parent directory path where the plugin directory resides
description (str | None): The description of the plugin, used if the plugin is not a KernelPlugin.
class_init_arguments (dict[str, dict[str, Any]] | None): The class initialization arguments
Returns:
KernelPlugin: The imported plugin of type KernelPlugin.
"""
if not plugin_name.strip():
raise PluginInvalidNameError("Plugin name cannot be empty")
logger.debug(f"Importing plugin {plugin_name}")
functions: dict[str, KernelFunction] = {}
candidates: list[tuple[str, MethodType]] | ItemsView[str, Any] = []
if isinstance(plugin_instance, dict):
candidates = plugin_instance.items()
else:
candidates = inspect.getmembers(plugin_instance, inspect.ismethod)
# Read every method from the plugin instance
for _, candidate in candidates:
# If the method is a prompt function, register it
if not hasattr(candidate, "__kernel_function__"):
continue
func = KernelFunctionFromMethod(plugin_name=plugin_name, method=candidate)
if func.name in functions:
raise FunctionNameNotUniqueError(
"Overloaded functions are not supported, " "please differentiate function names."
)
functions[func.name] = func
logger.debug(f"Methods imported: {len(functions)}")
plugin = KernelPlugin(name=plugin_name, functions=functions)
self.plugins.add(plugin)
return plugin
def import_native_plugin_from_directory(
self, parent_directory: str, plugin_directory_name: str
) -> KernelPlugin | None:
MODULE_NAME = "native_function"
validate_plugin_name(plugin_directory_name)
plugin_directory = os.path.abspath(os.path.join(parent_directory, plugin_directory_name))
native_py_file_path = os.path.join(plugin_directory, f"{MODULE_NAME}.py")
if not os.path.exists(native_py_file_path):
raise PluginInitializationError(f"Native Plugin Python File does not exist: {native_py_file_path}")
plugin_name = os.path.basename(plugin_directory)
spec = importlib.util.spec_from_file_location(MODULE_NAME, native_py_file_path)
if not spec:
raise PluginInitializationError(f"Failed to load plugin: {plugin_name}")
module = importlib.util.module_from_spec(spec)
assert spec.loader
spec.loader.exec_module(module)
class_name = next(
(name for name, cls in inspect.getmembers(module, inspect.isclass) if cls.__module__ == MODULE_NAME),
None,
)
if class_name:
plugin_obj = getattr(module, class_name)()
return self.import_plugin_from_object(plugin_obj, plugin_name)
return None
def import_plugin_from_prompt_directory(self, parent_directory: str, plugin_directory_name: str) -> KernelPlugin:
"""
Import a plugin from a specified directory, processing both YAML files and subdirectories
containing `skprompt.txt` and `config.json` files to create KernelFunction objects. These objects
are then grouped into a single KernelPlugin instance.
This method does not recurse into subdirectories beyond one level deep from the specified plugin directory.
For YAML files, function names are extracted from the content of the YAML files themselves (the name property).
For directories, the function name is assumed to be the name of the directory. Each KernelFunction object is
initialized with data parsed from the associated files and added to a list of functions that are then assigned
to the created KernelPlugin object.
Args:
parent_directory (str): The parent directory path where the plugin directory resides. This should be
an absolute path to ensure correct file resolution.
plugin_directory_name (str): The name of the directory that contains the plugin's YAML files and
subdirectories. This directory name is used as the plugin name and should be directly under the
parent_directory.
Returns:
KernelPlugin: An instance of KernelPlugin containing all the KernelFunction objects created from
the YAML files and directories found in the specified plugin directory. The name of the
plugin is set to the plugin_directory_name.
KernelPlugin: The plugin that was added.
Raises:
PluginInitializationError: If the plugin directory does not exist.
PluginInvalidNameError: If the plugin name is invalid.
ValidationError: If a KernelPlugin needs to be created, but it is not valid.
Example:
Assuming a plugin directory structure as follows:
MyPlugins/
|--- pluginA.yaml
|--- pluginB.yaml
|--- Directory1/
|--- skprompt.txt
|--- config.json
|--- Directory2/
|--- skprompt.txt
|--- config.json
Calling `import_plugin("/path/to", "MyPlugins")` will create a KernelPlugin object named
"MyPlugins", containing KernelFunction objects for `pluginA.yaml`, `pluginB.yaml`,
`Directory1`, and `Directory2`, each initialized with their respective configurations.
"""
plugin_directory = self._validate_plugin_directory(
parent_directory=parent_directory, plugin_directory_name=plugin_directory_name
)
if isinstance(plugin, KernelPlugin):
self.plugins[plugin.name] = plugin
return self.plugins[plugin.name]
if not plugin_name:
raise ValueError("plugin_name must be provided if a plugin is not supplied.")
if plugin:
self.plugins[plugin_name] = KernelPlugin.from_object(
plugin_name=plugin_name, plugin_instance=plugin, description=description
)
return self.plugins[plugin_name]
if plugin is None and parent_directory is not None:
self.plugins[plugin_name] = KernelPlugin.from_directory(
plugin_name=plugin_name,
parent_directory=parent_directory,
description=description,
class_init_arguments=class_init_arguments,
)
return self.plugins[plugin_name]
raise ValueError("plugin or parent_directory must be provided.")
functions = []
def add_plugins(self, plugins: list[KernelPlugin | object] | dict[str, KernelPlugin | object]) -> None:
"""
Adds a list of plugins to the kernel's collection of plugins.
# Handle YAML files at the root
yaml_files = glob.glob(os.path.join(plugin_directory, "*.yaml"))
for yaml_file in yaml_files:
with open(yaml_file, "r") as file:
yaml_content = file.read()
functions.append(self.create_function_from_yaml(yaml_content, plugin_name=plugin_directory_name))
Args:
plugins (list[KernelPlugin] | dict[str, KernelPlugin]): The plugins to add to the kernel
"""
if isinstance(plugins, list):
for plugin in plugins:
self.add_plugin(plugin)
return
for name, plugin in plugins.items():
self.add_plugin(plugin, plugin_name=name)
# Handle directories containing skprompt.txt and config.json
for item in os.listdir(plugin_directory):
item_path = os.path.join(plugin_directory, item)
if os.path.isdir(item_path):
prompt_path = os.path.join(item_path, "skprompt.txt")
config_path = os.path.join(item_path, "config.json")
if os.path.exists(prompt_path) and os.path.exists(config_path):
with open(config_path, "r") as config_file:
prompt_template_config = PromptTemplateConfig.from_json(config_file.read())
prompt_template_config.name = item
with open(prompt_path, "r") as prompt_file:
prompt = prompt_file.read()
prompt_template_config.template = prompt
prompt_template = TEMPLATE_FORMAT_MAP[prompt_template_config.template_format]( # type: ignore
prompt_template_config=prompt_template_config
)
functions.append(
self.create_function_from_prompt(
plugin_name=plugin_directory_name,
prompt_template=prompt_template,
prompt_template_config=prompt_template_config,
template_format=prompt_template_config.template_format,
function_name=item,
description=prompt_template_config.description,
)
)
return KernelPlugin(name=plugin_directory_name, functions=functions)
async def import_plugin_from_openai(
def add_function(
self,
plugin_name: str,
plugin_url: str | None = None,
plugin_str: str | None = None,
execution_parameters: OpenAIFunctionExecutionParameters | None = None,
function: KERNEL_FUNCTION_TYPE | None = None,
function_name: str | None = None,
description: str | None = None,
prompt: str | None = None,
prompt_template_config: PromptTemplateConfig | None = None,
prompt_execution_settings: (
PromptExecutionSettings | list[PromptExecutionSettings] | dict[str, PromptExecutionSettings] | None
) = None,
template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME,
prompt_template: PromptTemplateBase | None = None,
return_plugin: bool = False,
**kwargs: Any,
) -> "KernelFunction | KernelPlugin":
"""
Adds a function to the specified plugin.
Args:
plugin_name (str): The name of the plugin to add the function to
function (KernelFunction | Callable[..., Any]): The function to add
function_name (str): The name of the function
plugin_name (str): The name of the plugin
description (str | None): The description of the function
prompt (str | None): The prompt template.
prompt_template_config (PromptTemplateConfig | None): The prompt template configuration
prompt_execution_settings (PromptExecutionSettings | list[PromptExecutionSettings]
| dict[str, PromptExecutionSettings] | None):
The execution settings, will be parsed into a dict.
template_format (str | None): The format of the prompt template
prompt_template (PromptTemplateBase | None): The prompt template
return_plugin (bool): If True, the plugin is returned instead of the function
kwargs (Any): Additional arguments
Returns:
KernelFunction | KernelPlugin: The function that was added, or the plugin if return_plugin is True
"""
from semantic_kernel.functions.kernel_function import KernelFunction
if function is None:
if not function_name or (not prompt and not prompt_template_config and not prompt_template):
raise ValueError(
"function_name and prompt, prompt_template_config or prompt_template must be provided if a function is not supplied." # noqa: E501
)
if prompt_execution_settings is None and (
prompt_template_config is None or prompt_template_config.execution_settings is None
):
prompt_execution_settings = PromptExecutionSettings(extension_data=kwargs)
function = KernelFunction.from_prompt(
function_name=function_name,
plugin_name=plugin_name,
description=description,
prompt=prompt,
template_format=template_format,
prompt_template=prompt_template,
prompt_template_config=prompt_template_config,
prompt_execution_settings=prompt_execution_settings,
)
elif not isinstance(function, KernelFunction):
function = KernelFunction.from_method(plugin_name=plugin_name, method=function)
if plugin_name not in self.plugins:
plugin = KernelPlugin(name=plugin_name, functions=function)
self.add_plugin(plugin)
return plugin if return_plugin else plugin[function.name]
self.plugins[plugin_name][function.name] = function
return self.plugins[plugin_name] if return_plugin else self.plugins[plugin_name][function.name]
def add_functions(
self,
plugin_name: str,
functions: list[KERNEL_FUNCTION_TYPE] | dict[str, KERNEL_FUNCTION_TYPE],
) -> "KernelPlugin":
"""
Adds a list of functions to the specified plugin.
Args:
plugin_name (str): The name of the plugin to add the functions to
functions (list[KernelFunction] | dict[str, KernelFunction]): The functions to add
Returns:
KernelPlugin: The plugin that the functions were added to.
"""
if plugin_name in self.plugins:
self.plugins[plugin_name].update(functions)
return self.plugins[plugin_name]
return self.add_plugin(KernelPlugin(name=plugin_name, functions=functions)) # type: ignore
def add_plugin_from_openapi(
self,
plugin_name: str,
openapi_document_path: str,
execution_settings: "OpenAPIFunctionExecutionParameters | None" = None,
description: str | None = None,
) -> KernelPlugin:
"""Create a plugin from the Open AI manifest.
"""Add a plugin from the Open AI manifest.
Args:
plugin_name (str): The name of the plugin
@@ -609,246 +578,142 @@ class Kernel(KernelBaseModel):
Raises:
PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided
"""
if execution_parameters is None:
execution_parameters = OpenAIFunctionExecutionParameters()
validate_plugin_name(plugin_name)
if plugin_str is not None:
# Load plugin from the provided JSON string/YAML string
openai_manifest = plugin_str
elif plugin_url is not None:
# Load plugin from the URL
http_client = execution_parameters.http_client if execution_parameters.http_client else httpx.AsyncClient()
openai_manifest = await DocumentLoader.from_uri(
url=plugin_url, http_client=http_client, auth_callback=None, user_agent=execution_parameters.user_agent
return self.add_plugin(
KernelPlugin.from_openapi(
plugin_name=plugin_name,
openapi_document_path=openapi_document_path,
execution_settings=execution_settings,
description=description,
)
else:
raise PluginInitializationError("Either plugin_url or plugin_json must be provided.")
try:
plugin_json = json.loads(openai_manifest)
openai_auth_config = OpenAIAuthenticationConfig(**plugin_json["auth"])
except json.JSONDecodeError as ex:
raise KernelPluginInvalidConfigurationError("Parsing of Open AI manifest for auth config failed.") from ex
# Modify the auth callback in execution parameters if it's provided
if execution_parameters and execution_parameters.auth_callback:
initial_auth_callback = execution_parameters.auth_callback
async def custom_auth_callback(**kwargs):
return await initial_auth_callback(plugin_name, openai_auth_config, **kwargs)
execution_parameters.auth_callback = custom_auth_callback
try:
openapi_spec_url = OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json)
except PluginInitializationError as ex:
raise KernelPluginInvalidConfigurationError(
"Parsing of Open AI manifest for OpenAPI spec URL failed."
) from ex
return self.import_plugin_from_openapi(
plugin_name=plugin_name,
openapi_document_path=openapi_spec_url,
execution_settings=execution_parameters,
)
def import_plugin_from_openapi(
async def add_plugin_from_openai(
self,
plugin_name: str,
openapi_document_path: str,
execution_settings: "OpenAIFunctionExecutionParameters" | "OpenAPIFunctionExecutionParameters" | None = None,
plugin_url: str | None = None,
plugin_str: str | None = None,
execution_parameters: "OpenAIFunctionExecutionParameters | None" = None,
description: str | None = None,
) -> KernelPlugin:
"""Create a plugin from an OpenAPI manifest.
"""Add a plugin from an OpenAPI document.
Args:
plugin_name (str): The name of the plugin
openapi_document_path (str): The OpenAPI document path
execution_settings (OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None):
The execution settings
plugin_url (str | None): The URL of the plugin
plugin_str (str | None): The JSON string of the plugin
execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters
description (str | None): The description of the plugin
Returns:
KernelPlugin: The imported plugin
"""
validate_plugin_name(plugin_name)
if not openapi_document_path:
raise PluginInitializationError("OpenAPI document path is required.")
plugin = OpenAPIPlugin.create(
plugin_name=plugin_name,
openapi_document_path=openapi_document_path,
execution_settings=execution_settings,
)
return self.import_plugin_from_object(plugin, plugin_name)
def _validate_plugin_directory(self, parent_directory: str, plugin_directory_name: str) -> str:
"""Validate the plugin name and that the plugin directory exists.
Args:
parent_directory (str): The parent directory
plugin_directory_name (str): The plugin directory name
Returns:
str: The plugin directory.
Raises:
PluginInitializationError: If the plugin directory does not exist.
PluginInvalidNameError: If the plugin name is invalid.
PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided
"""
validate_plugin_name(plugin_directory_name)
plugin_directory = os.path.join(parent_directory, plugin_directory_name)
plugin_directory = os.path.abspath(plugin_directory)
if not os.path.exists(plugin_directory):
raise PluginInitializationError(f"Plugin directory does not exist: {plugin_directory_name}")
return plugin_directory
# endregion
# region Functions
def func(self, plugin_name: str, function_name: str) -> KernelFunction:
if plugin_name not in self.plugins:
raise KernelPluginNotFoundError(f"Plugin '{plugin_name}' not found")
if function_name not in self.plugins[plugin_name]:
raise KernelFunctionNotFoundError(f"Function '{function_name}' not found in plugin '{plugin_name}'")
return self.plugins[plugin_name][function_name]
def func_from_fully_qualified_function_name(self, fully_qualified_function_name: str) -> KernelFunction:
plugin_name, function_name = fully_qualified_function_name.split("-")
if plugin_name not in self.plugins:
raise KernelPluginNotFoundError(f"Plugin '{plugin_name}' not found")
if function_name not in self.plugins[plugin_name]:
raise KernelFunctionNotFoundError(f"Function '{function_name}' not found in plugin '{plugin_name}'")
return self.plugins[plugin_name][function_name]
def create_function_from_prompt(
self,
function_name: str,
plugin_name: str,
description: str | None = None,
prompt: str | None = None,
prompt_template_config: PromptTemplateConfig | None = None,
prompt_execution_settings: (
PromptExecutionSettings | list[PromptExecutionSettings] | dict[str, PromptExecutionSettings] | None
) = None,
template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME,
prompt_template: PromptTemplateBase | None = None,
**kwargs: Any,
) -> KernelFunction:
"""
Create a Kernel Function from a prompt.
Args:
function_name (str): The name of the function
plugin_name (str): The name of the plugin
description (str | None): The description of the function
prompt (str | None): The prompt template.
prompt_template_config (PromptTemplateConfig | None): The prompt template configuration
prompt_execution_settings (PromptExecutionSettings | list[PromptExecutionSettings]
| dict[str, PromptExecutionSettings] | None):
The execution settings, will be parsed into a dict.
template_format (str | None): The format of the prompt template
prompt_template (PromptTemplateBase | None): The prompt template
kwargs (Any): Additional arguments
Returns:
KernelFunction: The created Kernel Function
"""
if prompt_execution_settings is None and (
prompt_template_config is None or prompt_template_config.execution_settings is None
):
prompt_execution_settings = PromptExecutionSettings(extension_data=kwargs)
function = KernelFunctionFromPrompt(
function_name=function_name,
plugin_name=plugin_name,
description=description,
prompt=prompt,
template_format=template_format,
prompt_template=prompt_template,
prompt_template_config=prompt_template_config,
prompt_execution_settings=prompt_execution_settings,
)
self.add_plugin(plugin_name or function.plugin_name, [function])
return function
def create_function_from_yaml(self, text: str, plugin_name: str) -> KernelFunction:
"""
Import a plugin from a YAML string.
Args:
text (str): The YAML string
Returns:
KernelFunction: The created Kernel Function
Raises:
PluginInitializationError: If the input YAML string is empty
"""
if not text:
raise PluginInitializationError("The input YAML string is empty")
try:
data = yaml.safe_load(text)
except yaml.YAMLError as exc:
raise PluginInitializationError(f"Error loading YAML: {exc}") from exc
if not isinstance(data, dict):
raise PluginInitializationError("The YAML content must represent a dictionary")
try:
prompt_template_config = PromptTemplateConfig(**data)
except TypeError as exc:
raise PluginInitializationError(f"Error initializing PromptTemplateConfig: {exc}") from exc
return self.create_function_from_prompt(
function_name=prompt_template_config.name,
plugin_name=plugin_name,
description=prompt_template_config.description,
prompt_template_config=prompt_template_config,
template_format=prompt_template_config.template_format,
)
def register_function_from_method(
self,
plugin_name: str,
method: Callable[..., Any],
) -> KernelFunction:
"""
Creates a native function from the plugin name and registers it with the kernel.
Args:
plugin_name (str | None): The name of the plugin. If empty, a random name will be generated.
kernel_function (Callable): The kernel function
Returns:
KernelFunction: The created native function
"""
if not hasattr(method, "__kernel_function__"):
raise FunctionInitializationError(
"kernel_function argument must be decorated with @kernel_function",
return self.add_plugin(
await KernelPlugin.from_openai(
plugin_name=plugin_name,
plugin_url=plugin_url,
plugin_str=plugin_str,
execution_parameters=execution_parameters,
description=description,
)
function = KernelFunctionFromMethod(
method=method,
plugin_name=plugin_name,
)
self.add_plugin(plugin_name or function.plugin_name, [function])
return function
def get_plugin(self, plugin_name: str) -> "KernelPlugin":
"""Get a plugin by name.
Args:
plugin_name (str): The name of the plugin
Returns:
KernelPlugin: The plugin
Raises:
KernelPluginNotFoundError: If the plugin is not found
"""
if plugin_name not in self.plugins:
raise KernelPluginNotFoundError(f"Plugin '{plugin_name}' not found")
return self.plugins[plugin_name]
def get_function(self, plugin_name: str | None, function_name: str) -> "KernelFunction":
"""Get a function by plugin_name and function_name.
Args:
plugin_name (str | None): The name of the plugin
function_name (str): The name of the function
Returns:
KernelFunction: The function
Raises:
KernelPluginNotFoundError: If the plugin is not found
KernelFunctionNotFoundError: If the function is not found
"""
if plugin_name is None:
for plugin in self.plugins.values():
if function_name in plugin:
return plugin[function_name]
raise KernelFunctionNotFoundError(f"Function '{function_name}' not found in any plugin.")
if plugin_name not in self.plugins:
raise KernelPluginNotFoundError(f"Plugin '{plugin_name}' not found")
if function_name not in self.plugins[plugin_name]:
raise KernelFunctionNotFoundError(f"Function '{function_name}' not found in plugin '{plugin_name}'")
return self.plugins[plugin_name][function_name]
def get_function_from_fully_qualified_function_name(self, fully_qualified_function_name: str) -> "KernelFunction":
"""Get a function by its fully qualified name (<plugin_name>-<function_name>).
Args:
fully_qualified_function_name (str): The fully qualified name of the function,
if there is no '-' in the name, it is assumed that it is only a function_name.
Returns:
KernelFunction: The function
Raises:
KernelPluginNotFoundError: If the plugin is not found
KernelFunctionNotFoundError: If the function is not found
"""
names = fully_qualified_function_name.split("-", maxsplit=1)
if len(names) == 1:
plugin_name = None
function_name = names[0]
else:
plugin_name = names[0]
function_name = names[1]
return self.get_function(plugin_name, function_name)
def get_list_of_function_metadata(
self, include_prompt: bool = True, include_native: bool = True
) -> list[KernelFunctionMetadata]:
"""
Get a list of the function metadata in the plugin collection
Args:
include_prompt (bool): Whether to include semantic functions in the list.
include_native (bool): Whether to include native functions in the list.
Returns:
A list of KernelFunctionMetadata objects in the collection.
"""
if not self.plugins:
return []
return [
func.metadata
for plugin in self.plugins.values()
for func in plugin.functions.values()
if (include_prompt and func.is_prompt) or (include_native and not func.is_prompt)
]
# endregion
# region Services
def select_ai_service(
self, function: KernelFunction, arguments: KernelArguments
self, function: "KernelFunction", arguments: KernelArguments
) -> tuple[ALL_SERVICE_TYPES, PromptExecutionSettings]:
"""Uses the AI service selector to select a service for the function."""
return self.ai_service_selector.select_ai_service(self, function, arguments)
@@ -74,13 +74,13 @@ class ActionPlanner:
extension_data={"max_tokens": self.config.max_tokens, "stop_sequences": self._stop_sequence},
)
self._planner_function = kernel.create_function_from_prompt(
function_name="ActionPlanner",
kernel.add_plugin(self, self.RESTRICTED_PLUGIN_NAME)
self._planner_function = kernel.add_function(
plugin_name=self.RESTRICTED_PLUGIN_NAME,
function_name="ActionPlanner",
prompt=self._prompt_template,
prompt_execution_settings=execute_settings,
)
kernel.import_plugin_from_object(self, self.RESTRICTED_PLUGIN_NAME)
self._kernel = kernel
self._arguments = KernelArguments()
@@ -213,7 +213,7 @@ class ActionPlanner:
def list_of_functions(self, goal: Annotated[str, "The current goal processed by the planner"]) -> str:
available_functions = [
self._create_function_string(func)
for func in self._kernel.plugins.get_list_of_function_metadata()
for func in self._kernel.get_list_of_function_metadata()
if (
func.plugin_name != self.RESTRICTED_PLUGIN_NAME
and func.plugin_name not in self.config.excluded_plugins
@@ -142,9 +142,7 @@ class BasicPlanner:
# Get a dictionary of plugin names to all native and semantic functions
if not kernel.plugins:
return ""
all_functions = {
f"{func.plugin_name}.{func.name}": func for func in kernel.plugins.get_list_of_function_metadata()
}
all_functions = {f"{func.plugin_name}.{func.name}": func for func in kernel.get_list_of_function_metadata()}
all_functions_descriptions_dict = {key: func.description for key, func in all_functions.items()}
all_functions_params_dict = {key: func.parameters for key, func in all_functions.items()}
@@ -190,7 +188,7 @@ class BasicPlanner:
)
# Create the prompt function for the planner with the given prompt
planner = kernel.create_function_from_prompt(
planner = kernel.add_function(
plugin_name="PlannerPlugin",
function_name="CreatePlan",
prompt_template_config=prompt_template_config,
@@ -225,7 +223,7 @@ class BasicPlanner:
for subtask in subtasks:
plugin_name, function_name = subtask["function"].split(".")
kernel_function = kernel.func(plugin_name, function_name)
kernel_function = kernel.get_function(plugin_name, function_name)
# Get the arguments dictionary for the function
args = subtask.get("args", None)
if args:
@@ -15,10 +15,7 @@ from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_pro
)
from semantic_kernel.connectors.ai.open_ai.services.azure_chat_completion import AzureChatCompletion
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.connectors.ai.open_ai.utils import (
get_function_calling_object,
get_tool_call_object,
)
from semantic_kernel.connectors.ai.open_ai.utils import get_function_calling_object, get_tool_call_object
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.exceptions.planner_exceptions import PlannerInvalidConfigurationError
from semantic_kernel.functions.kernel_arguments import KernelArguments
@@ -138,7 +135,7 @@ class FunctionCallingStepwisePlanner(KernelBaseModel):
# Clone the kernel so that we can add planner-specific plugins without affecting the original kernel instance
cloned_kernel = copy(kernel)
cloned_kernel.import_plugin_from_object(UserInteraction(), "UserInteraction")
cloned_kernel.add_plugin(UserInteraction(), "UserInteraction")
# Create and invoke a kernel function to generate the initial plan
initial_plan = await self._generate_plan(question=question, kernel=cloned_kernel, arguments=arguments)
@@ -224,7 +221,7 @@ class FunctionCallingStepwisePlanner(KernelBaseModel):
if "default" in prompt_template_config.execution_settings:
settings = prompt_template_config.execution_settings.pop("default")
prompt_template_config.execution_settings[self.service_id] = settings
return kernel.create_function_from_prompt(
return kernel.add_function(
function_name="create_plan",
plugin_name="sequential_planner",
description="Create a plan for the given goal",
@@ -61,7 +61,7 @@ class PlannerKernelExtension:
available_functions = [
func
for func in kernel.plugins.get_list_of_function_metadata()
for func in kernel.get_list_of_function_metadata()
if (func.plugin_name not in excluded_plugins and func.name not in excluded_functions)
]
@@ -84,7 +84,7 @@ class SequentialPlanner:
settings = prompt_config.execution_settings.pop("default")
prompt_config.execution_settings[service_id] = settings
return self._kernel.create_function_from_prompt(
return self._kernel.add_function(
plugin_name=self.RESTRICTED_PLUGIN_NAME,
function_name=self.RESTRICTED_PLUGIN_NAME,
prompt_template_config=prompt_config,
@@ -64,7 +64,7 @@ class SequentialPlannerKernelExtension:
available_functions = [
func
for func in kernel.plugins.get_list_of_function_metadata()
for func in kernel.get_list_of_function_metadata()
if (func.plugin_name not in excluded_plugins and func.name not in excluded_functions)
]
@@ -72,7 +72,7 @@ class SequentialPlanParser:
raise PlannerInvalidPlanError(f"Failed to find function '{plugin_function_name}'.") from exc
else:
try:
func = kernel.func_from_fully_qualified_function_name(plugin_function_name)
func = kernel.get_function_from_fully_qualified_function_name(plugin_function_name)
except (KernelFunctionNotFoundError, KernelPluginNotFoundError) as exc:
if allow_missing_functions:
plan.add_steps([Plan.from_goal(plugin_function_name)])
@@ -86,7 +86,7 @@ class StepwisePlanner:
prompt_config.template = prompt_template
self._system_step_function = self.import_function_from_prompt(kernel, "StepwiseStep", prompt_config)
self._native_functions = self._kernel.import_plugin_from_object(self, RESTRICTED_PLUGIN_NAME)
self._native_functions = self._kernel.add_plugin(self, RESTRICTED_PLUGIN_NAME)
self._arguments = KernelArguments()
@@ -324,7 +324,7 @@ class StepwisePlanner:
raise PlannerExecutionException(f"The function '{action_name}' was not found.")
try:
function = self._kernel.func(target_function.plugin_name, target_function.name)
function = self._kernel.get_function(target_function.plugin_name, target_function.name)
action_arguments = self.create_action_arguments(action_variables)
result = await function.invoke(self._kernel, action_arguments)
@@ -360,7 +360,7 @@ class StepwisePlanner:
excluded_functions = self.config.excluded_functions or []
available_functions = [
func
for func in self._kernel.plugins.get_list_of_function_metadata()
for func in self._kernel.get_list_of_function_metadata()
if (func.plugin_name not in excluded_plugins and func.name not in excluded_functions)
]
available_functions = sorted(available_functions, key=lambda x: (x.plugin_name, x.name))
@@ -379,9 +379,10 @@ class StepwisePlanner:
function_name: str,
config: PromptTemplateConfig = None,
) -> "KernelFunction":
return kernel.create_function_from_prompt(
kernel.add_function(
plugin_name=RESTRICTED_PLUGIN_NAME, function_name=function_name, prompt_template_config=config
)
return kernel.get_function(RESTRICTED_PLUGIN_NAME, function_name)
def to_manual_string(self, function: KernelFunctionMetadata) -> str:
inputs = [
@@ -72,16 +72,16 @@ class HandlebarsPromptTemplate(PromptTemplateBase):
"""
if not self._template_compiler:
return ""
if not arguments:
if arguments is None:
arguments = KernelArguments()
helpers = {}
for plugin in kernel.plugins:
for plugin in kernel.plugins.values():
helpers.update(
{
function.fully_qualified_name: create_template_helper_from_function(
function, kernel, arguments, self.prompt_template_config.template_format
)
for function in plugin.functions.values()
for function in plugin
}
)
helpers.update(HANDLEBAR_SYSTEM_HELPERS)
@@ -77,11 +77,11 @@ class Jinja2PromptTemplate(PromptTemplateBase):
"""
if not self._env:
return ""
if not arguments:
if arguments is None:
arguments = KernelArguments()
helpers = {}
helpers.update(JINJA2_SYSTEM_HELPERS)
for plugin in kernel.plugins:
for plugin in kernel.plugins.values():
helpers.update(
{
function.fully_qualified_name.replace("-", "_"): create_template_helper_from_function(
@@ -90,7 +90,7 @@ class Jinja2PromptTemplate(PromptTemplateBase):
arguments,
self.prompt_template_config.template_format,
)
for function in plugin.functions.values()
for function in plugin
}
)
try:
@@ -92,7 +92,7 @@ class KernelPromptTemplate(PromptTemplateBase):
Returns:
The prompt template ready to be used for an AI request
"""
if not arguments:
if arguments is None:
arguments = KernelArguments()
return await self.render_blocks(self._blocks, kernel, arguments)
@@ -2,21 +2,19 @@
import logging
from copy import copy
from typing import TYPE_CHECKING, Any, ClassVar, List, Optional
from typing import TYPE_CHECKING, Any, ClassVar, List
from pydantic import Field, field_validator, model_validator
from semantic_kernel.exceptions import CodeBlockRenderException, CodeBlockTokenError
from semantic_kernel.exceptions.kernel_exceptions import KernelFunctionNotFoundError, KernelPluginNotFoundError
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
from semantic_kernel.functions.kernel_plugin_collection import KernelPluginCollection
from semantic_kernel.template_engine.blocks.block import Block
from semantic_kernel.template_engine.blocks.block_types import BlockTypes
from semantic_kernel.template_engine.blocks.function_id_block import FunctionIdBlock
from semantic_kernel.template_engine.code_tokenizer import CodeTokenizer
if TYPE_CHECKING:
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function import KernelFunction
from semantic_kernel.kernel import Kernel
logger: logging.Logger = logging.getLogger(__name__)
@@ -116,11 +114,12 @@ these will be ignored."
async def _render_function_call(self, kernel: "Kernel", arguments: "KernelArguments"):
function_block = self.tokens[0]
function = self._get_function_from_plugin_collection(kernel.plugins, function_block)
if not function:
try:
function = kernel.get_function(function_block.plugin_name, function_block.function_name)
except (KernelFunctionNotFoundError, KernelPluginNotFoundError) as exc:
error_msg = f"Function `{function_block.content}` not found"
logger.error(error_msg)
raise CodeBlockRenderException(error_msg)
raise CodeBlockRenderException(error_msg) from exc
arguments_clone = copy(arguments)
if len(self.tokens) > 1:
@@ -152,26 +151,3 @@ these will be ignored."
arguments[token.name] = rendered_value
return arguments
def _get_function_from_plugin_collection(
self, plugins: KernelPluginCollection, function_block: FunctionIdBlock
) -> Optional["KernelFunction"]:
"""
Get the function from the plugin collection
Args:
plugins: The plugin collection
function_block: The function block that contains the function name
Returns:
The function if it exists, None otherwise.
"""
if function_block.plugin_name is not None and len(function_block.plugin_name) > 0:
return plugins[function_block.plugin_name][function_block.function_name]
else:
# We now require a plug-in name, but if one isn't set then we'll try to find the function
for plugin in plugins:
if function_block.function_name in plugin:
return plugin[function_block.function_name]
return None
@@ -0,0 +1,36 @@
import sys
from typing import Optional
from semantic_kernel.functions.kernel_function_decorator import kernel_function
if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated
class TestNativeEchoBotPlugin:
"""
Description: Test Native Plugin for testing purposes
"""
def __init__(self, static_input: Optional[str] = None):
self.static_input = static_input or ""
@kernel_function(
description="Echo for input text with static",
name="echo",
)
def echo(self, text: Annotated[str, "The text to echo"]) -> str:
"""
Echo for input text with a static input
Example:
"hello world" => "hello world"
Args:
text -- The text to echo
Returns:
input text
"""
return self.static_input + text
@@ -0,0 +1,13 @@
{
"schema": 1,
"description": "Test Description",
"execution_settings": {
"default": {
"max_tokens": 123,
"temperature": 0.0,
"top_p": 1.0,
"presence_penalty": 0.0,
"frequency_penalty": 2.0
}
}
}
@@ -0,0 +1,5 @@
{{$input}}
==
Test prompt.
==
@@ -0,0 +1,32 @@
import sys
from semantic_kernel.functions.kernel_function_decorator import kernel_function
if sys.version_info >= (3, 9):
from typing import Annotated
else:
from typing_extensions import Annotated
class TestNativeEchoBotPlugin:
"""
Description: Test Native Plugin for testing purposes
"""
@kernel_function(
description="Echo for input text",
name="echoAsync",
)
async def echo(self, text: Annotated[str, "The text to echo"]) -> str:
"""
Echo for input text
Example:
"hello world" => "hello world"
Args:
text -- The text to echo
Returns:
input text
"""
return text
@@ -0,0 +1,12 @@
name: TestFunctionYaml
template_format: semantic-kernel
template: |
{{$input}}
description: A test function from a yaml file.
execution_settings:
default:
temperature: 0.6
max_tokens: 123
top_p: 1.0
presence_penalty: 0.0
frequency_penalty: 2.0
@@ -0,0 +1,13 @@
{
"schema": 1,
"description": "Test Description",
"execution_settings": {
"default": {
"max_tokens": 123,
"temperature": 0.0,
"top_p": 1.0,
"presence_penalty": 0.0,
"frequency_penalty": 2.0
}
}
}
@@ -0,0 +1,5 @@
{{$input}}
==
Test prompt.
==
+2
View File
@@ -115,6 +115,8 @@ def create_mock_function() -> Callable:
mock_function.description = kernel_function_metadata.description
mock_function.invoke.return_value = FunctionResult(function=mock_function.metadata, value=value, metadata={})
mock_function.invoke_stream = stream_func
mock_function.function_copy.return_value = mock_function
mock_function.__kernel_function__ = True
return mock_function
@@ -100,7 +100,7 @@ def setup_gp_text_completion_function(kernel: Kernel, get_gp_config):
prompt_template_config = PromptTemplateConfig(template=prompt, execution_settings=exec_settings)
# Create the semantic function
text2text_function = kernel.create_function_from_prompt(
text2text_function = kernel.add_function(
function_name="hello", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -7,6 +7,9 @@ from openai import AsyncAzureOpenAI
from test_utils import retry
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.utils import get_tool_call_object
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.core_plugins.math_plugin import MathPlugin
@@ -46,13 +49,11 @@ async def test_azure_e2e_chat_completion_with_plugin(setup_tldr_function_for_oai
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config
)
kernel.add_function(function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config)
arguments = KernelArguments(input=text_to_summarize)
summary = await retry(lambda: kernel.invoke(tldr_function, arguments))
summary = await retry(lambda: kernel.invoke(function_name="tldr", plugin_name="plugin", arguments=arguments))
output = str(summary).strip()
print(f"TLDR using input string: '{output}'")
assert "First Law" not in output and ("human" in output or "Human" in output or "preserve" in output)
@@ -102,13 +103,11 @@ async def test_azure_e2e_chat_completion_with_plugin_and_provided_client(
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config
)
kernel.add_function(function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config)
arguments = KernelArguments(input=text_to_summarize)
summary = await retry(lambda: kernel.invoke(tldr_function, arguments))
summary = await retry(lambda: kernel.invoke(function_name="tldr", plugin_name="plugin", arguments=arguments))
output = str(summary).strip()
print(f"TLDR using input string: '{output}'")
assert "First Law" not in output and ("human" in output or "Human" in output or "preserve" in output)
@@ -116,9 +115,7 @@ async def test_azure_e2e_chat_completion_with_plugin_and_provided_client(
@pytest.mark.asyncio
async def test_azure_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_models, get_aoai_config):
kernel, _, _ = setup_tldr_function_for_oai_models
async def test_azure_oai_chat_service_with_tool_call(kernel: Kernel, get_aoai_config):
_, api_key, endpoint = get_aoai_config
if "Python_Integration_Tests" in os.environ:
@@ -147,9 +144,9 @@ async def test_azure_oai_chat_service_with_tool_call(setup_tldr_function_for_oai
),
)
kernel.import_plugin_from_object(MathPlugin(), plugin_name="math")
kernel.add_plugin(MathPlugin(), plugin_name="math")
execution_settings = sk_oai.AzureChatPromptExecutionSettings(
execution_settings = AzureChatPromptExecutionSettings(
service_id="chat_completion",
max_tokens=2000,
temperature=0.7,
@@ -165,11 +162,13 @@ async def test_azure_oai_chat_service_with_tool_call(setup_tldr_function_for_oai
)
# Create the prompt function
tldr_function = kernel.create_function_from_prompt(
kernel.add_function(
function_name="math_fun", plugin_name="math_int_test", prompt_template_config=prompt_template_config
)
summary = await retry(lambda: kernel.invoke(tldr_function, input="what is 1+1?"))
summary = await retry(
lambda: kernel.invoke(function_name="math_fun", plugin_name="math_int_test", input="what is 1+1?")
)
output = str(summary).strip()
print(f"Math output: '{output}'")
assert "2" in output
@@ -206,10 +205,10 @@ async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, g
),
)
kernel.import_plugin_from_object(MathPlugin(), plugin_name="Math")
kernel.add_plugin(MathPlugin(), plugin_name="Math")
# Create the prompt function
chat_func = kernel.create_function_from_prompt(prompt="{{$input}}", function_name="chat", plugin_name="chat")
kernel.add_function(prompt="{{$input}}", function_name="chat", plugin_name="chat")
execution_settings = sk_oai.AzureChatPromptExecutionSettings(
service_id="chat_completion",
max_tokens=2000,
@@ -223,7 +222,7 @@ async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, g
arguments = KernelArguments(input="what is 101+102?", settings=execution_settings)
result = None
async for message in kernel.invoke_stream(chat_func, arguments=arguments):
async for message in kernel.invoke_stream(function_name="chat", plugin_name="chat", arguments=arguments):
result = message[0] if not result else result + message[0]
output = str(result)
@@ -130,10 +130,8 @@ async def create_with_data_chat_function(get_aoai_config, kernel: Kernel, create
)
# Create the semantic function
chat_function = kernel.create_function_from_prompt(
function_name="story", plugin_name="plugin", prompt_template_config=prompt_template_config
)
kernel.add_function(function_name="story", plugin_name="plugin", prompt_template_config=prompt_template_config)
chat_function = kernel.get_function("plugin", "story")
return chat_function, kernel, collection, memory_store
except:
await memory_store.delete_collection(collection)
@@ -46,7 +46,7 @@ async def test_azure_e2e_text_completion_with_plugin(setup_tldr_function_for_oai
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -103,7 +103,7 @@ async def test_azure_e2e_text_completion_with_plugin_with_provided_client(
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -45,7 +45,7 @@ async def test_azure_summarize_conversation_using_plugin(setup_summarize_convers
),
)
conversationSummaryPlugin = kernel.import_plugin_from_object(
conversationSummaryPlugin = kernel.add_plugin(
ConversationSummaryPlugin(kernel, prompt_template_config), "conversationSummary"
)
@@ -92,7 +92,7 @@ async def test_oai_summarize_conversation_using_plugin(
),
)
conversationSummaryPlugin = kernel.import_plugin_from_object(
conversationSummaryPlugin = kernel.add_plugin(
ConversationSummaryPlugin(kernel, prompt_template_config), "conversationSummary"
)
@@ -40,7 +40,7 @@ async def test_gp_chat_service_with_plugins(setup_tldr_function_for_oai_models,
prompt_template_config = PromptTemplateConfig(template=prompt, execution_settings=exec_settings)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="tldr", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -6,9 +6,7 @@ from openai import AsyncOpenAI
from test_utils import retry
import semantic_kernel.connectors.ai.open_ai as sk_oai
from semantic_kernel.connectors.ai.open_ai.utils import (
get_tool_call_object,
)
from semantic_kernel.connectors.ai.open_ai.utils import get_tool_call_object
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.core_plugins.math_plugin import MathPlugin
@@ -38,7 +36,7 @@ async def test_oai_chat_service_with_plugins(setup_tldr_function_for_oai_models,
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -65,7 +63,7 @@ async def test_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_model
),
)
kernel.import_plugin_from_object(MathPlugin(), plugin_name="math")
kernel.add_plugin(MathPlugin(), plugin_name="math")
execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(
service_id="chat-gpt",
@@ -83,7 +81,7 @@ async def test_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_model
)
# Create the prompt function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="math_fun", plugin_name="math_int_test", prompt_template_config=prompt_template_config
)
@@ -110,7 +108,7 @@ async def test_oai_chat_service_with_tool_call_streaming(setup_tldr_function_for
),
)
kernel.import_plugin_from_object(MathPlugin(), plugin_name="math")
kernel.add_plugin(MathPlugin(), plugin_name="math")
execution_settings = sk_oai.OpenAIChatPromptExecutionSettings(
service_id="chat-gpt",
@@ -128,7 +126,7 @@ async def test_oai_chat_service_with_tool_call_streaming(setup_tldr_function_for
)
# Create the prompt function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="math_fun", plugin_name="math_int_test", prompt_template_config=prompt_template_config
)
@@ -175,7 +173,7 @@ async def test_oai_chat_service_with_plugins_with_provided_client(setup_tldr_fun
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story",
plugin_name="story_plugin",
prompt_template_config=prompt_template_config,
@@ -220,7 +218,7 @@ async def test_oai_chat_stream_service_with_plugins(setup_tldr_function_for_oai_
)
# Create the prompt function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story",
plugin_name="story_plugin",
prompt_template_config=prompt_template_config,
@@ -260,9 +258,9 @@ async def test_oai_chat_service_with_yaml_jinja2(setup_tldr_function_for_oai_mod
overwrite=True, # Overwrite the service if it already exists since add service says it does
)
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins", "TestPlugin")
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins")
plugin = kernel.import_plugin_from_prompt_directory(plugins_directory, "TestFunctionYamlJinja2")
plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="TestFunctionYamlJinja2")
assert plugin is not None
assert plugin["TestFunctionJinja2"] is not None
@@ -299,9 +297,9 @@ async def test_oai_chat_service_with_yaml_handlebars(setup_tldr_function_for_oai
overwrite=True, # Overwrite the service if it already exists since add service says it does
)
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins", "TestPlugin")
plugins_directory = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins")
plugin = kernel.import_plugin_from_prompt_directory(plugins_directory, "TestFunctionYamlHandlebars")
plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="TestFunctionYamlHandlebars")
assert plugin is not None
assert plugin["TestFunctionHandlebars"] is not None
@@ -36,7 +36,7 @@ async def test_oai_text_completion_with_plugins(setup_tldr_function_for_oai_mode
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story", plugin_name="plugin", prompt_template_config=prompt_template_config
)
@@ -84,7 +84,7 @@ async def test_oai_text_completion_with_plugins_with_provided_client(
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story",
plugin_name="plugin",
prompt_template_config=prompt_template_config,
@@ -134,7 +134,7 @@ async def test_oai_text_stream_completion_with_plugins(setup_tldr_function_for_o
)
# Create the semantic function
tldr_function = kernel.create_function_from_prompt(
tldr_function = kernel.add_function(
function_name="story",
plugin_name="plugin",
prompt_template_config=prompt_template_config,
@@ -30,7 +30,7 @@ async def test_azure_text_embedding_service(kernel: Kernel, get_aoai_config):
kernel.add_service(embeddings_gen)
memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embeddings_gen)
kernel.import_plugin_from_object(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000")
await memory.save_reference(
@@ -66,7 +66,7 @@ async def test_azure_text_embedding_service_with_provided_client(kernel: Kernel,
kernel.add_service(embeddings_gen)
memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embeddings_gen)
kernel.import_plugin_from_object(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000")
await memory.save_reference(
@@ -29,7 +29,7 @@ async def test_gp_embedding_service(kernel: Kernel, get_gp_config):
kernel.add_service(palm_text_embed)
memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=palm_text_embed)
kernel.import_plugin_from_object(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin")
await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000")
await memory.save_reference(

Some files were not shown because too many files have changed in this diff Show More