mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: further improvements to checks and docs in dev_setup (#6445)
### 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. --> Added a section on documentation to the DEV_SETUP Added a check for the copyright row to pre-commit Closes #5629 ### Description <!-- Describe your changes, the overall approach, the underlying design. These notes will help understanding how your code works. Thanks! --> ### Contribution Checklist <!-- Before submitting this PR, please make sure: --> - [x] The code builds clean without any errors or warnings - [x] The PR follows the [SK Contribution Guidelines](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md) and the [pre-submission formatting script](https://github.com/microsoft/semantic-kernel/blob/main/CONTRIBUTING.md#development-scripts) raises no violations - [x] All unit tests pass, and I have added new tests where possible - [x] I didn't break anyone 😄
This commit is contained in:
@@ -155,6 +155,57 @@ It's important to note that most of this library is written with asynchronous in
|
||||
developer should always assume everything is asynchronous. One can use the function signature
|
||||
with either `async def` or `def` to understand if something is asynchronous or not.
|
||||
|
||||
### Documentation
|
||||
|
||||
Each file should have a single first line containing: # Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
We follow the [Google Docstring](https://github.com/google/styleguide/blob/gh-pages/pyguide.md#383-functions-and-methods) style guide for functions and methods.
|
||||
They are currently not checked for private functions (functions starting with '_').
|
||||
|
||||
They should contain:
|
||||
- Single line explaining what the function does, ending with a period.
|
||||
- If necessary to further explain the logic a newline follows the first line and then the explanation is given.
|
||||
- The following three sections are optional, and if used should be separated by a single empty line.
|
||||
- Arguments are then specified after a header called `Args:`, with each argument being specified in the following format:
|
||||
- `arg_name` (`arg_type`): Explanation of the argument, arg_type is optional, as long as you are consistent.
|
||||
- if a longer explanation is needed for a argument, it should be placed on the next line, indented by 4 spaces.
|
||||
- Default values do not have to be specified, they will be pulled from the definition.
|
||||
- Returns are specified after a header called `Returns:` or `Yields:`, with the return type and explanation of the return value.
|
||||
- Finally, a header for exceptions can be added, called `Raises:`, with each exception being specified in the following format:
|
||||
- `ExceptionType`: Explanation of the exception.
|
||||
- if a longer explanation is needed for a exception, it should be placed on the next line, indented by 4 spaces.
|
||||
|
||||
Putting them all together, gives you at minimum this:
|
||||
|
||||
```python
|
||||
def equal(arg1: str, arg2: str) -> bool:
|
||||
"""Compares two strings and returns True if they are the same."""
|
||||
...
|
||||
```
|
||||
Or a complete version of this:
|
||||
|
||||
```python
|
||||
def equal(arg1: str, arg2: str) -> bool:
|
||||
"""Compares two strings and returns True if they are the same.
|
||||
|
||||
Here is extra explanation of the logic involved.
|
||||
|
||||
Args:
|
||||
arg1 (str): The first string to compare.
|
||||
arg2 (str): The second string to compare.
|
||||
This string requires extra explanation.
|
||||
|
||||
Returns:
|
||||
bool: True if the strings are the same, False otherwise.
|
||||
|
||||
Raises:
|
||||
ValueError: If one of the strings is empty.
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
If in doubt, use the link above to read much more considerations of what to do and when, or use common sense.
|
||||
|
||||
## Pydantic and Serialization
|
||||
|
||||
[Pydantic Documentation](https://docs.pydantic.dev/1.10/)
|
||||
|
||||
+11
-5
@@ -127,18 +127,24 @@ target-version = "py310"
|
||||
include = ["*.py", "*.pyi", "**/pyproject.toml", "*.ipynb"]
|
||||
|
||||
[tool.ruff.lint]
|
||||
select = ["D", "E", "F", "I"]
|
||||
ignore = ["D100", "D101", "D104"]
|
||||
preview = true
|
||||
select = ["D", "E", "F", "I", "CPY", "ISC", "INP", "RSE102", "RET", "SIM", "TD", "FIX", "ERA001", "RUF"]
|
||||
ignore = ["D100", "D101", "D104", "TD003", "FIX002"]
|
||||
|
||||
[tool.ruff.lint.pydocstyle]
|
||||
convention = "google"
|
||||
|
||||
[tool.ruff.lint.per-file-ignores]
|
||||
# Ignore all directories named `tests`.
|
||||
"tests/**" = ["D"]
|
||||
"samples/**" = ["D"]
|
||||
# Ignore all directories named `tests` and `samples`.
|
||||
"tests/**" = ["D", "INP", "TD", "ERA001", "RUF"]
|
||||
"samples/**" = ["D", "INP", "ERA001", "RUF"]
|
||||
# Ignore all files that end in `_test.py`.
|
||||
"*_test.py" = ["D"]
|
||||
"*.ipynb" = ["CPY", "E501"]
|
||||
|
||||
[tool.ruff.lint.flake8-copyright]
|
||||
notice-rgx = "^# Copyright \\(c\\) Microsoft\\. All rights reserved\\."
|
||||
min-file-size = 1
|
||||
|
||||
[tool.bandit]
|
||||
targets = ["python/semantic_kernel"]
|
||||
|
||||
@@ -28,7 +28,7 @@ chat_service = AzureChatCompletion(
|
||||
kernel.add_service(chat_service)
|
||||
|
||||
## there are three ways to create the request settings in code: # noqa: E266
|
||||
## Note: the prompt_execution_settings are a dictionary with the service_id as the key and the request settings as the value. # noqa: E501
|
||||
# Note: the prompt_execution_settings are a dictionary with the service_id as the key and the request settings as the value. # noqa: E501
|
||||
|
||||
## 1. create the request settings from the base class: # noqa: E266
|
||||
# from semantic_kernel.connectors.ai.chat_completion_client_base import PromptExecutionSettings
|
||||
|
||||
@@ -42,7 +42,7 @@ async def input_output_filter(
|
||||
if context.result:
|
||||
logger.info(f'Usage: {context.result.metadata.get("usage")}')
|
||||
context.arguments["chat_history"].add_message(context.result.value[0])
|
||||
print(f"Mosscap:> {str(context.result)}")
|
||||
print(f"Mosscap:> {context.result!s}")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
@@ -78,8 +80,7 @@ def setup(use_azure: bool = False, plugin_name: str = "GroundingPlugin"):
|
||||
def get_summary_text():
|
||||
summary_text = """My father, a respected resident of Milan, was a close friend of a merchant named Beaufort who, after a series of misfortunes, moved to Zurich in poverty. My father was upset by his friend's troubles and sought him out, finding him in a mean street. Beaufort had saved a small sum of money, but it was not enough to support him and his daughter, Mary. Mary procured work to eek out a living, but after ten months her father died, leaving her a beggar. My father came to her aid and two years later they married when they visited Rome.""" # noqa: E501
|
||||
|
||||
summary_text = summary_text.replace("\n", " ").replace(" ", " ")
|
||||
return summary_text
|
||||
return summary_text.replace("\n", " ").replace(" ", " ")
|
||||
|
||||
|
||||
async def run_entity_extraction(kernel: Kernel, plugin_name: str, summary_text: str):
|
||||
@@ -87,38 +88,34 @@ async def run_entity_extraction(kernel: Kernel, plugin_name: str, summary_text:
|
||||
topic="people and places", example_entities="John, Jane, mother, brother, Paris, Rome", input=summary_text
|
||||
)
|
||||
|
||||
extraction_result = await kernel.invoke(
|
||||
return await kernel.invoke(
|
||||
plugin_name=plugin_name, function_name="ExtractEntities", arguments=arguments
|
||||
)
|
||||
|
||||
return extraction_result
|
||||
|
||||
|
||||
async def run_reference_check(kernel: Kernel, plugin_name: str, extraction_result):
|
||||
grounding_result = await kernel.invoke(
|
||||
return 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(kernel: Kernel, plugin_name: str, summary_text, grounding_result):
|
||||
excision_result = await kernel.invoke(
|
||||
return 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):
|
||||
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"\n{ '-' * 80 }\n")
|
||||
print(
|
||||
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).
|
||||
|
||||
@@ -133,10 +130,10 @@ What is an 'entity' in this context? In its simplest form, it's a named object s
|
||||
|
||||
print(f"\nThe grounding text: \n{Colors.CGREY.value}{get_grounding_text()}{Colors.CEND.value}")
|
||||
|
||||
print(f"\n{ '-'*80 }\n")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
summary_text = get_summary_text()
|
||||
print(f"Summary text: \n{Colors.CBLUE.value}{summary_text}{Colors.CEND.value}")
|
||||
print(f"\n{ '-'*80 }\n")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
print(
|
||||
f"""{Colors.CGREEN.value}Some things to note:
|
||||
|
||||
@@ -154,25 +151,25 @@ The grounding plugin has three stages:
|
||||
|
||||
Now, let us start calling individual semantic functions.{Colors.CEND.value}"""
|
||||
)
|
||||
print(f"\n{ '-'*80 }\n")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
print(
|
||||
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 = 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"Extraction result: \n{Colors.CBLUE.value}{extraction_result!s}{Colors.CEND.value}")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
print(
|
||||
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 = 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"Grounding result: \n{Colors.CBLUE.value}{grounding_result!s}{Colors.CEND.value}")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
print(
|
||||
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 = 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"The final summary text: \n{Colors.CBLUE.value}{excision_result!s}{Colors.CEND.value}")
|
||||
print(f"\n{ '-' * 80 }\n")
|
||||
print(f"{Colors.CBOLD.value}Finished!{Colors.CEND.value}")
|
||||
|
||||
|
||||
|
||||
@@ -50,14 +50,12 @@ async def setup_chat_with_memory(
|
||||
execution_settings={service_id: kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)},
|
||||
)
|
||||
|
||||
chat_func = kernel.add_function(
|
||||
return kernel.add_function(
|
||||
function_name="chat_with_memory",
|
||||
plugin_name="TextMemoryPlugin",
|
||||
prompt_template_config=prompt_template_config,
|
||||
)
|
||||
|
||||
return chat_func
|
||||
|
||||
|
||||
async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool:
|
||||
try:
|
||||
|
||||
@@ -50,14 +50,12 @@ async def setup_chat_with_memory(
|
||||
execution_settings={service_id: kernel.get_prompt_execution_settings_from_service_id(service_id=service_id)},
|
||||
)
|
||||
|
||||
chat_func = kernel.add_function(
|
||||
return kernel.add_function(
|
||||
function_name="chat_with_memory",
|
||||
plugin_name="TextMemoryPlugin",
|
||||
prompt_template_config=prompt_template_config,
|
||||
)
|
||||
|
||||
return chat_func
|
||||
|
||||
|
||||
async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool:
|
||||
try:
|
||||
|
||||
@@ -23,20 +23,19 @@ class WeatherPlugin:
|
||||
def get_weather_for_city(self, city: Annotated[str, "The input city"]) -> Annotated[str, "The output is a string"]:
|
||||
if city == "Boston":
|
||||
return "61 and rainy"
|
||||
elif city == "London":
|
||||
if city == "London":
|
||||
return "55 and cloudy"
|
||||
elif city == "Miami":
|
||||
if city == "Miami":
|
||||
return "80 and sunny"
|
||||
elif city == "Paris":
|
||||
if city == "Paris":
|
||||
return "60 and rainy"
|
||||
elif city == "Tokyo":
|
||||
if city == "Tokyo":
|
||||
return "50 and sunny"
|
||||
elif city == "Sydney":
|
||||
if city == "Sydney":
|
||||
return "75 and sunny"
|
||||
elif city == "Tel Aviv":
|
||||
if city == "Tel Aviv":
|
||||
return "80 and sunny"
|
||||
else:
|
||||
return "31 and snowing"
|
||||
return "31 and snowing"
|
||||
|
||||
|
||||
async def main():
|
||||
|
||||
@@ -59,7 +59,7 @@ class OpenAIAuthenticationProvider:
|
||||
) -> dict[str, str] | None:
|
||||
"""An example of how to authenticate a request as part of an auth callback."""
|
||||
if openai_auth_config.type == OpenAIAuthenticationType.NoneType:
|
||||
return
|
||||
return None
|
||||
|
||||
scheme = ""
|
||||
credential = ""
|
||||
|
||||
@@ -24,7 +24,7 @@ async def main():
|
||||
|
||||
result = await kernel.invoke(plugin["productsUsingGET"], **query_params)
|
||||
|
||||
print(f"Function execution result: {str(result)}")
|
||||
print(f"Function execution result: {result!s}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
import semantic_kernel as sk
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from aiohttp import web
|
||||
|
||||
"""Server"""
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
|
||||
@@ -11,11 +11,7 @@ from semantic_kernel.prompt_template import PromptTemplateConfig
|
||||
async def main():
|
||||
kernel = Kernel()
|
||||
service_id = "chat-gpt"
|
||||
kernel.add_service(
|
||||
AzureChatCompletion(
|
||||
service_id=service_id,
|
||||
),
|
||||
)
|
||||
kernel.add_service(AzureChatCompletion(service_id=service_id))
|
||||
connector = BingConnector()
|
||||
web_plugin = kernel.add_plugin(WebSearchEnginePlugin(connector), "WebSearch")
|
||||
|
||||
|
||||
@@ -10,8 +10,7 @@ async def text_completion_example_complete(kernel: Kernel, user_mssg, settings):
|
||||
"""Complete a text prompt using the Google PaLM model and print the results."""
|
||||
palm_text_completion = GooglePalmTextCompletion("models/text-bison-001")
|
||||
kernel.add_service(palm_text_completion)
|
||||
answer = await palm_text_completion.get_text_contents(user_mssg, settings)
|
||||
return answer
|
||||
return await palm_text_completion.get_text_contents(user_mssg, settings)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
|
||||
@@ -38,7 +38,7 @@ class BookingSampleSettings(BaseSettings):
|
||||
|
||||
@classmethod
|
||||
def create(cls, **kwargs):
|
||||
if "env_file_path" in kwargs and kwargs["env_file_path"]:
|
||||
if kwargs.get("env_file_path"):
|
||||
cls.Config.env_file = kwargs["env_file_path"]
|
||||
else:
|
||||
cls.Config.env_file = None
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel import Kernel, kernel\n",
|
||||
"from semantic_kernel import Kernel\n",
|
||||
"\n",
|
||||
"kernel = Kernel()"
|
||||
]
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
"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",
|
||||
"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",
|
||||
"\"\"\""
|
||||
]
|
||||
},
|
||||
@@ -194,9 +194,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.functions import KernelArguments\n",
|
||||
"\n",
|
||||
"summary = await kernel.invoke(summarize, KernelArguments(input=input_text))\n",
|
||||
"summary = await kernel.invoke(summarize, input=input_text)\n",
|
||||
"\n",
|
||||
"print(summary)"
|
||||
]
|
||||
@@ -261,6 +259,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (\n",
|
||||
" OpenAIChatPromptExecutionSettings,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"prompt = \"\"\"\n",
|
||||
"{{$input}}\n",
|
||||
"\n",
|
||||
@@ -278,10 +280,6 @@
|
||||
" does not conflict with the First or Second Law.\n",
|
||||
"\"\"\"\n",
|
||||
"\n",
|
||||
"from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (\n",
|
||||
" OpenAIChatPromptExecutionSettings,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" execution_settings = OpenAIChatPromptExecutionSettings(\n",
|
||||
" service_id=service_id,\n",
|
||||
@@ -313,7 +311,7 @@
|
||||
" prompt_template_config=prompt_template_config,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"summary = await kernel.invoke(tldr_function, KernelArguments(input=text))\n",
|
||||
"summary = await kernel.invoke(tldr_function, input=text)\n",
|
||||
"\n",
|
||||
"print(f\"Output: {summary}\")"
|
||||
]
|
||||
|
||||
@@ -114,8 +114,8 @@
|
||||
"from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (\n",
|
||||
" OpenAIChatPromptExecutionSettings,\n",
|
||||
")\n",
|
||||
"from semantic_kernel.prompt_template.input_variable import InputVariable\n",
|
||||
"from semantic_kernel.prompt_template import PromptTemplateConfig\n",
|
||||
"from semantic_kernel.prompt_template.input_variable import InputVariable\n",
|
||||
"\n",
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" execution_settings = OpenAIChatPromptExecutionSettings(\n",
|
||||
|
||||
@@ -92,9 +92,9 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import semantic_kernel as sk\n",
|
||||
"import semantic_kernel.connectors.ai.open_ai as sk_oai # noqa: F401\n",
|
||||
"from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt\n",
|
||||
"import semantic_kernel.connectors.ai.open_ai as sk_oai\n",
|
||||
"from semantic_kernel.core_plugins.text_plugin import TextPlugin\n",
|
||||
"from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt\n",
|
||||
"\n",
|
||||
"kernel = sk.Kernel()\n",
|
||||
"service_id = \"default\"\n",
|
||||
@@ -271,7 +271,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import semantic_kernel as sk\n",
|
||||
"import semantic_kernel.connectors.ai.open_ai as sk_oai # noqa: F401\n",
|
||||
"import semantic_kernel.connectors.ai.open_ai as sk_oai\n",
|
||||
"\n",
|
||||
"kernel = sk.Kernel()\n",
|
||||
"service_id = \"default\"\n",
|
||||
@@ -303,6 +303,7 @@
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from typing import Annotated\n",
|
||||
"\n",
|
||||
"from semantic_kernel.functions.kernel_function_decorator import kernel_function\n",
|
||||
"\n",
|
||||
"\n",
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,18 +23,6 @@
|
||||
"!python -m pip install semantic-kernel[hugging_face]==1.0.3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"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,
|
||||
|
||||
@@ -305,7 +305,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random, sys\n",
|
||||
"import sys\n",
|
||||
"\n",
|
||||
"from semantic_kernel.functions import kernel_function\n",
|
||||
"\n",
|
||||
@@ -491,7 +491,6 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import random\n",
|
||||
"\n",
|
||||
"from semantic_kernel.functions import kernel_function\n",
|
||||
"\n",
|
||||
|
||||
@@ -1,418 +1,418 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "68e1c158",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# Multiple Results\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "fb81bacd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this notebook we show how you can in a single request, have the LLM model return multiple results per prompt. This is useful for running experiments where you want to evaluate the robustness of your prompt and the parameters of your config against a particular large language model.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a77bdf89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!python -m pip install semantic-kernel==1.0.3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f4bfee4",
|
||||
"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": "508ad44f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.contents import ChatHistory # noqa: F401\n",
|
||||
"\n",
|
||||
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
|
||||
" from semantic_kernel.connectors.ai.open_ai import ( # noqa: F401\n",
|
||||
" AzureChatCompletion,\n",
|
||||
" AzureChatPromptExecutionSettings,\n",
|
||||
" AzureTextCompletion,\n",
|
||||
" OpenAIChatCompletion,\n",
|
||||
" OpenAIChatPromptExecutionSettings,\n",
|
||||
" OpenAITextCompletion,\n",
|
||||
" OpenAITextPromptExecutionSettings,\n",
|
||||
" )\n",
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion # noqa: F401"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "d8ddffc1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we will set up the text and chat services we will be submitting prompts to.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8f8dcbc6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel import Kernel\n",
|
||||
"\n",
|
||||
"kernel = Kernel()\n",
|
||||
"\n",
|
||||
"# Configure Azure LLM service\n",
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" azure_text_service = AzureTextCompletion(\n",
|
||||
" service_id=\"aoai_text\"\n",
|
||||
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct)\n",
|
||||
" azure_chat_service = AzureChatCompletion(\n",
|
||||
" service_id=\"aoai_chat\"\n",
|
||||
" ) # set the deployment name to the value of your chat model\n",
|
||||
"\n",
|
||||
"# Configure OpenAI service\n",
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" oai_text_service = OpenAITextCompletion(service_id=\"oai_text\", ai_model_id=\"gpt-3.5-turbo-instruct\")\n",
|
||||
" oai_chat_service = OpenAIChatCompletion(service_id=\"oai_chat\", ai_model_id=\"gpt-3.5-turbo\")\n",
|
||||
"\n",
|
||||
"# Configure Hugging Face service\n",
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" hf_text_service = HuggingFaceTextCompletion(service_id=\"hf_text\", ai_model_id=\"distilgpt2\", task=\"text-generation\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "50561d82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we'll set up the completion request settings for text completion services.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "628c843e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"oai_text_prompt_execution_settings = OpenAITextPromptExecutionSettings(\n",
|
||||
" service=\"oai_text\",\n",
|
||||
" extension_data={\n",
|
||||
" \"max_tokens\": 80,\n",
|
||||
" \"temperature\": 0.7,\n",
|
||||
" \"top_p\": 1,\n",
|
||||
" \"frequency_penalty\": 0.5,\n",
|
||||
" \"presence_penalty\": 0.5,\n",
|
||||
" \"number_of_responses\": 3,\n",
|
||||
" },\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "857a9c89",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Open AI Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e2979db8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" prompt = \"What is the purpose of a rubber duck?\"\n",
|
||||
"\n",
|
||||
" results = await oai_text_service.get_text_contents(prompt=prompt, settings=oai_text_prompt_execution_settings)\n",
|
||||
" i = 1\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i}: {result}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "4288d09f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Azure Open AI Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5319f14d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
|
||||
"\n",
|
||||
" results = await azure_text_service.get_text_contents(prompt=prompt, settings=oai_text_prompt_execution_settings)\n",
|
||||
" i = 1\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i}: {result}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "eb548f9c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Hugging Face Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4a148709",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face.hf_prompt_execution_settings import (\n",
|
||||
" HuggingFacePromptExecutionSettings,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" hf_prompt_execution_settings = HuggingFacePromptExecutionSettings(\n",
|
||||
" service_id=\"hf_text\", extension_data={\"max_new_tokens\": 80, \"temperature\": 0.7, \"top_p\": 1}\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9525e4f3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" prompt = \"The purpose of a rubber duck is\"\n",
|
||||
"\n",
|
||||
" results = await hf_text_service.get_text_contents(\n",
|
||||
" prompt=prompt, prompt_execution_settings=hf_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" print(\"\".join(results))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "da632e12",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here, we're setting up the settings for Chat completions.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e5f11e46",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"oai_chat_prompt_execution_settings = OpenAIChatPromptExecutionSettings(\n",
|
||||
" service_id=\"oai_chat\",\n",
|
||||
" max_tokens=80,\n",
|
||||
" temperature=0.7,\n",
|
||||
" top_p=1,\n",
|
||||
" frequency_penalty=0.5,\n",
|
||||
" presence_penalty=0.5,\n",
|
||||
" number_of_responses=3,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "d6bf238e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple OpenAI Chat Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dabc6a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(\n",
|
||||
" \"It's a beautiful day outside, birds are singing, flowers are blooming. On days like these, kids like you...\"\n",
|
||||
" )\n",
|
||||
" results = await oai_chat_service.get_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=oai_chat_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" i = 0\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i+1}: {str(result)}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "cdb8f740",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Azure OpenAI Chat Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "66ba4767",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"az_oai_prompt_execution_settings = AzureChatPromptExecutionSettings(\n",
|
||||
" service_id=\"aoai_chat\",\n",
|
||||
" max_tokens=80,\n",
|
||||
" temperature=0.7,\n",
|
||||
" top_p=1,\n",
|
||||
" frequency_penalty=0.5,\n",
|
||||
" presence_penalty=0.5,\n",
|
||||
" number_of_responses=3,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b74a64a9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" content = (\n",
|
||||
" \"Tomorrow is going to be a great day, I can feel it. I'm going to wake up early, go for a run, and then...\"\n",
|
||||
" )\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(content)\n",
|
||||
" results = await azure_chat_service.get_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=az_oai_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" i = 0\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i+1}: {str(result)}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "98c8191d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Streaming Multiple Results\n",
|
||||
"\n",
|
||||
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "26a37702",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" import os\n",
|
||||
" import time\n",
|
||||
"\n",
|
||||
" from IPython.display import clear_output\n",
|
||||
"\n",
|
||||
" # Determine the clear command based on OS\n",
|
||||
" clear_command = \"cls\" if os.name == \"nt\" else \"clear\"\n",
|
||||
"\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(\"what is the purpose of a rubber duck?\")\n",
|
||||
"\n",
|
||||
" stream = oai_chat_service.get_streaming_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=oai_chat_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" number_of_responses = oai_chat_prompt_execution_settings.number_of_responses\n",
|
||||
" texts = [\"\"] * number_of_responses\n",
|
||||
"\n",
|
||||
" last_clear_time = time.time()\n",
|
||||
" clear_interval = 0.5 # seconds\n",
|
||||
"\n",
|
||||
" # Note: there are some quirks with displaying the output, which sometimes flashes and disappears.\n",
|
||||
" # This could be influenced by a few factors specific to Jupyter notebooks and asynchronous processing.\n",
|
||||
" # The following code attempts to buffer the results to avoid the output flashing on/off the screen.\n",
|
||||
"\n",
|
||||
" async for results in stream:\n",
|
||||
" current_time = time.time()\n",
|
||||
"\n",
|
||||
" # Update texts with new results\n",
|
||||
" for idx, result in enumerate(results):\n",
|
||||
" if idx < number_of_responses:\n",
|
||||
" texts[idx] += str(result)\n",
|
||||
"\n",
|
||||
" # Clear and display output at intervals\n",
|
||||
" if current_time - last_clear_time > clear_interval:\n",
|
||||
" clear_output(wait=True)\n",
|
||||
" for idx, text in enumerate(texts):\n",
|
||||
" print(f\"Result {idx + 1}: {text}\")\n",
|
||||
" last_clear_time = current_time\n",
|
||||
"\n",
|
||||
" print(\"----------------------------------------\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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": [
|
||||
"# Multiple Results\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "fb81bacd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"In this notebook we show how you can in a single request, have the LLM model return multiple results per prompt. This is useful for running experiments where you want to evaluate the robustness of your prompt and the parameters of your config against a particular large language model.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "a77bdf89",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!python -m pip install semantic-kernel==1.0.3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "3f4bfee4",
|
||||
"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": "508ad44f",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.contents import ChatHistory\n",
|
||||
"\n",
|
||||
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
|
||||
" from semantic_kernel.connectors.ai.open_ai import (\n",
|
||||
" AzureChatCompletion,\n",
|
||||
" AzureChatPromptExecutionSettings,\n",
|
||||
" AzureTextCompletion,\n",
|
||||
" OpenAIChatCompletion,\n",
|
||||
" OpenAIChatPromptExecutionSettings,\n",
|
||||
" OpenAITextCompletion,\n",
|
||||
" OpenAITextPromptExecutionSettings,\n",
|
||||
" )\n",
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face import HuggingFaceTextCompletion"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "d8ddffc1",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"First, we will set up the text and chat services we will be submitting prompts to.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "8f8dcbc6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel import Kernel\n",
|
||||
"\n",
|
||||
"kernel = Kernel()\n",
|
||||
"\n",
|
||||
"# Configure Azure LLM service\n",
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" azure_text_service = AzureTextCompletion(\n",
|
||||
" service_id=\"aoai_text\"\n",
|
||||
" ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct)\n",
|
||||
" azure_chat_service = AzureChatCompletion(\n",
|
||||
" service_id=\"aoai_chat\"\n",
|
||||
" ) # set the deployment name to the value of your chat model\n",
|
||||
"\n",
|
||||
"# Configure OpenAI service\n",
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" oai_text_service = OpenAITextCompletion(service_id=\"oai_text\", ai_model_id=\"gpt-3.5-turbo-instruct\")\n",
|
||||
" oai_chat_service = OpenAIChatCompletion(service_id=\"oai_chat\", ai_model_id=\"gpt-3.5-turbo\")\n",
|
||||
"\n",
|
||||
"# Configure Hugging Face service\n",
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" hf_text_service = HuggingFaceTextCompletion(service_id=\"hf_text\", ai_model_id=\"distilgpt2\", task=\"text-generation\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "50561d82",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Next, we'll set up the completion request settings for text completion services.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "628c843e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"oai_text_prompt_execution_settings = OpenAITextPromptExecutionSettings(\n",
|
||||
" service=\"oai_text\",\n",
|
||||
" extension_data={\n",
|
||||
" \"max_tokens\": 80,\n",
|
||||
" \"temperature\": 0.7,\n",
|
||||
" \"top_p\": 1,\n",
|
||||
" \"frequency_penalty\": 0.5,\n",
|
||||
" \"presence_penalty\": 0.5,\n",
|
||||
" \"number_of_responses\": 3,\n",
|
||||
" },\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "857a9c89",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Open AI Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e2979db8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" prompt = \"What is the purpose of a rubber duck?\"\n",
|
||||
"\n",
|
||||
" results = await oai_text_service.get_text_contents(prompt=prompt, settings=oai_text_prompt_execution_settings)\n",
|
||||
" i = 1\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i}: {result}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "4288d09f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Azure Open AI Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "5319f14d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n",
|
||||
"\n",
|
||||
" results = await azure_text_service.get_text_contents(prompt=prompt, settings=oai_text_prompt_execution_settings)\n",
|
||||
" i = 1\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i}: {result}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "eb548f9c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Hugging Face Text Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "4a148709",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face.hf_prompt_execution_settings import (\n",
|
||||
" HuggingFacePromptExecutionSettings,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" hf_prompt_execution_settings = HuggingFacePromptExecutionSettings(\n",
|
||||
" service_id=\"hf_text\", extension_data={\"max_new_tokens\": 80, \"temperature\": 0.7, \"top_p\": 1}\n",
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9525e4f3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" prompt = \"The purpose of a rubber duck is\"\n",
|
||||
"\n",
|
||||
" results = await hf_text_service.get_text_contents(\n",
|
||||
" prompt=prompt, prompt_execution_settings=hf_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" print(\"\".join(results))"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "da632e12",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Here, we're setting up the settings for Chat completions.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e5f11e46",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"oai_chat_prompt_execution_settings = OpenAIChatPromptExecutionSettings(\n",
|
||||
" service_id=\"oai_chat\",\n",
|
||||
" max_tokens=80,\n",
|
||||
" temperature=0.7,\n",
|
||||
" top_p=1,\n",
|
||||
" frequency_penalty=0.5,\n",
|
||||
" presence_penalty=0.5,\n",
|
||||
" number_of_responses=3,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "d6bf238e",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple OpenAI Chat Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "dabc6a4c",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(\n",
|
||||
" \"It's a beautiful day outside, birds are singing, flowers are blooming. On days like these, kids like you...\"\n",
|
||||
" )\n",
|
||||
" results = await oai_chat_service.get_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=oai_chat_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" i = 0\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i + 1}: {result!s}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "cdb8f740",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Multiple Azure OpenAI Chat Completions\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "66ba4767",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"az_oai_prompt_execution_settings = AzureChatPromptExecutionSettings(\n",
|
||||
" service_id=\"aoai_chat\",\n",
|
||||
" max_tokens=80,\n",
|
||||
" temperature=0.7,\n",
|
||||
" top_p=1,\n",
|
||||
" frequency_penalty=0.5,\n",
|
||||
" presence_penalty=0.5,\n",
|
||||
" number_of_responses=3,\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b74a64a9",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.AzureOpenAI:\n",
|
||||
" content = (\n",
|
||||
" \"Tomorrow is going to be a great day, I can feel it. I'm going to wake up early, go for a run, and then...\"\n",
|
||||
" )\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(content)\n",
|
||||
" results = await azure_chat_service.get_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=az_oai_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" i = 0\n",
|
||||
" for result in results:\n",
|
||||
" print(f\"Result {i + 1}: {result!s}\")\n",
|
||||
" i += 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"attachments": {},
|
||||
"cell_type": "markdown",
|
||||
"id": "98c8191d",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Streaming Multiple Results\n",
|
||||
"\n",
|
||||
"Here is an example pattern if you want to stream your multiple results. Note that this is not supported for Hugging Face text completions at this time.\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "26a37702",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"if selectedService == Service.OpenAI:\n",
|
||||
" import os\n",
|
||||
" import time\n",
|
||||
"\n",
|
||||
" from IPython.display import clear_output\n",
|
||||
"\n",
|
||||
" # Determine the clear command based on OS\n",
|
||||
" clear_command = \"cls\" if os.name == \"nt\" else \"clear\"\n",
|
||||
"\n",
|
||||
" chat = ChatHistory()\n",
|
||||
" chat.add_user_message(\"what is the purpose of a rubber duck?\")\n",
|
||||
"\n",
|
||||
" stream = oai_chat_service.get_streaming_chat_message_contents(\n",
|
||||
" chat_history=chat, settings=oai_chat_prompt_execution_settings\n",
|
||||
" )\n",
|
||||
" number_of_responses = oai_chat_prompt_execution_settings.number_of_responses\n",
|
||||
" texts = [\"\"] * number_of_responses\n",
|
||||
"\n",
|
||||
" last_clear_time = time.time()\n",
|
||||
" clear_interval = 0.5 # seconds\n",
|
||||
"\n",
|
||||
" # Note: there are some quirks with displaying the output, which sometimes flashes and disappears.\n",
|
||||
" # This could be influenced by a few factors specific to Jupyter notebooks and asynchronous processing.\n",
|
||||
" # The following code attempts to buffer the results to avoid the output flashing on/off the screen.\n",
|
||||
"\n",
|
||||
" async for results in stream:\n",
|
||||
" current_time = time.time()\n",
|
||||
"\n",
|
||||
" # Update texts with new results\n",
|
||||
" for idx, result in enumerate(results):\n",
|
||||
" if idx < number_of_responses:\n",
|
||||
" texts[idx] += str(result)\n",
|
||||
"\n",
|
||||
" # Clear and display output at intervals\n",
|
||||
" if current_time - last_clear_time > clear_interval:\n",
|
||||
" clear_output(wait=True)\n",
|
||||
" for idx, text in enumerate(texts):\n",
|
||||
" print(f\"Result {idx + 1}: {text}\")\n",
|
||||
" last_clear_time = current_time\n",
|
||||
"\n",
|
||||
" print(\"----------------------------------------\")"
|
||||
]
|
||||
}
|
||||
],
|
||||
"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
|
||||
}
|
||||
|
||||
@@ -41,10 +41,10 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.contents import ChatHistory # noqa: F401\n",
|
||||
"from semantic_kernel.contents import ChatHistory\n",
|
||||
"\n",
|
||||
"if selectedService == Service.OpenAI or selectedService == Service.AzureOpenAI:\n",
|
||||
" from semantic_kernel.connectors.ai.open_ai import ( # noqa: F401\n",
|
||||
" from semantic_kernel.connectors.ai.open_ai import (\n",
|
||||
" AzureChatCompletion,\n",
|
||||
" AzureChatPromptExecutionSettings,\n",
|
||||
" AzureTextCompletion,\n",
|
||||
@@ -54,7 +54,7 @@
|
||||
" OpenAITextPromptExecutionSettings,\n",
|
||||
" )\n",
|
||||
"if selectedService == Service.HuggingFace:\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face import ( # noqa: F401\n",
|
||||
" from semantic_kernel.connectors.ai.hugging_face import (\n",
|
||||
" HuggingFacePromptExecutionSettings,\n",
|
||||
" HuggingFaceTextCompletion,\n",
|
||||
" )"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
"""This module defines an enumeration representing different services.
|
||||
"""
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
"""This module defines an enumeration representing different services."""
|
||||
|
||||
from enum import Enum
|
||||
|
||||
|
||||
+11
-26
@@ -114,9 +114,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"!pip install semantic-kernel==1.0.3\n",
|
||||
"!pip install weaviate-client\n",
|
||||
"!pip install python-dotenv"
|
||||
"!pip install -U semantic-kernel[weaviate]==1.0.3"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -129,13 +127,6 @@
|
||||
"- on Windows, you may need to run Docker Desktop as administrator\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"metadata": {},
|
||||
@@ -153,7 +144,7 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.connectors.memory.weaviate import weaviate_memory_store\n",
|
||||
"from semantic_kernel.connectors.memory.weaviate import WeaviateMemoryStore\n",
|
||||
"\n",
|
||||
"# Note the Weaviate Config values need to be either configured as environment variables\n",
|
||||
"# or in the .env file, as a back up. When creating the instance of the `weaviate_memory_store`\n",
|
||||
@@ -164,7 +155,7 @@
|
||||
"# WEAVIATE_API_KEY=\"\"\n",
|
||||
"# WEAVIATE_USE_EMBED=True|False\n",
|
||||
"\n",
|
||||
"store = weaviate_memory_store.WeaviateMemoryStore()\n",
|
||||
"store = WeaviateMemoryStore()\n",
|
||||
"store.client.schema.delete_all()"
|
||||
]
|
||||
},
|
||||
@@ -193,11 +184,11 @@
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from semantic_kernel.kernel import Kernel\n",
|
||||
"from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding\n",
|
||||
"from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin\n",
|
||||
"from semantic_kernel.kernel import Kernel\n",
|
||||
"from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n",
|
||||
"from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore\n",
|
||||
"from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin\n",
|
||||
"\n",
|
||||
"kernel = Kernel()\n",
|
||||
"\n",
|
||||
@@ -323,13 +314,11 @@
|
||||
" },\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" chat_func = kernel.add_function(\n",
|
||||
" return kernel.add_function(\n",
|
||||
" function_name=\"chat_with_memory\",\n",
|
||||
" plugin_name=\"TextMemoryPlugin\",\n",
|
||||
" prompt_template_config=prompt_template_config,\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" return chat_func"
|
||||
" )"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -438,8 +427,7 @@
|
||||
"COLLECTION = \"SKGitHub\"\n",
|
||||
"\n",
|
||||
"print(\"Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.\")\n",
|
||||
"i = 0\n",
|
||||
"for entry, value in github_files.items():\n",
|
||||
"for index, (entry, value) in enumerate(github_files.items()):\n",
|
||||
" await memory.save_reference(\n",
|
||||
" collection=COLLECTION,\n",
|
||||
" description=value,\n",
|
||||
@@ -447,8 +435,7 @@
|
||||
" external_id=entry,\n",
|
||||
" external_source_name=\"GitHub\",\n",
|
||||
" )\n",
|
||||
" i += 1\n",
|
||||
" print(\" URL {} saved\".format(i))"
|
||||
" print(\" URL {} saved\".format(index))"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -469,10 +456,8 @@
|
||||
"\n",
|
||||
"memories = await memory.search(COLLECTION, ask, limit=5, min_relevance_score=0.77)\n",
|
||||
"\n",
|
||||
"i = 0\n",
|
||||
"for memory in memories:\n",
|
||||
" i += 1\n",
|
||||
" print(f\"Result {i}:\")\n",
|
||||
"for index, memory in enumerate(memories):\n",
|
||||
" print(f\"Result {index}:\")\n",
|
||||
" print(\" URL: : \" + memory.id)\n",
|
||||
" print(\" Title : \" + memory.description)\n",
|
||||
" print(\" Relevance: \" + str(memory.relevance))\n",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from dotenv import dotenv_values
|
||||
from promptflow import PFClient
|
||||
from promptflow.entities import AzureOpenAIConnection
|
||||
|
||||
@@ -22,7 +22,7 @@ async def main():
|
||||
service_id = "default"
|
||||
prompt_template_config = PromptTemplateConfig(
|
||||
template=ConversationSummaryPlugin._summarize_conversation_prompt_template,
|
||||
description="Given a section of a conversation transcript, summarize the part of" " the conversation.",
|
||||
description="Given a section of a conversation transcript, summarize the part of the conversation.",
|
||||
execution_settings=PromptExecutionSettings(
|
||||
service_id=service_id, max_tokens=ConversationSummaryPlugin._max_tokens, temperature=0.1, top_p=0.5
|
||||
),
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from promptflow import PFClient
|
||||
|
||||
pf_client = PFClient()
|
||||
|
||||
+4
-7
@@ -1,16 +1,13 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from plugins.MathPlugin.Math import Math as Math # ignore F841
|
||||
from promptflow import tool
|
||||
from promptflow.connections import (
|
||||
AzureOpenAIConnection,
|
||||
)
|
||||
from promptflow.connections import AzureOpenAIConnection
|
||||
|
||||
import semantic_kernel as sk
|
||||
from semantic_kernel.connectors.ai.open_ai import (
|
||||
AzureChatCompletion,
|
||||
AzureTextCompletion,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureTextCompletion
|
||||
from semantic_kernel.planning.sequential_planner import SequentialPlanner
|
||||
|
||||
|
||||
|
||||
+3
-4
@@ -1,10 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import math
|
||||
|
||||
from semantic_kernel.orchestration.sk_context import SKContext
|
||||
from semantic_kernel.skill_definition import (
|
||||
sk_function,
|
||||
sk_function_context_parameter,
|
||||
)
|
||||
from semantic_kernel.skill_definition import sk_function, sk_function_context_parameter
|
||||
|
||||
|
||||
class Math:
|
||||
|
||||
@@ -35,7 +35,7 @@ async def main():
|
||||
|
||||
prompt_template_config = PromptTemplateConfig(
|
||||
template=template,
|
||||
description="Given a section of a conversation transcript, summarize the part of" " the conversation.",
|
||||
description="Given a section of a conversation transcript, summarize the part of the conversation.",
|
||||
execution_settings=execution_settings,
|
||||
InputVariables=[
|
||||
InputVariable(name="input", description="The user input", is_required=True),
|
||||
|
||||
@@ -49,7 +49,6 @@ class ChatCompletionClientBase(AIServiceClientBase, ABC):
|
||||
settings (PromptExecutionSettings): Settings for the request.
|
||||
kwargs (Dict[str, Any]): The optional arguments.
|
||||
|
||||
|
||||
Yields:
|
||||
A stream representing the response(s) from the LLM.
|
||||
"""
|
||||
|
||||
@@ -185,7 +185,6 @@ class RequiredFunction(FunctionCallBehavior):
|
||||
if not self.enable_kernel_functions:
|
||||
return
|
||||
# since using this always calls this single function, we do not want to allow repeated calls
|
||||
# TODO: reevaluate when other models support function calling then OpenAI.
|
||||
if self.max_auto_invoke_attempts > 1:
|
||||
self.max_auto_invoke_attempts = 1
|
||||
update_settings_callback(
|
||||
|
||||
@@ -15,9 +15,9 @@ from semantic_kernel.connectors.ai.google_palm.services.gp_text_embedding import
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"GooglePalmTextCompletion",
|
||||
"GooglePalmChatCompletion",
|
||||
"GooglePalmTextEmbedding",
|
||||
"GooglePalmChatPromptExecutionSettings",
|
||||
"GooglePalmTextCompletion",
|
||||
"GooglePalmTextEmbedding",
|
||||
"GooglePalmTextPromptExecutionSettings",
|
||||
]
|
||||
|
||||
@@ -8,7 +8,7 @@ from pydantic import Field, model_validator
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.exceptions import ServiceInvalidExecutionSettingsError
|
||||
|
||||
# TODO: replace back with google types once pydantic issue is fixed.
|
||||
# TODO (eavanvalkenburg): replace back with google types once pydantic issue is fixed.
|
||||
MessagesOptions = list[dict[str, Any]]
|
||||
|
||||
MessagePromptOption = Union[str, dict[str, Any]]
|
||||
@@ -41,8 +41,5 @@ class GooglePalmChatPromptExecutionSettings(GooglePalmPromptExecutionSettings):
|
||||
@model_validator(mode="after")
|
||||
def validate_input(self):
|
||||
"""Validate input."""
|
||||
if self.prompt is not None:
|
||||
if self.messages or self.context or self.examples:
|
||||
raise ServiceInvalidExecutionSettingsError(
|
||||
"Prompt cannot be used without messages, context or examples"
|
||||
)
|
||||
if self.prompt is not None and (self.messages or self.context or self.examples):
|
||||
raise ServiceInvalidExecutionSettingsError("Prompt cannot be used without messages, context or examples")
|
||||
|
||||
@@ -121,7 +121,7 @@ class GooglePalmChatCompletion(ChatCompletionClientBase, TextCompletionClientBas
|
||||
inner_content=response,
|
||||
ai_model_id=self.ai_model_id,
|
||||
metadata=metadata,
|
||||
role=int_to_role[int(candidate.get("author"))], # TODO: why is author coming back as '1'?
|
||||
role=int_to_role[int(candidate.get("author"))], # TODO (moonbox3): why is author coming back as '1'?
|
||||
content=candidate.get("content"),
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ from semantic_kernel.connectors.ai.hugging_face.services.hf_text_embedding impor
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"HuggingFacePromptExecutionSettings",
|
||||
"HuggingFaceTextCompletion",
|
||||
"HuggingFaceTextEmbedding",
|
||||
"HuggingFacePromptExecutionSettings",
|
||||
]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from typing import Any
|
||||
|
||||
from transformers import GenerationConfig
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
@@ -6,8 +6,8 @@ from semantic_kernel.connectors.ai.ollama.services.ollama_text_completion import
|
||||
from semantic_kernel.connectors.ai.ollama.services.ollama_text_embedding import OllamaTextEmbedding
|
||||
|
||||
__all__ = [
|
||||
"OllamaChatCompletion",
|
||||
"OllamaPromptExecutionSettings",
|
||||
"OllamaTextCompletion",
|
||||
"OllamaChatCompletion",
|
||||
"OllamaTextEmbedding",
|
||||
]
|
||||
|
||||
@@ -56,18 +56,20 @@ class OllamaChatCompletion(TextCompletionClientBase, ChatCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.messages = self._prepare_chat_history_for_request(chat_history)
|
||||
settings.stream = False
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(str(self.url), json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
response_object = await response.json()
|
||||
return [
|
||||
ChatMessageContent(
|
||||
inner_content=response_object,
|
||||
ai_model_id=self.ai_model_id,
|
||||
role="assistant",
|
||||
content=response_object.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(str(self.url), json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
response_object = await response.json()
|
||||
return [
|
||||
ChatMessageContent(
|
||||
inner_content=response_object,
|
||||
ai_model_id=self.ai_model_id,
|
||||
role="assistant",
|
||||
content=response_object.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
|
||||
async def get_streaming_chat_message_contents(
|
||||
self,
|
||||
@@ -92,24 +94,26 @@ class OllamaChatCompletion(TextCompletionClientBase, ChatCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.messages = self._prepare_chat_history_for_request(chat_history)
|
||||
settings.stream = True
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(str(self.url), json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("message", {}).get("content") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingChatMessageContent(
|
||||
role="assistant",
|
||||
choice_index=0,
|
||||
inner_content=body,
|
||||
ai_model_id=self.ai_model_id,
|
||||
content=body.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(str(self.url), json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("message", {}).get("content") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingChatMessageContent(
|
||||
role="assistant",
|
||||
choice_index=0,
|
||||
inner_content=body,
|
||||
ai_model_id=self.ai_model_id,
|
||||
content=body.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
|
||||
async def get_text_contents(
|
||||
self,
|
||||
@@ -129,17 +133,19 @@ class OllamaChatCompletion(TextCompletionClientBase, ChatCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.messages = [{"role": "user", "content": prompt}]
|
||||
settings.stream = False
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(str(self.url), json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
response_object = await response.json()
|
||||
return [
|
||||
TextContent(
|
||||
inner_content=response_object,
|
||||
ai_model_id=self.ai_model_id,
|
||||
text=response_object.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(str(self.url), json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
response_object = await response.json()
|
||||
return [
|
||||
TextContent(
|
||||
inner_content=response_object,
|
||||
ai_model_id=self.ai_model_id,
|
||||
text=response_object.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
|
||||
async def get_streaming_text_contents(
|
||||
self,
|
||||
@@ -161,23 +167,25 @@ class OllamaChatCompletion(TextCompletionClientBase, ChatCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.messages = [{"role": "user", "content": prompt}]
|
||||
settings.stream = True
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(str(self.url), json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("message", {}).get("content") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingTextContent(
|
||||
choice_index=0,
|
||||
inner_content=body,
|
||||
ai_model_id=self.ai_model_id,
|
||||
text=body.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(str(self.url), json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("message", {}).get("content") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingTextContent(
|
||||
choice_index=0,
|
||||
inner_content=body,
|
||||
ai_model_id=self.ai_model_id,
|
||||
text=body.get("message", {"content": None}).get("content", None),
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
|
||||
def get_prompt_execution_settings_class(self) -> "OllamaChatPromptExecutionSettings":
|
||||
"""Get the request settings class."""
|
||||
|
||||
@@ -48,12 +48,14 @@ class OllamaTextCompletion(TextCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.prompt = prompt
|
||||
settings.stream = False
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(self.url, json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
inner_content = await response.json()
|
||||
text = inner_content["response"]
|
||||
return [TextContent(inner_content=inner_content, ai_model_id=self.ai_model_id, text=text)]
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(self.url, json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
inner_content = await response.json()
|
||||
text = inner_content["response"]
|
||||
return [TextContent(inner_content=inner_content, ai_model_id=self.ai_model_id, text=text)]
|
||||
|
||||
async def get_streaming_text_contents(
|
||||
self,
|
||||
@@ -76,20 +78,22 @@ class OllamaTextCompletion(TextCompletionClientBase):
|
||||
settings.ai_model_id = self.ai_model_id
|
||||
settings.prompt = prompt
|
||||
settings.stream = True
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(self.url, json=settings.prepare_settings_dict()) as response:
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("response") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingTextContent(
|
||||
choice_index=0, inner_content=body, ai_model_id=self.ai_model_id, text=body.get("response")
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
async with (
|
||||
AsyncSession(self.session) as session,
|
||||
session.post(self.url, json=settings.prepare_settings_dict()) as response,
|
||||
):
|
||||
response.raise_for_status()
|
||||
async for line in response.content:
|
||||
body = json.loads(line)
|
||||
if body.get("done") and body.get("response") is None:
|
||||
break
|
||||
yield [
|
||||
StreamingTextContent(
|
||||
choice_index=0, inner_content=body, ai_model_id=self.ai_model_id, text=body.get("response")
|
||||
)
|
||||
]
|
||||
if body.get("done"):
|
||||
break
|
||||
|
||||
def get_prompt_execution_settings_class(self) -> "OllamaTextPromptExecutionSettings":
|
||||
"""Get the request settings class."""
|
||||
|
||||
@@ -39,12 +39,11 @@ class OllamaTextEmbedding(EmbeddingGeneratorBase):
|
||||
async def generate_embeddings(self, texts: list[str], **kwargs: Any) -> ndarray:
|
||||
result = []
|
||||
for text in texts:
|
||||
async with AsyncSession(self.session) as session:
|
||||
async with session.post(
|
||||
self.url,
|
||||
json={"model": self.ai_model_id, "prompt": text, "options": kwargs},
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
response = await response.json()
|
||||
result.append(response["embedding"])
|
||||
async with AsyncSession(self.session) as session, session.post(
|
||||
self.url,
|
||||
json={"model": self.ai_model_id, "prompt": text, "options": kwargs},
|
||||
) as response:
|
||||
response.raise_for_status()
|
||||
response = await response.json()
|
||||
result.append(response["embedding"])
|
||||
return array(result)
|
||||
|
||||
@@ -26,25 +26,25 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion impo
|
||||
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding import OpenAITextEmbedding
|
||||
|
||||
__all__ = [
|
||||
"OpenAIPromptExecutionSettings",
|
||||
"OpenAIChatPromptExecutionSettings",
|
||||
"OpenAITextPromptExecutionSettings",
|
||||
"AzureChatPromptExecutionSettings",
|
||||
"OpenAITextCompletion",
|
||||
"OpenAIChatCompletion",
|
||||
"OpenAITextEmbedding",
|
||||
"AzureTextCompletion",
|
||||
"AzureChatCompletion",
|
||||
"AzureTextEmbedding",
|
||||
"ApiKeyAuthentication",
|
||||
"AzureAISearchDataSource",
|
||||
"AzureAISearchDataSourceParameters",
|
||||
"AzureChatCompletion",
|
||||
"AzureChatPromptExecutionSettings",
|
||||
"AzureCosmosDBDataSource",
|
||||
"AzureCosmosDBDataSourceParameters",
|
||||
"AzureDataSourceParameters",
|
||||
"ApiKeyAuthentication",
|
||||
"AzureEmbeddingDependency",
|
||||
"AzureTextCompletion",
|
||||
"AzureTextEmbedding",
|
||||
"ConnectionStringAuthentication",
|
||||
"DataSourceFieldsMapping",
|
||||
"ExtraBody",
|
||||
"AzureEmbeddingDependency",
|
||||
"DataSourceFieldsMapping",
|
||||
"ExtraBody",
|
||||
"OpenAIChatCompletion",
|
||||
"OpenAIChatPromptExecutionSettings",
|
||||
"OpenAIPromptExecutionSettings",
|
||||
"OpenAITextCompletion",
|
||||
"OpenAITextEmbedding",
|
||||
"OpenAITextPromptExecutionSettings",
|
||||
]
|
||||
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import logging
|
||||
from typing import Annotated, Any, Literal, Union
|
||||
|
||||
|
||||
@@ -125,15 +125,15 @@ class AzureChatCompletion(AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenA
|
||||
"""
|
||||
return AzureChatCompletion(
|
||||
service_id=settings.get("service_id"),
|
||||
api_key=settings.get("api_key", None),
|
||||
deployment_name=settings.get("deployment_name", None),
|
||||
endpoint=settings.get("endpoint", None),
|
||||
base_url=settings.get("base_url", None),
|
||||
api_version=settings.get("api_version", None),
|
||||
api_key=settings.get("api_key"),
|
||||
deployment_name=settings.get("deployment_name"),
|
||||
endpoint=settings.get("endpoint"),
|
||||
base_url=settings.get("base_url"),
|
||||
api_version=settings.get("api_version"),
|
||||
ad_token=settings.get("ad_token"),
|
||||
ad_token_provider=settings.get("ad_token_provider"),
|
||||
default_headers=settings.get("default_headers"),
|
||||
env_file_path=settings.get("env_file_path", None),
|
||||
env_file_path=settings.get("env_file_path"),
|
||||
)
|
||||
|
||||
def get_prompt_execution_settings_class(self) -> "PromptExecutionSettings":
|
||||
@@ -181,10 +181,7 @@ class AzureChatCompletion(AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenA
|
||||
|
||||
def _get_tool_message_from_chat_choice(self, choice: Choice | ChunkChoice) -> str | None:
|
||||
"""Get the tool message from a choice."""
|
||||
if isinstance(choice, Choice):
|
||||
content = choice.message
|
||||
else:
|
||||
content = choice.delta
|
||||
content = choice.message if isinstance(choice, Choice) else choice.delta
|
||||
if content.model_extra is not None and "context" in content.model_extra:
|
||||
return json.dumps(content.model_extra["context"])
|
||||
|
||||
|
||||
@@ -100,13 +100,13 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase):
|
||||
"""
|
||||
return AzureTextCompletion(
|
||||
service_id=settings.get("service_id"),
|
||||
api_key=settings.get("api_key", None),
|
||||
deployment_name=settings.get("deployment_name", None),
|
||||
endpoint=settings.get("endpoint", None),
|
||||
base_url=settings.get("base_url", None),
|
||||
api_version=settings.get("api_version", None),
|
||||
api_key=settings.get("api_key"),
|
||||
deployment_name=settings.get("deployment_name"),
|
||||
endpoint=settings.get("endpoint"),
|
||||
base_url=settings.get("base_url"),
|
||||
api_version=settings.get("api_version"),
|
||||
ad_token=settings.get("ad_token"),
|
||||
ad_token_provider=settings.get("ad_token_provider"),
|
||||
default_headers=settings.get("default_headers"),
|
||||
env_file_path=settings.get("env_file_path", None),
|
||||
env_file_path=settings.get("env_file_path"),
|
||||
)
|
||||
|
||||
@@ -105,13 +105,13 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase):
|
||||
"""
|
||||
return AzureTextEmbedding(
|
||||
service_id=settings.get("service_id"),
|
||||
api_key=settings.get("api_key", None),
|
||||
deployment_name=settings.get("deployment_name", None),
|
||||
endpoint=settings.get("endpoint", None),
|
||||
base_url=settings.get("base_url", None),
|
||||
api_version=settings.get("api_version", None),
|
||||
api_key=settings.get("api_key"),
|
||||
deployment_name=settings.get("deployment_name"),
|
||||
endpoint=settings.get("endpoint"),
|
||||
base_url=settings.get("base_url"),
|
||||
api_version=settings.get("api_version"),
|
||||
ad_token=settings.get("ad_token"),
|
||||
ad_token_provider=settings.get("ad_token_provider"),
|
||||
default_headers=settings.get("default_headers"),
|
||||
env_file_path=settings.get("env_file_path", None),
|
||||
env_file_path=settings.get("env_file_path"),
|
||||
)
|
||||
|
||||
+15
-21
@@ -260,10 +260,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
"""Send the chat request."""
|
||||
response = await self._send_request(request_settings=settings)
|
||||
response_metadata = self._get_metadata_from_chat_response(response)
|
||||
completions = [
|
||||
self._create_chat_message_content(response, choice, response_metadata) for choice in response.choices
|
||||
]
|
||||
return completions
|
||||
return [self._create_chat_message_content(response, choice, response_metadata) for choice in response.choices]
|
||||
|
||||
async def _send_chat_stream_request(
|
||||
self, settings: OpenAIChatPromptExecutionSettings
|
||||
@@ -353,10 +350,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
|
||||
def _get_tool_calls_from_chat_choice(self, choice: Choice | ChunkChoice) -> list[FunctionCallContent]:
|
||||
"""Get tool calls from a chat choice."""
|
||||
if isinstance(choice, Choice):
|
||||
content = choice.message
|
||||
else:
|
||||
content = choice.delta
|
||||
content = choice.message if isinstance(choice, Choice) else choice.delta
|
||||
if content.tool_calls is None:
|
||||
return []
|
||||
return [
|
||||
@@ -371,10 +365,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
|
||||
def _get_function_call_from_chat_choice(self, choice: Choice | ChunkChoice) -> list[FunctionCallContent]:
|
||||
"""Get a function call from a chat choice."""
|
||||
if isinstance(choice, Choice):
|
||||
content = choice.message
|
||||
else:
|
||||
content = choice.delta
|
||||
content = choice.message if isinstance(choice, Choice) else choice.delta
|
||||
if content.function_call is None:
|
||||
return []
|
||||
return [
|
||||
@@ -442,18 +433,20 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
result="The tool call arguments are malformed, please try again.",
|
||||
)
|
||||
chat_history.add_message(message=frc.to_chat_message_content())
|
||||
return
|
||||
return None
|
||||
|
||||
logger.info(f"Calling {function_call.name} function with args: {function_call.arguments}")
|
||||
try:
|
||||
if function_call.name is None:
|
||||
raise ValueError("The function name is required.")
|
||||
if isinstance(function_call_behavior, RequiredFunction):
|
||||
if function_call.name != function_call_behavior.function_fully_qualified_name:
|
||||
raise ValueError(
|
||||
f"Only function: {function_call_behavior.function_fully_qualified_name} "
|
||||
f"is allowed, {function_call.name} is not allowed."
|
||||
)
|
||||
if (
|
||||
isinstance(function_call_behavior, RequiredFunction)
|
||||
and function_call.name != function_call_behavior.function_fully_qualified_name
|
||||
):
|
||||
raise ValueError(
|
||||
f"Only function: {function_call_behavior.function_fully_qualified_name} "
|
||||
f"is allowed, {function_call.name} is not allowed."
|
||||
)
|
||||
if isinstance(function_call_behavior, EnabledFunctions):
|
||||
enabled_functions = [
|
||||
func.fully_qualified_name
|
||||
@@ -471,7 +464,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
result="The tool call could not be found, please try again and make sure to validate the name.",
|
||||
)
|
||||
chat_history.add_message(message=frc.to_chat_message_content())
|
||||
return
|
||||
return None
|
||||
|
||||
num_required_func_params = len([param for param in function_to_call.parameters if param.is_required])
|
||||
if len(parsed_args) < num_required_func_params:
|
||||
@@ -487,7 +480,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
result=msg,
|
||||
)
|
||||
chat_history.add_message(message=frc.to_chat_message_content())
|
||||
return
|
||||
return None
|
||||
|
||||
_rebuild_auto_function_invocation_context()
|
||||
invocation_context = AutoFunctionInvocationContext(
|
||||
@@ -515,6 +508,7 @@ class OpenAIChatCompletionBase(OpenAIHandler, ChatCompletionClientBase):
|
||||
function_call_content=function_call, result=invocation_context.function_result
|
||||
)
|
||||
chat_history.add_message(message=frc.to_chat_message_content())
|
||||
return None
|
||||
|
||||
async def _inner_auto_function_invoke_handler(self, context: AutoFunctionInvocationContext):
|
||||
"""Inner auto function invocation handler."""
|
||||
|
||||
@@ -74,7 +74,7 @@ class OpenAIHandler(KernelBaseModel, ABC):
|
||||
response = await self.client.embeddings.create(**settings.prepare_settings_dict())
|
||||
self.store_usage(response)
|
||||
# make numpy arrays from the response
|
||||
# TODO: the openai response is cast to a list[float], could be used instead of ndarray
|
||||
# TODO (eavanvalkenburg): the openai response is cast to a list[float], could be used instead of ndarray
|
||||
return [array(x.embedding) for x in response.data]
|
||||
except Exception as ex:
|
||||
raise ServiceResponseException(
|
||||
|
||||
@@ -79,10 +79,10 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase):
|
||||
if "default_headers" in settings and isinstance(settings["default_headers"], str):
|
||||
settings["default_headers"] = json.loads(settings["default_headers"])
|
||||
return OpenAITextCompletion(
|
||||
ai_model_id=settings.get("ai_model_id", None),
|
||||
api_key=settings.get("api_key", None),
|
||||
org_id=settings.get("org_id", None),
|
||||
ai_model_id=settings.get("ai_model_id"),
|
||||
api_key=settings.get("api_key"),
|
||||
org_id=settings.get("org_id"),
|
||||
service_id=settings.get("service_id"),
|
||||
default_headers=settings.get("default_headers"),
|
||||
env_file_path=settings.get("env_file_path", None),
|
||||
env_file_path=settings.get("env_file_path"),
|
||||
)
|
||||
|
||||
@@ -79,9 +79,9 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase):
|
||||
"""
|
||||
return OpenAITextEmbedding(
|
||||
ai_model_id=settings["ai_model_id"],
|
||||
api_key=settings.get("api_key", None),
|
||||
org_id=settings.get("org_id", None),
|
||||
api_key=settings.get("api_key"),
|
||||
org_id=settings.get("org_id"),
|
||||
service_id=settings.get("service_id"),
|
||||
default_headers=settings.get("default_headers"),
|
||||
env_file_path=settings.get("env_file_path", None),
|
||||
env_file_path=settings.get("env_file_path"),
|
||||
)
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ class OpenAITextEmbeddingBase(OpenAIHandler, EmbeddingGeneratorBase):
|
||||
raw_embeddings = []
|
||||
batch_size = batch_size or len(texts)
|
||||
for i in range(0, len(texts), batch_size):
|
||||
batch = texts[i : i + batch_size] # noqa: E203
|
||||
batch = texts[i : i + batch_size]
|
||||
settings.input = batch
|
||||
raw_embedding = await self._send_embedding_request(
|
||||
settings=settings,
|
||||
|
||||
@@ -11,10 +11,7 @@ from semantic_kernel.utils.experimental_decorator import experimental_class
|
||||
|
||||
ASTRA_CALLER_IDENTITY: str
|
||||
SEMANTIC_KERNEL_VERSION = APP_INFO.get("Semantic-Kernel-Version")
|
||||
if SEMANTIC_KERNEL_VERSION:
|
||||
ASTRA_CALLER_IDENTITY = f"semantic-kernel/{SEMANTIC_KERNEL_VERSION}"
|
||||
else:
|
||||
ASTRA_CALLER_IDENTITY = "semantic-kernel"
|
||||
ASTRA_CALLER_IDENTITY = f"semantic-kernel/{SEMANTIC_KERNEL_VERSION}" if SEMANTIC_KERNEL_VERSION else "semantic-kernel"
|
||||
|
||||
|
||||
@experimental_class
|
||||
@@ -48,16 +45,16 @@ class AstraClient:
|
||||
self._session = session
|
||||
|
||||
async def _run_query(self, request_url: str, query: dict):
|
||||
async with AsyncSession(self._session) as session:
|
||||
async with session.post(request_url, data=json.dumps(query), headers=self.request_header) as response:
|
||||
if response.status == 200:
|
||||
response_dict = await response.json()
|
||||
if "errors" in response_dict:
|
||||
raise ServiceResponseException(f"Astra DB request error - {response_dict['errors']}")
|
||||
else:
|
||||
return response_dict
|
||||
else:
|
||||
raise ServiceResponseException(f"Astra DB not available. Status : {response}")
|
||||
async with (
|
||||
AsyncSession(self._session) as session,
|
||||
session.post(request_url, data=json.dumps(query), headers=self.request_header) as response,
|
||||
):
|
||||
if response.status == 200:
|
||||
response_dict = await response.json()
|
||||
if "errors" in response_dict:
|
||||
raise ServiceResponseException(f"Astra DB request error - {response_dict['errors']}")
|
||||
return response_dict
|
||||
raise ServiceResponseException(f"Astra DB not available. Status : {response}")
|
||||
|
||||
async def find_collections(self, include_detail: bool = True):
|
||||
"""Finds all collections in the keyspace."""
|
||||
@@ -94,13 +91,13 @@ class AstraClient:
|
||||
}
|
||||
}
|
||||
result = await self._run_query(self.request_base_url, query)
|
||||
return True if result["status"]["ok"] == 1 else False
|
||||
return result["status"]["ok"] == 1
|
||||
|
||||
async def delete_collection(self, collection_name: str):
|
||||
"""Deletes a collection from the keyspace."""
|
||||
query = {"deleteCollection": {"name": collection_name}}
|
||||
result = await self._run_query(self.request_base_url, query)
|
||||
return True if result["status"]["ok"] == 1 else False
|
||||
return result["status"]["ok"] == 1
|
||||
|
||||
def _build_request_collection_url(self, collection_name: str):
|
||||
return f"{self.request_base_url}/{collection_name}"
|
||||
|
||||
@@ -74,7 +74,7 @@ class AstraDBMemoryStore(MemoryStoreBase):
|
||||
if self._embedding_dim > MAX_DIMENSIONALITY:
|
||||
raise MemoryConnectorInitializationError(
|
||||
f"Dimensionality of {self._embedding_dim} exceeds "
|
||||
+ f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
||||
f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
||||
)
|
||||
|
||||
self._client = AstraClient(
|
||||
@@ -170,7 +170,7 @@ class AstraDBMemoryStore(MemoryStoreBase):
|
||||
update = {"$set": build_payload(record)}
|
||||
status = await self._client.update_document(collection_name, filter, update, True)
|
||||
|
||||
return status["upsertedId"] if "upsertedId" in status else record._id
|
||||
return status.get("upsertedId", record._id)
|
||||
|
||||
async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]:
|
||||
"""Upsert a batch of memory records into the data store.
|
||||
|
||||
@@ -36,9 +36,9 @@ def build_payload(record: MemoryRecord) -> dict[str, Any]:
|
||||
|
||||
def parse_payload(document: dict[str, Any]) -> MemoryRecord:
|
||||
"""Parses a record from AstraDb into a MemoryRecord."""
|
||||
text = document.get("text", None)
|
||||
description = document["description"] if "description" in document else None
|
||||
additional_metadata = document["additional_metadata"] if "additional_metadata" in document else None
|
||||
text = document.get("text")
|
||||
description = document.get("description")
|
||||
additional_metadata = document.get("additional_metadata")
|
||||
|
||||
return MemoryRecord.local_record(
|
||||
id=document["_id"],
|
||||
|
||||
@@ -5,4 +5,4 @@ from semantic_kernel.connectors.memory.azure_cognitive_search.azure_cognitive_se
|
||||
AzureCognitiveSearchMemoryStore,
|
||||
)
|
||||
|
||||
__all__ = ["AzureCognitiveSearchMemoryStore", "AzureAISearchSettings"]
|
||||
__all__ = ["AzureAISearchSettings", "AzureCognitiveSearchMemoryStore"]
|
||||
|
||||
+5
-11
@@ -1,5 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import contextlib
|
||||
import logging
|
||||
import uuid
|
||||
from inspect import isawaitable
|
||||
@@ -149,10 +150,8 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase):
|
||||
|
||||
# Check to see if collection exists
|
||||
collection_index = None
|
||||
try:
|
||||
with contextlib.suppress(ResourceNotFoundError):
|
||||
collection_index = await self._search_index_client.get_index(collection_name.lower())
|
||||
except ResourceNotFoundError:
|
||||
pass
|
||||
|
||||
if not collection_index:
|
||||
# Create the search index with the semantic settings
|
||||
@@ -204,10 +203,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase):
|
||||
try:
|
||||
collection_result = await self._search_index_client.get_index(name=collection_name.lower())
|
||||
|
||||
if collection_result:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
return bool(collection_result)
|
||||
except ResourceNotFoundError:
|
||||
return False
|
||||
|
||||
@@ -259,8 +255,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase):
|
||||
|
||||
if result[0].succeeded:
|
||||
return search_ids
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord:
|
||||
"""Gets a record.
|
||||
@@ -372,8 +367,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase):
|
||||
|
||||
if len(memory_records) > 0:
|
||||
return memory_records[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
async def get_nearest_matches(
|
||||
self,
|
||||
|
||||
@@ -89,7 +89,7 @@ def get_index_schema(vector_size: int, vector_search_profile_name: str) -> list:
|
||||
Returns:
|
||||
list: The Azure Cognitive Search schema as list type.
|
||||
"""
|
||||
search_fields = [
|
||||
return [
|
||||
SimpleField(
|
||||
name=SEARCH_FIELD_ID,
|
||||
type=SearchFieldDataType.String,
|
||||
@@ -142,8 +142,6 @@ def get_index_schema(vector_size: int, vector_search_profile_name: str) -> list:
|
||||
),
|
||||
]
|
||||
|
||||
return search_fields
|
||||
|
||||
|
||||
def get_field_selection(with_embeddings: bool) -> list[str]:
|
||||
"""Get the list of fields to search and load.
|
||||
@@ -179,7 +177,7 @@ def dict_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord:
|
||||
Returns:
|
||||
MemoryRecord: The MemoryRecord from Azure Cognitive Search Data Result.
|
||||
"""
|
||||
sk_result = MemoryRecord(
|
||||
return MemoryRecord(
|
||||
id=decode_id(data[SEARCH_FIELD_ID]),
|
||||
key=data[SEARCH_FIELD_ID],
|
||||
text=data[SEARCH_FIELD_TEXT],
|
||||
@@ -190,7 +188,6 @@ def dict_to_memory_record(data: dict, with_embeddings: bool) -> MemoryRecord:
|
||||
embedding=data[SEARCH_FIELD_EMBEDDING] if with_embeddings else None,
|
||||
timestamp=None,
|
||||
)
|
||||
return sk_result
|
||||
|
||||
|
||||
def memory_record_to_search_record(record: MemoryRecord) -> dict:
|
||||
|
||||
@@ -96,32 +96,34 @@ class MongoStoreApi(AzureCosmosDBStoreApi):
|
||||
|
||||
@override
|
||||
async def create_collection(self, collection_name: str) -> None:
|
||||
if not await self.does_collection_exist(collection_name):
|
||||
if self.index_name not in self.database[collection_name].list_indexes():
|
||||
# check the kind of vector search to be performed
|
||||
# prepare the command accordingly
|
||||
create_index_commands = {}
|
||||
if self.kind == CosmosDBVectorSearchType.VECTOR_IVF:
|
||||
create_index_commands = self._get_vector_index_ivf(
|
||||
collection_name, self.kind, self.num_lists, self.similarity, self.vector_dimensions
|
||||
)
|
||||
elif self.kind == CosmosDBVectorSearchType.VECTOR_HNSW:
|
||||
create_index_commands = self._get_vector_index_hnsw(
|
||||
collection_name,
|
||||
self.kind,
|
||||
self.m,
|
||||
self.ef_construction,
|
||||
self.similarity,
|
||||
self.vector_dimensions,
|
||||
)
|
||||
# invoke the command from the database object
|
||||
self.database.command(create_index_commands)
|
||||
if (
|
||||
not await self.does_collection_exist(collection_name)
|
||||
and self.index_name not in self.database[collection_name].list_indexes()
|
||||
):
|
||||
# check the kind of vector search to be performed
|
||||
# prepare the command accordingly
|
||||
create_index_commands = {}
|
||||
if self.kind == CosmosDBVectorSearchType.VECTOR_IVF:
|
||||
create_index_commands = self._get_vector_index_ivf(
|
||||
collection_name, self.kind, self.num_lists, self.similarity, self.vector_dimensions
|
||||
)
|
||||
elif self.kind == CosmosDBVectorSearchType.VECTOR_HNSW:
|
||||
create_index_commands = self._get_vector_index_hnsw(
|
||||
collection_name,
|
||||
self.kind,
|
||||
self.m,
|
||||
self.ef_construction,
|
||||
self.similarity,
|
||||
self.vector_dimensions,
|
||||
)
|
||||
# invoke the command from the database object
|
||||
self.database.command(create_index_commands)
|
||||
self.collection = self.database[collection_name]
|
||||
|
||||
def _get_vector_index_ivf(
|
||||
self, collection_name: str, kind: str, num_lists: int, similarity: str, dimensions: int
|
||||
) -> dict[str, Any]:
|
||||
command = {
|
||||
return {
|
||||
"createIndexes": collection_name,
|
||||
"indexes": [
|
||||
{
|
||||
@@ -136,12 +138,11 @@ class MongoStoreApi(AzureCosmosDBStoreApi):
|
||||
}
|
||||
],
|
||||
}
|
||||
return command
|
||||
|
||||
def _get_vector_index_hnsw(
|
||||
self, collection_name: str, kind: str, m: int, ef_construction: int, similarity: str, dimensions: int
|
||||
) -> dict[str, Any]:
|
||||
command = {
|
||||
return {
|
||||
"createIndexes": collection_name,
|
||||
"indexes": [
|
||||
{
|
||||
@@ -157,7 +158,6 @@ class MongoStoreApi(AzureCosmosDBStoreApi):
|
||||
}
|
||||
],
|
||||
}
|
||||
return command
|
||||
|
||||
@override
|
||||
async def get_collections(self) -> list[str]:
|
||||
@@ -334,8 +334,7 @@ class MongoStoreApi(AzureCosmosDBStoreApi):
|
||||
|
||||
if len(nearest_results) > 0:
|
||||
return nearest_results[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def __serialize_metadata(record: MemoryRecord) -> str:
|
||||
|
||||
+1
-2
@@ -182,8 +182,7 @@ class AzureCosmosDBNoSQLMemoryStore(MemoryStoreBase):
|
||||
)
|
||||
if len(nearest_results) > 0:
|
||||
return nearest_results[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def __serialize_metadata(record: MemoryRecord) -> str:
|
||||
|
||||
@@ -65,7 +65,7 @@ class ChromaMemoryStore(MemoryStoreBase):
|
||||
|
||||
except ImportError as exc:
|
||||
raise ServiceInitializationError(
|
||||
"Could not import chromadb python package. " "Please install it with `pip install chromadb`."
|
||||
"Could not import chromadb python package. Please install it with `pip install chromadb`."
|
||||
) from exc
|
||||
|
||||
if client_settings:
|
||||
@@ -131,10 +131,7 @@ class ChromaMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
bool: True if the collection exists; otherwise, False.
|
||||
"""
|
||||
if await self.get_collection(collection_name) is None:
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
return await self.get_collection(collection_name) is not None
|
||||
|
||||
async def upsert(self, collection_name: str, record: MemoryRecord) -> str:
|
||||
"""Upsert a single MemoryRecord.
|
||||
@@ -219,8 +216,7 @@ class ChromaMemoryStore(MemoryStoreBase):
|
||||
query_includes = ["embeddings", "metadatas", "documents"] if with_embeddings else ["metadatas", "documents"]
|
||||
|
||||
value = collection.get(ids=keys, include=query_includes)
|
||||
record = query_results_to_records(value, with_embeddings)
|
||||
return record
|
||||
return query_results_to_records(value, with_embeddings)
|
||||
|
||||
async def remove(self, collection_name: str, key: str) -> None:
|
||||
"""Removes a record.
|
||||
@@ -312,9 +308,7 @@ class ChromaMemoryStore(MemoryStoreBase):
|
||||
)
|
||||
|
||||
filtered_results = [x for x in sorted_results if x[1] >= min_relevance_score]
|
||||
top_results = filtered_results[:limit]
|
||||
|
||||
return top_results
|
||||
return filtered_results[:limit]
|
||||
|
||||
async def get_nearest_match(
|
||||
self,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from semantic_kernel.connectors.memory.milvus.milvus_memory_store import (
|
||||
MilvusMemoryStore,
|
||||
)
|
||||
|
||||
@@ -80,19 +80,19 @@ def milvus_dict_to_memoryrecord(milvus_dict: dict[str, Any]) -> MemoryRecord:
|
||||
MemoryRecord
|
||||
"""
|
||||
# Embedding needs conversion to numpy array
|
||||
embedding = milvus_dict.get(SEARCH_FIELD_EMBEDDING, None)
|
||||
embedding = milvus_dict.get(SEARCH_FIELD_EMBEDDING)
|
||||
if embedding is not None:
|
||||
embedding = array(embedding)
|
||||
return MemoryRecord(
|
||||
is_reference=milvus_dict.get(SEARCH_FIELD_IS_REF, None),
|
||||
external_source_name=milvus_dict.get(SEARCH_FIELD_SRC, None),
|
||||
id=milvus_dict.get(SEARCH_FIELD_ID, None),
|
||||
description=milvus_dict.get(SEARCH_FIELD_DESC, None),
|
||||
text=milvus_dict.get(SEARCH_FIELD_TEXT, None),
|
||||
additional_metadata=milvus_dict.get(SEARCH_FIELD_METADATA, None),
|
||||
is_reference=milvus_dict.get(SEARCH_FIELD_IS_REF),
|
||||
external_source_name=milvus_dict.get(SEARCH_FIELD_SRC),
|
||||
id=milvus_dict.get(SEARCH_FIELD_ID),
|
||||
description=milvus_dict.get(SEARCH_FIELD_DESC),
|
||||
text=milvus_dict.get(SEARCH_FIELD_TEXT),
|
||||
additional_metadata=milvus_dict.get(SEARCH_FIELD_METADATA),
|
||||
embedding=embedding,
|
||||
key=milvus_dict.get("key", None),
|
||||
timestamp=milvus_dict.get(SEARCH_FIELD_TIMESTAMP, None),
|
||||
key=milvus_dict.get("key"),
|
||||
timestamp=milvus_dict.get(SEARCH_FIELD_TIMESTAMP),
|
||||
)
|
||||
|
||||
|
||||
@@ -194,9 +194,8 @@ class MilvusMemoryStore(MemoryStoreBase):
|
||||
create_fields(dimension_num), "Semantic Kernel Milvus Collection", enable_dynamic_field=True
|
||||
)
|
||||
index_param = {"index_type": _INDEX_TYPE, "params": {"nlist": _NLIST}, "metric_type": distance_type}
|
||||
if utility.has_collection(collection_name):
|
||||
if overwrite:
|
||||
utility.drop_collection(collection_name=collection_name)
|
||||
if utility.has_collection(collection_name) and overwrite:
|
||||
utility.drop_collection(collection_name=collection_name)
|
||||
self.collections[collection_name] = Collection(
|
||||
name=collection_name,
|
||||
schema=schema,
|
||||
@@ -453,5 +452,4 @@ class MilvusMemoryStore(MemoryStoreBase):
|
||||
)
|
||||
if len(m) > 0:
|
||||
return m[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -61,7 +61,7 @@ class PineconeMemoryStore(MemoryStoreBase):
|
||||
if default_dimensionality > MAX_DIMENSIONALITY:
|
||||
raise MemoryConnectorInitializationError(
|
||||
f"Dimensionality of {default_dimensionality} exceeds "
|
||||
+ f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
||||
f"the maximum allowed value of {MAX_DIMENSIONALITY}."
|
||||
)
|
||||
try:
|
||||
pinecone_settings = PineconeSettings.create(
|
||||
@@ -217,8 +217,7 @@ class PineconeMemoryStore(MemoryStoreBase):
|
||||
|
||||
if upsert_response.upserted_count is None:
|
||||
raise ServiceResponseException(f"Error upserting record: {upsert_response.message}")
|
||||
else:
|
||||
return [record._id for record in records]
|
||||
return [record._id for record in records]
|
||||
|
||||
async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord:
|
||||
"""Gets a record.
|
||||
@@ -259,7 +258,7 @@ class PineconeMemoryStore(MemoryStoreBase):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
|
||||
fetch_response = await self.__get_batch(collection_name, keys, with_embeddings)
|
||||
return [parse_payload(fetch_response.vectors[key], with_embeddings) for key in fetch_response.vectors.keys()]
|
||||
return [parse_payload(fetch_response.vectors[key], with_embeddings) for key in fetch_response.vectors]
|
||||
|
||||
async def remove(self, collection_name: str, key: str) -> None:
|
||||
"""Removes a record.
|
||||
@@ -351,7 +350,7 @@ class PineconeMemoryStore(MemoryStoreBase):
|
||||
raise ServiceInvalidRequestError(
|
||||
"Limit must be less than or equal to " + f"{MAX_QUERY_WITHOUT_METADATA_BATCH_SIZE}"
|
||||
)
|
||||
elif limit > MAX_QUERY_WITH_METADATA_BATCH_SIZE:
|
||||
if limit > MAX_QUERY_WITH_METADATA_BATCH_SIZE:
|
||||
query_response = collection.query(
|
||||
vector=embedding.tolist(),
|
||||
top_k=limit,
|
||||
@@ -363,7 +362,7 @@ class PineconeMemoryStore(MemoryStoreBase):
|
||||
vectors = fetch_response.vectors
|
||||
for match in query_response.matches:
|
||||
vectors[match.id].update(match)
|
||||
matches = [vectors[key] for key in vectors.keys()]
|
||||
matches = [vectors[key] for key in vectors]
|
||||
else:
|
||||
query_response = collection.query(
|
||||
vector=embedding.tolist(),
|
||||
|
||||
@@ -98,24 +98,23 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
else:
|
||||
self._check_dimensionality(dimension_num)
|
||||
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS {scm}.{tbl} (
|
||||
key TEXT PRIMARY KEY,
|
||||
embedding vector({dim}),
|
||||
metadata JSONB,
|
||||
timestamp TIMESTAMP
|
||||
)"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
dim=dimension_num,
|
||||
),
|
||||
(),
|
||||
)
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
dim=dimension_num,
|
||||
),
|
||||
(),
|
||||
)
|
||||
|
||||
async def get_collections(self) -> list[str]:
|
||||
"""Gets the list of collections.
|
||||
@@ -123,9 +122,8 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
List[str]: The list of collections.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
return await self.__get_collections(cur)
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
return await self.__get_collections(cur)
|
||||
|
||||
async def delete_collection(self, collection_name: str) -> None:
|
||||
"""Deletes a collection.
|
||||
@@ -136,13 +134,12 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(
|
||||
SQL("DROP TABLE IF EXISTS {scm}.{tbl} CASCADE").format(
|
||||
scm=Identifier(self._schema), tbl=Identifier(collection_name)
|
||||
),
|
||||
)
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
cur.execute(
|
||||
SQL("DROP TABLE IF EXISTS {scm}.{tbl} CASCADE").format(
|
||||
scm=Identifier(self._schema), tbl=Identifier(collection_name)
|
||||
),
|
||||
)
|
||||
|
||||
async def does_collection_exist(self, collection_name: str) -> bool:
|
||||
"""Checks if a collection exists.
|
||||
@@ -153,9 +150,8 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
bool: True if the collection exists; otherwise, False.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
return await self.__does_collection_exist(cur, collection_name)
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
return await self.__does_collection_exist(cur, collection_name)
|
||||
|
||||
async def upsert(self, collection_name: str, record: MemoryRecord) -> str:
|
||||
r"""Upserts a record.
|
||||
@@ -167,13 +163,12 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
str: The unique database key of the record. In Pinecone, this is the record ID.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
INSERT INTO {scm}.{tbl} (key, embedding, metadata, timestamp)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
@@ -182,21 +177,21 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
timestamp = EXCLUDED.timestamp
|
||||
RETURNING key
|
||||
"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
(
|
||||
record._id,
|
||||
record.embedding.tolist(),
|
||||
self.__serialize_metadata(record),
|
||||
record._timestamp,
|
||||
),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
return result[0]
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
(
|
||||
record._id,
|
||||
record.embedding.tolist(),
|
||||
self.__serialize_metadata(record),
|
||||
record._timestamp,
|
||||
),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
return result[0]
|
||||
|
||||
async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]:
|
||||
"""Upserts a batch of records.
|
||||
@@ -208,14 +203,13 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
List[str]: The unique database keys of the records.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.nextset()
|
||||
cur.executemany(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.nextset()
|
||||
cur.executemany(
|
||||
SQL(
|
||||
"""
|
||||
INSERT INTO {scm}.{tbl} (key, embedding, metadata, timestamp)
|
||||
VALUES (%s, %s, %s, %s)
|
||||
ON CONFLICT (key) DO UPDATE
|
||||
@@ -224,29 +218,29 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
timestamp = EXCLUDED.timestamp
|
||||
RETURNING key
|
||||
"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
[
|
||||
(
|
||||
record._id,
|
||||
record.embedding.tolist(),
|
||||
self.__serialize_metadata(record),
|
||||
record._timestamp,
|
||||
)
|
||||
for record in records
|
||||
],
|
||||
returning=True,
|
||||
)
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
[
|
||||
(
|
||||
record._id,
|
||||
record.embedding.tolist(),
|
||||
self.__serialize_metadata(record),
|
||||
record._timestamp,
|
||||
)
|
||||
for record in records
|
||||
],
|
||||
returning=True,
|
||||
)
|
||||
|
||||
# collate results
|
||||
results = [cur.fetchone()]
|
||||
while cur.nextset():
|
||||
results.append(cur.fetchone())
|
||||
if None in results:
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
return [result[0] for result in results if result is not None]
|
||||
# collate results
|
||||
results = [cur.fetchone()]
|
||||
while cur.nextset():
|
||||
results.append(cur.fetchone())
|
||||
if None in results:
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
return [result[0] for result in results if result is not None]
|
||||
|
||||
async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord:
|
||||
"""Gets a record.
|
||||
@@ -259,36 +253,35 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
MemoryRecord: The record.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
SELECT key, embedding, metadata, timestamp
|
||||
FROM {scm}.{tbl}
|
||||
WHERE key = %s
|
||||
"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
(key,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
raise ServiceResourceNotFoundError("Key not found")
|
||||
return MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",") if with_embedding else np.array([])
|
||||
),
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[3],
|
||||
)
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
(key,),
|
||||
)
|
||||
result = cur.fetchone()
|
||||
if result is None:
|
||||
raise ServiceResourceNotFoundError("Key not found")
|
||||
return MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",") if with_embedding else np.array([])
|
||||
),
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[3],
|
||||
)
|
||||
|
||||
async def get_batch(
|
||||
self, collection_name: str, keys: list[str], with_embeddings: bool = False
|
||||
@@ -303,39 +296,38 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
List[MemoryRecord]: The records that were found from list of keys, can be empty.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
SELECT key, embedding, metadata, timestamp
|
||||
FROM {scm}.{tbl}
|
||||
WHERE key = ANY(%s)
|
||||
"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
),
|
||||
(list(keys),),
|
||||
)
|
||||
results = cur.fetchall()
|
||||
return [
|
||||
MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",")
|
||||
if with_embeddings
|
||||
else np.array([])
|
||||
),
|
||||
(list(keys),),
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[3],
|
||||
)
|
||||
results = cur.fetchall()
|
||||
return [
|
||||
MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",")
|
||||
if with_embeddings
|
||||
else np.array([])
|
||||
),
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[3],
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
for result in results
|
||||
]
|
||||
|
||||
async def remove(self, collection_name: str, key: str) -> None:
|
||||
"""Removes a record.
|
||||
@@ -347,19 +339,18 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
DELETE FROM {scm}.{tbl}
|
||||
WHERE key = %s
|
||||
"""
|
||||
).format(scm=Identifier(self._schema), tbl=Identifier(collection_name)),
|
||||
(key,),
|
||||
)
|
||||
).format(scm=Identifier(self._schema), tbl=Identifier(collection_name)),
|
||||
(key,),
|
||||
)
|
||||
|
||||
async def remove_batch(self, collection_name: str, keys: list[str]) -> None:
|
||||
"""Removes a batch of records.
|
||||
@@ -371,19 +362,18 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
DELETE FROM {scm}.{tbl}
|
||||
WHERE key = ANY(%s)
|
||||
"""
|
||||
).format(scm=Identifier(self._schema), tbl=Identifier(collection_name)),
|
||||
(list(keys),),
|
||||
)
|
||||
).format(scm=Identifier(self._schema), tbl=Identifier(collection_name)),
|
||||
(list(keys),),
|
||||
)
|
||||
|
||||
async def get_nearest_matches(
|
||||
self,
|
||||
@@ -405,13 +395,12 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
List[Tuple[MemoryRecord, float]]: The records and their relevance scores.
|
||||
"""
|
||||
with self._connection_pool.connection() as conn:
|
||||
with conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
with self._connection_pool.connection() as conn, conn.cursor() as cur:
|
||||
if not await self.__does_collection_exist(cur, collection_name):
|
||||
raise ServiceResourceNotFoundError(f"Collection '{collection_name}' does not exist")
|
||||
cur.execute(
|
||||
SQL(
|
||||
"""
|
||||
SELECT key,
|
||||
embedding,
|
||||
metadata,
|
||||
@@ -426,34 +415,34 @@ class PostgresMemoryStore(MemoryStoreBase):
|
||||
ORDER BY cosine_similarity DESC
|
||||
LIMIT {limit}
|
||||
"""
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
mrs=min_relevance_score,
|
||||
limit=limit,
|
||||
emb=SQL(",").join(embedding.tolist()),
|
||||
)
|
||||
).format(
|
||||
scm=Identifier(self._schema),
|
||||
tbl=Identifier(collection_name),
|
||||
mrs=min_relevance_score,
|
||||
limit=limit,
|
||||
emb=SQL(",").join(embedding.tolist()),
|
||||
)
|
||||
results = cur.fetchall()
|
||||
)
|
||||
results = cur.fetchall()
|
||||
|
||||
return [
|
||||
(
|
||||
MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",")
|
||||
if with_embeddings
|
||||
else np.array([])
|
||||
),
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[4],
|
||||
return [
|
||||
(
|
||||
MemoryRecord.local_record(
|
||||
id=result[0],
|
||||
embedding=(
|
||||
np.fromstring(result[1].strip("[]"), dtype=float, sep=",")
|
||||
if with_embeddings
|
||||
else np.array([])
|
||||
),
|
||||
result[3],
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
text=result[2]["text"],
|
||||
description=result[2]["description"],
|
||||
additional_metadata=result[2]["additional_metadata"],
|
||||
timestamp=result[4],
|
||||
),
|
||||
result[3],
|
||||
)
|
||||
for result in results
|
||||
]
|
||||
|
||||
async def get_nearest_match(
|
||||
self,
|
||||
|
||||
@@ -67,8 +67,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
Returns:
|
||||
CollectionInfo -- Collection Information from Qdrant about collection.
|
||||
"""
|
||||
collection_info = self._qdrantclient.get_collection(collection_name=collection_name)
|
||||
return collection_info
|
||||
return self._qdrantclient.get_collection(collection_name=collection_name)
|
||||
|
||||
@override
|
||||
async def delete_collection(self, collection_name: str) -> None:
|
||||
@@ -96,8 +95,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
|
||||
if result.status == qdrant_models.UpdateStatus.COMPLETED:
|
||||
return data_to_upsert.id
|
||||
else:
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
raise ServiceResponseException("Upsert failed")
|
||||
|
||||
@override
|
||||
async def upsert_batch(self, collection_name: str, records: list[MemoryRecord]) -> list[str]:
|
||||
@@ -119,8 +117,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
|
||||
if result.status == qdrant_models.UpdateStatus.COMPLETED:
|
||||
return [data.id for data in data_to_upsert]
|
||||
else:
|
||||
raise ServiceResponseException("Batch upsert failed")
|
||||
raise ServiceResponseException("Batch upsert failed")
|
||||
|
||||
@override
|
||||
async def get(self, collection_name: str, key: str, with_embedding: bool = False) -> MemoryRecord | None:
|
||||
@@ -142,8 +139,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
key=result.id,
|
||||
timestamp=result.payload["_timestamp"],
|
||||
)
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
@override
|
||||
async def get_batch(
|
||||
@@ -287,8 +283,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
|
||||
if existing_record:
|
||||
return existing_record[0]
|
||||
else:
|
||||
return None
|
||||
return None
|
||||
|
||||
async def _convert_from_memory_record(
|
||||
self, collection_name: str, record: MemoryRecord
|
||||
@@ -302,10 +297,7 @@ class QdrantMemoryStore(MemoryStoreBase):
|
||||
payload_id=record._id,
|
||||
)
|
||||
|
||||
if existing_record:
|
||||
pointId = str(existing_record.id)
|
||||
else:
|
||||
pointId = str(uuid.uuid4())
|
||||
pointId = str(existing_record.id) if existing_record else str(uuid.uuid4())
|
||||
|
||||
payload = record.__dict__.copy()
|
||||
embedding = payload.pop("_embedding")
|
||||
|
||||
@@ -48,13 +48,12 @@ def serialize_record_to_redis(record: MemoryRecord, vector_type: np.dtype) -> di
|
||||
"additional_metadata": record._additional_metadata or "",
|
||||
}
|
||||
|
||||
redis_mapping = {
|
||||
return {
|
||||
"key": record._key or "",
|
||||
"timestamp": record._timestamp.isoformat() if record._timestamp else "",
|
||||
"metadata": json.dumps(all_metadata),
|
||||
"embedding": (record._embedding.astype(vector_type).tobytes() if record._embedding is not None else ""),
|
||||
}
|
||||
return redis_mapping
|
||||
|
||||
|
||||
def deserialize_redis_to_record(fields: dict[str, Any], vector_type: np.dtype, with_embedding: bool) -> MemoryRecord:
|
||||
@@ -62,7 +61,7 @@ def deserialize_redis_to_record(fields: dict[str, Any], vector_type: np.dtype, w
|
||||
metadata = json.loads(fields[b"metadata"])
|
||||
record = MemoryRecord(
|
||||
id=metadata["id"],
|
||||
is_reference=True if metadata["is_reference"] is True else False,
|
||||
is_reference=metadata["is_reference"] is True,
|
||||
description=metadata["description"],
|
||||
external_source_name=metadata["external_source_name"],
|
||||
text=metadata["text"],
|
||||
@@ -91,7 +90,7 @@ def deserialize_document_to_record(
|
||||
metadata = json.loads(doc["metadata"])
|
||||
record = MemoryRecord(
|
||||
id=id_str,
|
||||
is_reference=True if metadata["is_reference"] is True else False,
|
||||
is_reference=metadata["is_reference"] is True,
|
||||
description=metadata["description"],
|
||||
external_source_name=metadata["external_source_name"],
|
||||
text=metadata["text"],
|
||||
|
||||
@@ -108,13 +108,11 @@ def pyarrow_table_to_memoryrecords(table: pa.Table, vectors: ndarray | None = No
|
||||
Returns:
|
||||
List[MemoryRecord]: List of MemoryRecords constructed from the table.
|
||||
"""
|
||||
result_memory_records = [
|
||||
return [
|
||||
MemoryRecord(**row.to_dict(), embedding=vectors[index] if vectors is not None else None)
|
||||
for index, row in table.to_pandas().iterrows()
|
||||
]
|
||||
|
||||
return result_memory_records
|
||||
|
||||
|
||||
@experimental_class
|
||||
class USearchMemoryStore(MemoryStoreBase):
|
||||
@@ -214,7 +212,7 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
|
||||
self._collections[collection_name] = _USearchCollection.create_default(embeddings_index)
|
||||
|
||||
return None
|
||||
return
|
||||
|
||||
def _read_embeddings_table(self, path: os.PathLike) -> tuple[pa.Table, dict[str, int]]:
|
||||
"""Read embeddings from the provided path and generate an ID to label mapping.
|
||||
@@ -283,7 +281,7 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
collection = self._collections.pop(collection_name, None)
|
||||
if collection:
|
||||
collection.embeddings_index.reset()
|
||||
return None
|
||||
return
|
||||
|
||||
async def does_collection_exist(self, collection_name: str) -> bool:
|
||||
"""Check if collection exists."""
|
||||
@@ -404,7 +402,7 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
"""Remove a single MemoryRecord using its key."""
|
||||
collection_name = collection_name.lower()
|
||||
await self.remove_batch(collection_name=collection_name, keys=[key])
|
||||
return None
|
||||
return
|
||||
|
||||
async def remove_batch(self, collection_name: str, keys: list[str]) -> None:
|
||||
"""Remove a batch of MemoryRecords using their keys."""
|
||||
@@ -419,7 +417,7 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
for key in keys:
|
||||
del ucollection.embeddings_id_to_label[key]
|
||||
|
||||
return None
|
||||
return
|
||||
|
||||
async def get_nearest_match(
|
||||
self,
|
||||
@@ -506,9 +504,6 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
exact=exact,
|
||||
log=log,
|
||||
)
|
||||
|
||||
# assert isinstance(result, Matches) # nosec
|
||||
|
||||
relevance_score = 1 / (result.distances + 1)
|
||||
filtered_labels = result.keys[np.where(relevance_score >= min_relevance_score)[0]]
|
||||
|
||||
@@ -566,7 +561,7 @@ class USearchMemoryStore(MemoryStoreBase):
|
||||
self._get_collection_path(collection_name, file_type=_CollectionFileType.PARQUET),
|
||||
)
|
||||
|
||||
return None
|
||||
return
|
||||
|
||||
async def close(self) -> None:
|
||||
"""Persist collection, clear.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# Copyright (c) Microsoft. All rights reserved
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from semantic_kernel.connectors.memory.weaviate.weaviate_memory_store import (
|
||||
WeaviateMemoryStore,
|
||||
)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Final
|
||||
|
||||
import numpy as np
|
||||
import weaviate
|
||||
@@ -63,49 +64,46 @@ SCHEMA = {
|
||||
ALL_PROPERTIES = [property["name"] for property in SCHEMA["properties"]]
|
||||
|
||||
|
||||
@experimental_class
|
||||
class WeaviateMemoryStore(MemoryStoreBase):
|
||||
class FieldMapper:
|
||||
"""This maps attribute names between the SK's memory record and weaviate's schema.
|
||||
class FieldMapper:
|
||||
"""This maps attribute names between the SK's memory record and weaviate's schema.
|
||||
|
||||
It provides methods for converting between the two naming conventions.
|
||||
"""
|
||||
It provides methods for converting between the two naming conventions.
|
||||
"""
|
||||
|
||||
SK_TO_WEAVIATE_MAPPING = {
|
||||
"_key": "key",
|
||||
"_timestamp": "timestamp",
|
||||
"_is_reference": "isReference",
|
||||
"_external_source_name": "externalSourceName",
|
||||
"_id": "skId",
|
||||
"_description": "description",
|
||||
"_text": "text",
|
||||
"_additional_metadata": "additionalMetadata",
|
||||
"_embedding": "vector",
|
||||
SK_TO_WEAVIATE_MAPPING: Final[dict[str, str]] = {
|
||||
"_key": "key",
|
||||
"_timestamp": "timestamp",
|
||||
"_is_reference": "isReference",
|
||||
"_external_source_name": "externalSourceName",
|
||||
"_id": "skId",
|
||||
"_description": "description",
|
||||
"_text": "text",
|
||||
"_additional_metadata": "additionalMetadata",
|
||||
"_embedding": "vector",
|
||||
}
|
||||
|
||||
WEAVIATE_TO_SK_MAPPING: Final[dict[str, str]] = {v: k for k, v in SK_TO_WEAVIATE_MAPPING.items()}
|
||||
|
||||
@classmethod
|
||||
def sk_to_weaviate(cls, sk_dict):
|
||||
"""Used to convert a MemoryRecord to a dict of attribute-values that can be used by Weaviate."""
|
||||
return {cls.SK_TO_WEAVIATE_MAPPING.get(k, k): v for k, v in sk_dict.items() if k in cls.SK_TO_WEAVIATE_MAPPING}
|
||||
|
||||
@classmethod
|
||||
def weaviate_to_sk(cls, weaviate_dict):
|
||||
"""Used to convert a Weaviate object to a dict that can be used to initialize a MemoryRecord."""
|
||||
return {
|
||||
cls.WEAVIATE_TO_SK_MAPPING.get(k, k): v for k, v in weaviate_dict.items() if k in cls.WEAVIATE_TO_SK_MAPPING
|
||||
}
|
||||
|
||||
WEAVIATE_TO_SK_MAPPING = {v: k for k, v in SK_TO_WEAVIATE_MAPPING.items()}
|
||||
@classmethod
|
||||
def remove_underscore_prefix(cls, sk_dict):
|
||||
"""Used to initialize a MemoryRecord from a SK's dict of private attribute-values."""
|
||||
return {key.lstrip("_"): value for key, value in sk_dict.items()}
|
||||
|
||||
@classmethod
|
||||
def sk_to_weaviate(cls, sk_dict):
|
||||
"""Used to convert a MemoryRecord to a dict of attribute-values that can be used by Weaviate."""
|
||||
return {
|
||||
cls.SK_TO_WEAVIATE_MAPPING.get(k, k): v for k, v in sk_dict.items() if k in cls.SK_TO_WEAVIATE_MAPPING
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def weaviate_to_sk(cls, weaviate_dict):
|
||||
"""Used to convert a Weaviate object to a dict that can be used to initialize a MemoryRecord."""
|
||||
return {
|
||||
cls.WEAVIATE_TO_SK_MAPPING.get(k, k): v
|
||||
for k, v in weaviate_dict.items()
|
||||
if k in cls.WEAVIATE_TO_SK_MAPPING
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def remove_underscore_prefix(cls, sk_dict):
|
||||
"""Used to initialize a MemoryRecord from a SK's dict of private attribute-values."""
|
||||
return {key.lstrip("_"): value for key, value in sk_dict.items()}
|
||||
|
||||
@experimental_class
|
||||
class WeaviateMemoryStore(MemoryStoreBase):
|
||||
def __init__(
|
||||
self,
|
||||
url: str | None = None,
|
||||
@@ -166,7 +164,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
|
||||
async def upsert(self, collection_name: str, record: MemoryRecord) -> str:
|
||||
"""Upserts a record into Weaviate."""
|
||||
weaviate_record = self.FieldMapper.sk_to_weaviate(vars(record))
|
||||
weaviate_record = FieldMapper.sk_to_weaviate(vars(record))
|
||||
|
||||
vector = weaviate_record.pop("vector", None)
|
||||
weaviate_id = weaviate.util.generate_uuid5(weaviate_record, collection_name)
|
||||
@@ -187,7 +185,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
results = []
|
||||
with self.client.batch as batch:
|
||||
for record in records:
|
||||
weaviate_record = self.FieldMapper.sk_to_weaviate(vars(record))
|
||||
weaviate_record = FieldMapper.sk_to_weaviate(vars(record))
|
||||
vector = weaviate_record.pop("vector", None)
|
||||
weaviate_id = weaviate.util.generate_uuid5(weaviate_record, collection_name)
|
||||
batch.add_data_object(
|
||||
@@ -216,11 +214,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
|
||||
get_dict = results.get("data", {}).get("Get", {})
|
||||
|
||||
memory_records = [
|
||||
self._convert_weaviate_doc_to_memory_record(doc) for docs in get_dict.values() for doc in docs
|
||||
]
|
||||
|
||||
return memory_records
|
||||
return [self._convert_weaviate_doc_to_memory_record(doc) for docs in get_dict.values() for doc in docs]
|
||||
|
||||
def _build_multi_get_query(self, collection_name: str, keys: list[str], with_embedding: bool):
|
||||
queries = []
|
||||
@@ -245,8 +239,8 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
weaviate_doc_copy = weaviate_doc.copy()
|
||||
vector = weaviate_doc_copy.pop("_additional", {}).get("vector")
|
||||
weaviate_doc_copy["vector"] = np.array(vector) if vector else None
|
||||
sk_doc = self.FieldMapper.weaviate_to_sk(weaviate_doc_copy)
|
||||
mem_vals = self.FieldMapper.remove_underscore_prefix(sk_doc)
|
||||
sk_doc = FieldMapper.weaviate_to_sk(weaviate_doc_copy)
|
||||
mem_vals = FieldMapper.remove_underscore_prefix(sk_doc)
|
||||
return MemoryRecord(**mem_vals)
|
||||
|
||||
async def remove(self, collection_name: str, key: str) -> None:
|
||||
@@ -255,10 +249,6 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
|
||||
async def remove_batch(self, collection_name: str, keys: list[str]) -> None:
|
||||
"""Removes a batch of records from Weaviate by keys."""
|
||||
# TODO: Use In operator when it's available
|
||||
# (https://github.com/weaviate/weaviate/issues/2387)
|
||||
# and handle max delete objects
|
||||
# (https://weaviate.io/developers/weaviate/api/rest/batch#maximum-number-of-deletes-per-query)
|
||||
for key in keys:
|
||||
where = {
|
||||
"path": ["key"],
|
||||
@@ -299,7 +289,7 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
|
||||
get_dict = results.get("data", {}).get("Get", {})
|
||||
|
||||
memory_records_and_scores = [
|
||||
return [
|
||||
(
|
||||
self._convert_weaviate_doc_to_memory_record(doc),
|
||||
item["_additional"]["certainty"],
|
||||
@@ -309,8 +299,6 @@ class WeaviateMemoryStore(MemoryStoreBase):
|
||||
for doc in [item]
|
||||
]
|
||||
|
||||
return memory_records_and_scores
|
||||
|
||||
async def get_nearest_match(
|
||||
self,
|
||||
collection_name: str,
|
||||
|
||||
@@ -10,8 +10,8 @@ from semantic_kernel.connectors.openai_plugin.openai_function_execution_paramete
|
||||
from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils
|
||||
|
||||
__all__ = [
|
||||
"OpenAIUtils",
|
||||
"OpenAIFunctionExecutionParameters",
|
||||
"OpenAIAuthenticationConfig",
|
||||
"OpenAIAuthenticationType",
|
||||
"OpenAIFunctionExecutionParameters",
|
||||
"OpenAIUtils",
|
||||
]
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
from typing import Any, Final
|
||||
from urllib.parse import urlencode, urljoin, urlparse, urlunparse
|
||||
|
||||
from semantic_kernel.connectors.openapi_plugin.models.rest_api_operation_expected_response import (
|
||||
@@ -30,7 +30,7 @@ class RestApiOperation:
|
||||
CONTENT_TYPE_ARGUMENT_NAME = "content-type"
|
||||
INVALID_SYMBOLS_REGEX = re.compile(r"[^0-9A-Za-z_]+")
|
||||
|
||||
_preferred_responses: list[str] = [
|
||||
_preferred_responses: Final[list[str]] = [
|
||||
"200",
|
||||
"201",
|
||||
"202",
|
||||
@@ -71,10 +71,7 @@ class RestApiOperation:
|
||||
def url_join(self, base_url: str, path: str):
|
||||
"""Join a base URL and a path, correcting for any missing slashes."""
|
||||
parsed_base = urlparse(base_url)
|
||||
if not parsed_base.path.endswith("/"):
|
||||
base_path = parsed_base.path + "/"
|
||||
else:
|
||||
base_path = parsed_base.path
|
||||
base_path = parsed_base.path + "/" if not parsed_base.path.endswith("/") else parsed_base.path
|
||||
full_path = urljoin(base_path, path.lstrip("/"))
|
||||
return urlunparse(parsed_base._replace(path=full_path))
|
||||
|
||||
@@ -113,7 +110,9 @@ class RestApiOperation:
|
||||
server_url_string = (
|
||||
self.server_url.geturl()
|
||||
if self.server_url
|
||||
else api_host_url.geturl() if api_host_url else self._raise_invalid_operation_exception()
|
||||
else api_host_url.geturl()
|
||||
if api_host_url
|
||||
else self._raise_invalid_operation_exception()
|
||||
)
|
||||
|
||||
# make sure the base URL ends with a trailing slash
|
||||
@@ -218,7 +217,7 @@ class RestApiOperation:
|
||||
self,
|
||||
properties: list["RestApiOperationPayloadProperty"],
|
||||
enable_namespacing: bool = False,
|
||||
root_property_name: bool = None,
|
||||
root_property_name: bool | None = None,
|
||||
) -> list["RestApiOperationParameter"]:
|
||||
parameters: list[RestApiOperationParameter] = []
|
||||
for property in properties:
|
||||
|
||||
@@ -118,8 +118,7 @@ def _create_function_from_operation(
|
||||
api_host_url=Uri(document_uri).get_left_part() if document_uri is not None else None,
|
||||
)
|
||||
|
||||
response = await runner.run_operation(operation, kernel_arguments, options)
|
||||
return response
|
||||
return await runner.run_operation(operation, kernel_arguments, options)
|
||||
except Exception as e:
|
||||
logger.error(f"Error running OpenAPI operation: {operation.id}", exc_info=True)
|
||||
raise FunctionExecutionException(f"Error running OpenAPI operation: {operation.id}") from e
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
from collections.abc import Generator
|
||||
from typing import TYPE_CHECKING, Any
|
||||
from typing import TYPE_CHECKING, Any, Final
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from prance import ResolvingParser
|
||||
@@ -51,8 +51,8 @@ class OpenApiParser:
|
||||
:return: The parsed OpenAPI file
|
||||
"""
|
||||
|
||||
PAYLOAD_PROPERTIES_HIERARCHY_MAX_DEPTH = 10
|
||||
supported_media_types = ["application/json", "text/plain"]
|
||||
PAYLOAD_PROPERTIES_HIERARCHY_MAX_DEPTH: int = 10
|
||||
SUPPORTED_MEDIA_TYPES: Final[list[str]] = ["application/json", "text/plain"]
|
||||
|
||||
def parse(self, openapi_document: str) -> Any | dict[str, Any] | None:
|
||||
"""Parse the OpenAPI document."""
|
||||
@@ -121,7 +121,7 @@ class OpenApiParser:
|
||||
) -> RestApiOperationPayload:
|
||||
if request_body is None or request_body.get("content") is None:
|
||||
return None
|
||||
media_type = next((mt for mt in OpenApiParser.supported_media_types if mt in request_body.get("content")), None)
|
||||
media_type = next((mt for mt in OpenApiParser.SUPPORTED_MEDIA_TYPES if mt in request_body.get("content")), None)
|
||||
if media_type is None:
|
||||
raise Exception(f"Neither of the media types of {operation_id} is supported.")
|
||||
media_type_metadata = request_body.get("content")[media_type]
|
||||
@@ -131,7 +131,7 @@ class OpenApiParser:
|
||||
return RestApiOperationPayload(
|
||||
media_type,
|
||||
payload_properties,
|
||||
request_body.get("description", None),
|
||||
request_body.get("description"),
|
||||
schema=media_type_metadata.get("schema", None),
|
||||
)
|
||||
|
||||
@@ -140,7 +140,7 @@ class OpenApiParser:
|
||||
) -> Generator[tuple[str, RestApiOperationExpectedResponse], None, None]:
|
||||
for response_key, response_value in responses.items():
|
||||
media_type = next(
|
||||
(mt for mt in OpenApiParser.supported_media_types if mt in response_value.get("content", {})), None
|
||||
(mt for mt in OpenApiParser.SUPPORTED_MEDIA_TYPES if mt in response_value.get("content", {})), None
|
||||
)
|
||||
if media_type is not None:
|
||||
matching_schema = response_value["content"][media_type].get("schema", {})
|
||||
|
||||
@@ -163,8 +163,7 @@ class OpenApiRunner:
|
||||
|
||||
if hasattr(self, "http_client") and self.http_client is not None:
|
||||
return await make_request(self.http_client)
|
||||
else:
|
||||
async with httpx.AsyncClient() as client:
|
||||
return await make_request(client)
|
||||
async with httpx.AsyncClient() as client:
|
||||
return await make_request(client)
|
||||
|
||||
return await fetch()
|
||||
|
||||
@@ -32,7 +32,7 @@ class BingConnector(ConnectorBase):
|
||||
the settings are read from this file path location.
|
||||
env_file_encoding (str | None): The optional encoding of the .env file.
|
||||
"""
|
||||
self.settings = BingSettings.create(
|
||||
self._settings = BingSettings.create(
|
||||
api_key=api_key,
|
||||
env_file_path=env_file_path,
|
||||
env_file_encoding=env_file_encoding,
|
||||
@@ -63,12 +63,14 @@ class BingConnector(ConnectorBase):
|
||||
|
||||
headers = {"Ocp-Apim-Subscription-Key": self._settings.api_key.get_secret_value()}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(_request_url, headers=headers, raise_for_status=True) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
pages = data.get("webPages", {}).get("value")
|
||||
if pages:
|
||||
return list(map(lambda x: x["snippet"], pages)) or []
|
||||
else:
|
||||
return []
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.get(_request_url, headers=headers, raise_for_status=True) as response,
|
||||
):
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
pages = data.get("webPages", {}).get("value")
|
||||
if pages:
|
||||
return list(map(lambda x: x["snippet"], pages)) or []
|
||||
return None
|
||||
return []
|
||||
|
||||
@@ -55,13 +55,11 @@ class GoogleConnector(ConnectorBase):
|
||||
|
||||
logger.info("Sending GET request to Google Search API.")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(_request_url, raise_for_status=True) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
logger.info("Request successful.")
|
||||
logger.info(f"API Response: {data}")
|
||||
return [x["snippet"] for x in data["items"]]
|
||||
else:
|
||||
logger.error(f"Request to Google Search API failed with status code: {response.status}.")
|
||||
return []
|
||||
async with aiohttp.ClientSession() as session, session.get(_request_url, raise_for_status=True) as response:
|
||||
if response.status == 200:
|
||||
data = await response.json()
|
||||
logger.info("Request successful.")
|
||||
logger.info(f"API Response: {data}")
|
||||
return [x["snippet"] for x in data["items"]]
|
||||
logger.error(f"Request to Google Search API failed with status code: {response.status}.")
|
||||
return []
|
||||
|
||||
@@ -10,12 +10,12 @@ from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
|
||||
__all__ = [
|
||||
"ChatMessageContent",
|
||||
"ChatHistory",
|
||||
"AuthorRole",
|
||||
"TextContent",
|
||||
"StreamingChatMessageContent",
|
||||
"StreamingTextContent",
|
||||
"ChatHistory",
|
||||
"ChatMessageContent",
|
||||
"FunctionCallContent",
|
||||
"FunctionResultContent",
|
||||
"StreamingChatMessageContent",
|
||||
"StreamingTextContent",
|
||||
"TextContent",
|
||||
]
|
||||
|
||||
@@ -14,12 +14,12 @@ from semantic_kernel.core_plugins.time_plugin import TimePlugin
|
||||
from semantic_kernel.core_plugins.web_search_engine_plugin import WebSearchEnginePlugin
|
||||
|
||||
__all__ = [
|
||||
"ConversationSummaryPlugin",
|
||||
"HttpPlugin",
|
||||
"MathPlugin",
|
||||
"SessionsPythonTool",
|
||||
"TextMemoryPlugin",
|
||||
"TextPlugin",
|
||||
"TimePlugin",
|
||||
"HttpPlugin",
|
||||
"ConversationSummaryPlugin",
|
||||
"MathPlugin",
|
||||
"SessionsPythonTool",
|
||||
"WebSearchEnginePlugin",
|
||||
]
|
||||
|
||||
@@ -17,8 +17,8 @@ class ConversationSummaryPlugin:
|
||||
|
||||
_summarize_conversation_prompt_template = (
|
||||
"BEGIN CONTENT TO SUMMARIZE:\n{{"
|
||||
+ "$input"
|
||||
+ "}}\nEND CONTENT TO SUMMARIZE.\nSummarize the conversation in 'CONTENT TO"
|
||||
"$input"
|
||||
"}}\nEND CONTENT TO SUMMARIZE.\nSummarize the conversation in 'CONTENT TO"
|
||||
" SUMMARIZE', identifying main points of discussion and any"
|
||||
" conclusions that were reached.\nDo not incorporate other general"
|
||||
" knowledge.\nSummary is in plain text, in complete sentences, with no markup"
|
||||
|
||||
@@ -36,9 +36,8 @@ class HttpPlugin(KernelBaseModel):
|
||||
if not url:
|
||||
raise FunctionExecutionException("url cannot be `None` or empty")
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
async with aiohttp.ClientSession() as session, session.get(url, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
|
||||
@kernel_function(description="Makes a POST request to a uri", name="postAsync")
|
||||
async def post(
|
||||
@@ -59,9 +58,11 @@ class HttpPlugin(KernelBaseModel):
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = json.dumps(body)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(url, headers=headers, data=data, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.post(url, headers=headers, data=data, raise_for_status=True) as response,
|
||||
):
|
||||
return await response.text()
|
||||
|
||||
@kernel_function(description="Makes a PUT request to a uri", name="putAsync")
|
||||
async def put(
|
||||
@@ -83,9 +84,11 @@ class HttpPlugin(KernelBaseModel):
|
||||
|
||||
headers = {"Content-Type": "application/json"}
|
||||
data = json.dumps(body)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.put(url, headers=headers, data=data, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
async with (
|
||||
aiohttp.ClientSession() as session,
|
||||
session.put(url, headers=headers, data=data, raise_for_status=True) as response,
|
||||
):
|
||||
return await response.text()
|
||||
|
||||
@kernel_function(description="Makes a DELETE request to a uri", name="deleteAsync")
|
||||
async def delete(self, url: Annotated[str, "The URI to send the request to."]) -> str:
|
||||
@@ -99,6 +102,5 @@ class HttpPlugin(KernelBaseModel):
|
||||
"""
|
||||
if not url:
|
||||
raise FunctionExecutionException("url cannot be `None` or empty")
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.delete(url, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
async with aiohttp.ClientSession() as session, session.delete(url, raise_for_status=True) as response:
|
||||
return await response.text()
|
||||
|
||||
@@ -7,4 +7,4 @@ from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_settings
|
||||
SessionsPythonSettings,
|
||||
)
|
||||
|
||||
__all__ = ["SessionsPythonTool", "SessionsPythonSettings"]
|
||||
__all__ = ["SessionsPythonSettings", "SessionsPythonTool"]
|
||||
|
||||
+16
-11
@@ -57,8 +57,8 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
env_file_path=env_file_path, pool_management_endpoint=pool_management_endpoint
|
||||
)
|
||||
except ValidationError as e:
|
||||
logger.error(f"Failed to load the ACASessionsSettings with message: {str(e)}")
|
||||
raise FunctionExecutionException(f"Failed to load the ACASessionsSettings with message: {str(e)}") from e
|
||||
logger.error(f"Failed to load the ACASessionsSettings with message: {e!s}")
|
||||
raise FunctionExecutionException(f"Failed to load the ACASessionsSettings with message: {e!s}") from e
|
||||
|
||||
endpoint = pool_management_endpoint or aca_settings.pool_management_endpoint
|
||||
|
||||
@@ -89,8 +89,8 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
try:
|
||||
auth_token = await self.auth_callback()
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to retrieve the client auth token with message: {str(e)}")
|
||||
raise FunctionExecutionException(f"Failed to retrieve the client auth token with messages: {str(e)}") from e
|
||||
logger.error(f"Failed to retrieve the client auth token with message: {e!s}")
|
||||
raise FunctionExecutionException(f"Failed to retrieve the client auth token with messages: {e!s}") from e
|
||||
|
||||
return auth_token
|
||||
|
||||
@@ -107,8 +107,7 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
# Removes `, whitespace & python from start
|
||||
code = re.sub(r"^(\s|`)*(?i:python)?\s*", "", code)
|
||||
# Removes whitespace & ` from end
|
||||
code = re.sub(r"(\s|`)*$", "", code)
|
||||
return code
|
||||
return re.sub(r"(\s|`)*$", "", code)
|
||||
|
||||
@kernel_function(
|
||||
description="""Executes the provided Python code.
|
||||
@@ -162,11 +161,15 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
response.raise_for_status()
|
||||
|
||||
result = response.json()
|
||||
return f"Result:\n{result['result']}Stdout:\n{result['stdout']}Stderr:\n{result['stderr']}" # noqa: E501
|
||||
return f"Result:\n{result['result']}Stdout:\n{result['stdout']}Stderr:\n{result['stderr']}"
|
||||
|
||||
@kernel_function(name="upload_file", description="Uploads a file for the current Session ID")
|
||||
async def upload_file(
|
||||
self, *, data: BufferedReader = None, remote_file_path: str = None, local_file_path: str = None
|
||||
self,
|
||||
*,
|
||||
data: BufferedReader | None = None,
|
||||
remote_file_path: str | None = None,
|
||||
local_file_path: str | None = None,
|
||||
) -> SessionsRemoteFileMetadata:
|
||||
"""Upload a file to the session pool.
|
||||
|
||||
@@ -184,7 +187,7 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
if local_file_path:
|
||||
if not remote_file_path:
|
||||
remote_file_path = os.path.basename(local_file_path)
|
||||
data = open(local_file_path, "rb")
|
||||
data = open(local_file_path, "rb") # noqa: SIM115
|
||||
|
||||
auth_token = await self._ensure_auth_token()
|
||||
self.http_client.headers.update(
|
||||
@@ -229,7 +232,9 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
response_json = response.json()
|
||||
return [SessionsRemoteFileMetadata.from_dict(entry) for entry in response_json["$values"]]
|
||||
|
||||
async def download_file(self, *, remote_file_path: str, local_file_path: str = None) -> BufferedReader | None:
|
||||
async def download_file(
|
||||
self, *, remote_file_path: str, local_file_path: str | None = None
|
||||
) -> BufferedReader | None:
|
||||
"""Download a file from the session pool.
|
||||
|
||||
Args:
|
||||
@@ -249,7 +254,7 @@ class SessionsPythonTool(KernelBaseModel):
|
||||
)
|
||||
|
||||
response = await self.http_client.get(
|
||||
url=f"{self.pool_management_endpoint}python/downloadFile?identifier={self.settings.session_id}&filename={remote_file_path}", # noqa: E501
|
||||
url=f"{self.pool_management_endpoint}python/downloadFile?identifier={self.settings.session_id}&filename={remote_file_path}",
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from semantic_kernel.exceptions.content_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.function_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.kernel_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.memory_connector_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.planner_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.service_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.template_engine_exceptions import * # noqa: F401, F403
|
||||
from semantic_kernel.exceptions.content_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.function_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.kernel_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.memory_connector_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.planner_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.service_exceptions import * # noqa: F403
|
||||
from semantic_kernel.exceptions.template_engine_exceptions import * # noqa: F403
|
||||
|
||||
@@ -28,10 +28,10 @@ class FunctionCallInvalidArgumentsException(ContentException):
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ContentException",
|
||||
"ContentSerializationError",
|
||||
"ContentInitializationError",
|
||||
"ContentAdditionException",
|
||||
"FunctionCallInvalidNameException",
|
||||
"ContentException",
|
||||
"ContentInitializationError",
|
||||
"ContentSerializationError",
|
||||
"FunctionCallInvalidArgumentsException",
|
||||
"FunctionCallInvalidNameException",
|
||||
]
|
||||
|
||||
@@ -50,14 +50,14 @@ class PromptRenderingException(FunctionException):
|
||||
|
||||
__all__ = [
|
||||
"FunctionException",
|
||||
"FunctionExecutionException",
|
||||
"FunctionInitializationError",
|
||||
"FunctionInvalidParamNameError",
|
||||
"FunctionInvalidNameError",
|
||||
"FunctionInvalidParamNameError",
|
||||
"FunctionNameNotUniqueError",
|
||||
"FunctionResultError",
|
||||
"FunctionSyntaxError",
|
||||
"PluginInitializationError",
|
||||
"PluginInvalidNameError",
|
||||
"FunctionExecutionException",
|
||||
"FunctionResultError",
|
||||
"PromptRenderingException",
|
||||
]
|
||||
|
||||
@@ -47,8 +47,8 @@ __all__ = [
|
||||
"KernelFunctionAlreadyExistsError",
|
||||
"KernelFunctionNotFoundError",
|
||||
"KernelInvokeException",
|
||||
"KernelPluginInvalidConfigurationError",
|
||||
"KernelPluginNotFoundError",
|
||||
"KernelServiceNotFoundError",
|
||||
"KernelPluginInvalidConfigurationError",
|
||||
"OperationCancelledException",
|
||||
]
|
||||
|
||||
@@ -29,10 +29,10 @@ class PlannerCreatePlanError(PlannerException):
|
||||
|
||||
|
||||
__all__ = [
|
||||
"PlannerCreatePlanError",
|
||||
"PlannerException",
|
||||
"PlannerExecutionException",
|
||||
"PlannerInvalidConfigurationError",
|
||||
"PlannerInvalidGoalError",
|
||||
"PlannerInvalidPlanError",
|
||||
"PlannerInvalidConfigurationError",
|
||||
"PlannerCreatePlanError",
|
||||
]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user