mirror of
https://github.com/microsoft/semantic-kernel.git
synced 2026-10-12 02:19:06 +00:00
Python: experimental py3.13 support (#10276)
### 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. --> Some small fixes in uv setup within actions Added experimental unit tests runs for py3.13 First typing improvement from pyright and pyright config. ### 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:
@@ -21,6 +21,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Check version
|
||||
run: |
|
||||
echo "Building and uploading Python package version: ${{ github.event.release.tag_name }}"
|
||||
|
||||
@@ -120,6 +120,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
@@ -257,6 +258,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
@@ -321,6 +323,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
@@ -379,6 +382,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
uv sync --all-extras --dev
|
||||
|
||||
@@ -30,6 +30,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
- uses: pre-commit/[email protected]
|
||||
|
||||
@@ -32,6 +32,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ env.UV_PYTHON }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev
|
||||
- name: Test with pytest
|
||||
|
||||
@@ -20,18 +20,10 @@ jobs:
|
||||
python-version: ["3.10", "3.11", "3.12"]
|
||||
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||
experimental: [false]
|
||||
# include:
|
||||
# - python-version: "3.13"
|
||||
# os: "ubuntu-latest"
|
||||
# experimental: true
|
||||
# - python-version: "3.13t"
|
||||
# os: "ubuntu-latest"
|
||||
# experimental: true
|
||||
# gil: 0
|
||||
# - python-version: "3.13t"
|
||||
# os: "ubuntu-latest"
|
||||
# experimental: true
|
||||
# gil: 1
|
||||
include:
|
||||
- python-version: "3.13"
|
||||
os: "ubuntu-latest"
|
||||
experimental: true
|
||||
env:
|
||||
UV_PYTHON: ${{ matrix.python-version }}
|
||||
permissions:
|
||||
@@ -47,6 +39,7 @@ jobs:
|
||||
version: "0.5.x"
|
||||
enable-cache: true
|
||||
cache-suffix: ${{ runner.os }}-${{ matrix.python-version }}
|
||||
cache-dependency-glob: "**/uv.lock"
|
||||
- name: Install the project
|
||||
run: uv sync --all-extras --dev -U --prerelease=if-necessary-or-explicit
|
||||
- name: Test with pytest
|
||||
|
||||
@@ -32,14 +32,14 @@ repos:
|
||||
- id: pyupgrade
|
||||
args: [--py310-plus]
|
||||
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||
rev: v0.7.4
|
||||
rev: v0.9.6
|
||||
hooks:
|
||||
- id: ruff
|
||||
args: [ --fix, --exit-non-zero-on-fix ]
|
||||
- id: ruff-format
|
||||
- repo: https://github.com/astral-sh/uv-pre-commit
|
||||
# uv version.
|
||||
rev: 0.5.21
|
||||
rev: 0.5.30
|
||||
hooks:
|
||||
# Update the uv lockfile
|
||||
- id: uv-lock
|
||||
|
||||
+4
-4
@@ -32,7 +32,7 @@ help:
|
||||
echo ""
|
||||
echo -e "\033[1mVARIABLES:\033[0m"
|
||||
echo " PYTHON_VERSION - Python version to use. Default is 3.10"
|
||||
echo " By default, 3.10, 3.11, and 3.12 are installed as well."
|
||||
echo " By default, 3.10, 3.11, 3.12 and 3.13 are installed as well."
|
||||
|
||||
##############################
|
||||
# INSTALL
|
||||
@@ -68,8 +68,8 @@ endif
|
||||
##############################
|
||||
.ONESHELL:
|
||||
install-python:
|
||||
echo "Installing python 3.10, 3.11, 3.12"
|
||||
uv python install 3.10 3.11 3.12
|
||||
echo "Installing python versions"
|
||||
uv python install 3.10 3.11 3.12 3.13
|
||||
|
||||
##############################
|
||||
# INSTALL-PRE-COMMIT
|
||||
@@ -87,7 +87,7 @@ install-sk:
|
||||
echo "Creating and activating venv for python $(PYTHON_VERSION)"
|
||||
uv venv --python $(PYTHON_VERSION)
|
||||
echo "Installing Semantic Kernel and all dependencies"
|
||||
uv sync --all-extras --dev
|
||||
uv sync --all-extras --dev --prerelease=if-necessary-or-explicit
|
||||
|
||||
##############################
|
||||
# CLEAN
|
||||
|
||||
@@ -45,6 +45,7 @@ dependencies = [
|
||||
"pybars4 ~= 0.9",
|
||||
"jinja2 ~= 3.1",
|
||||
"nest-asyncio ~= 1.6",
|
||||
"scipy>=1.15.1",
|
||||
]
|
||||
|
||||
### Optional dependencies
|
||||
@@ -62,7 +63,7 @@ chroma = [
|
||||
]
|
||||
google = [
|
||||
"google-cloud-aiplatform == 1.79.0",
|
||||
"google-generativeai == 0.7"
|
||||
"google-generativeai ~= 0.8"
|
||||
]
|
||||
hugging_face = [
|
||||
"transformers[torch] ~= 4.28",
|
||||
@@ -87,7 +88,7 @@ ollama = [
|
||||
"ollama ~= 0.4"
|
||||
]
|
||||
onnx = [
|
||||
"onnxruntime-genai ~= 0.5"
|
||||
"onnxruntime-genai ~= 0.5; python_version < '3.13'"
|
||||
]
|
||||
anthropic = [
|
||||
"anthropic ~= 0.32"
|
||||
@@ -107,11 +108,11 @@ redis = [
|
||||
"redisvl >= 0.3.6",
|
||||
]
|
||||
usearch = [
|
||||
"usearch ~= 2.9",
|
||||
"usearch ~= 2.16",
|
||||
"pyarrow >= 12.0,< 20.0"
|
||||
]
|
||||
weaviate = [
|
||||
"weaviate-client>=4.7,<5.0",
|
||||
"weaviate-client>=4.10,<5.0",
|
||||
]
|
||||
pandas = [
|
||||
"pandas ~= 2.2"
|
||||
@@ -139,7 +140,8 @@ dev-dependencies = [
|
||||
"snoop ~= 0.4",
|
||||
"mypy >= 1.10",
|
||||
"types-PyYAML ~= 6.0.12.20240311",
|
||||
"ruff ~= 0.7",
|
||||
"ruff ~= 0.9",
|
||||
"pyright>=1.1.392.post0",
|
||||
]
|
||||
environments = [
|
||||
"sys_platform == 'darwin'",
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"reportMissingImports": false,
|
||||
"include": [
|
||||
"semantic_kernel/**/*.py"
|
||||
],
|
||||
"pythonVersion": "3.10",
|
||||
"exclude": [
|
||||
"semantic_kernel/memory/*",
|
||||
"semantic_kernel/planners/*",
|
||||
"semantic_kernel/connectors/memory/astradb/*",
|
||||
"semantic_kernel/connectors/memory/azure_ai_search/*",
|
||||
"semantic_kernel/connectors/memory/azure_cognitive_search/*",
|
||||
"semantic_kernel/connectors/memory/azure_cosmosdb/*",
|
||||
"semantic_kernel/connectors/memory/azure_cosmosdb_no_sql/*",
|
||||
"semantic_kernel/connectors/memory/chroma/*",
|
||||
"semantic_kernel/connectors/memory/milvus/*",
|
||||
"semantic_kernel/connectors/memory/mongodb_atlas/*",
|
||||
"semantic_kernel/connectors/memory/pinecone/*",
|
||||
"semantic_kernel/connectors/memory/postgres/*",
|
||||
"semantic_kernel/connectors/memory/qdrant/*",
|
||||
"semantic_kernel/connectors/memory/redis/*",
|
||||
"semantic_kernel/connectors/memory/usearch/*",
|
||||
"semantic_kernel/connectors/memory/weaviate/*"
|
||||
],
|
||||
"venvPath": ".",
|
||||
"venv": ".venv"
|
||||
}
|
||||
@@ -102,12 +102,6 @@
|
||||
- [Azure Chat GPT with Data API Function Calling](./on_your_data/azure_chat_gpt_with_data_api_function_calling.py)
|
||||
- [Azure Chat GPT with Data API Vector Search](./on_your_data/azure_chat_gpt_with_data_api_vector_search.py)
|
||||
|
||||
### Planners - Showing the uses of [`Planners`](https://github.com/microsoft/semantic-kernel/tree/main/python/semantic_kernel/planners)
|
||||
|
||||
- [Sequential Planner](./planners/sequential_planner.py)
|
||||
- [OpenAI Function Calling Stepwise Planner](./planners/openai_function_calling_stepwise_planner.py)
|
||||
- [Azure OpenAI Function Calling Stepwise Planner](./planners/azure_openai_function_calling_stepwise_planner.py)
|
||||
|
||||
### Plugins - Different ways of creating and using [`Plugins`](https://github.com/microsoft/semantic-kernel/blob/main/python/semantic_kernel/functions/kernel_plugin.py)
|
||||
|
||||
- [Azure Key Vault Settings](./plugins/azure_key_vault_settings.py)
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ chat_completion_service, request_settings = get_chat_completion_service_and_requ
|
||||
|
||||
# Configure the function choice behavior. Here, we set it to Auto, where auto_invoke=True by default.
|
||||
# With `auto_invoke=True`, the model will automatically choose and call functions as needed.
|
||||
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto()
|
||||
request_settings.function_choice_behavior = FunctionChoiceBehavior.Auto(filters={"excluded_plugins": ["ChatBot"]})
|
||||
|
||||
kernel.add_service(chat_completion_service)
|
||||
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
|
||||
from semantic_kernel.core_plugins import MathPlugin, TimePlugin
|
||||
from semantic_kernel.planners import FunctionCallingStepwisePlanner, FunctionCallingStepwisePlannerOptions
|
||||
|
||||
|
||||
async def main():
|
||||
kernel = Kernel()
|
||||
|
||||
service_id = "planner"
|
||||
kernel.add_service(
|
||||
AzureChatCompletion(
|
||||
service_id=service_id,
|
||||
),
|
||||
)
|
||||
|
||||
plugin_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||
"resources",
|
||||
)
|
||||
kernel.add_plugin(parent_directory=plugin_path, plugin_name="email_plugin")
|
||||
|
||||
kernel.add_plugin(MathPlugin(), "MathPlugin")
|
||||
kernel.add_plugin(TimePlugin(), "TimePlugin")
|
||||
|
||||
questions = [
|
||||
"What is the current hour number, plus 5?",
|
||||
"What is 387 minus 22? Email the solution to John and Mary.",
|
||||
"Write a limerick, translate it to Spanish, and send it to Jane",
|
||||
]
|
||||
|
||||
options = FunctionCallingStepwisePlannerOptions(
|
||||
max_iterations=10,
|
||||
max_tokens=4000,
|
||||
)
|
||||
|
||||
planner = FunctionCallingStepwisePlanner(service_id=service_id, options=options)
|
||||
|
||||
for question in questions:
|
||||
result = await planner.invoke(kernel, question)
|
||||
print(f"Q: {question}\nA: {result.final_answer}\n")
|
||||
|
||||
# Uncomment the following line to view the planner's process for completing the request
|
||||
# print(f"Chat history: {result.chat_history}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,51 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
|
||||
from semantic_kernel.core_plugins import MathPlugin, TimePlugin
|
||||
from semantic_kernel.planners import FunctionCallingStepwisePlanner, FunctionCallingStepwisePlannerOptions
|
||||
|
||||
|
||||
async def main():
|
||||
kernel = Kernel()
|
||||
|
||||
service_id = "planner"
|
||||
kernel.add_service(
|
||||
OpenAIChatCompletion(
|
||||
service_id=service_id,
|
||||
),
|
||||
)
|
||||
|
||||
plugin_path = os.path.join(
|
||||
os.path.dirname(os.path.dirname(os.path.realpath(__file__))),
|
||||
"resources",
|
||||
)
|
||||
kernel.add_plugin(parent_directory=plugin_path, plugin_name="email_plugin")
|
||||
kernel.add_plugins({"MathPlugin": MathPlugin(), "TimePlugin": TimePlugin()})
|
||||
|
||||
questions = [
|
||||
"What is the current hour number, plus 5?",
|
||||
"What is 387 minus 22? Email the solution to John and Mary.",
|
||||
"Write a limerick, translate it to Spanish, and send it to Jane",
|
||||
]
|
||||
|
||||
options = FunctionCallingStepwisePlannerOptions(
|
||||
max_iterations=10,
|
||||
max_tokens=4000,
|
||||
)
|
||||
|
||||
planner = FunctionCallingStepwisePlanner(service_id=service_id, options=options)
|
||||
|
||||
for question in questions:
|
||||
result = await planner.invoke(kernel, question)
|
||||
print(f"Q: {question}\nA: {result.final_answer}\n")
|
||||
|
||||
# Uncomment the following line to view the planner's process for completing the request
|
||||
# print(f"\nChat history: {result.chat_history}\n")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,42 +0,0 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion
|
||||
from semantic_kernel.core_plugins import MathPlugin, TextPlugin, TimePlugin
|
||||
from semantic_kernel.planners import SequentialPlanner
|
||||
|
||||
|
||||
async def main():
|
||||
kernel = Kernel()
|
||||
|
||||
service_id = "gpt-3.5"
|
||||
kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo"))
|
||||
kernel.add_plugins({"math": MathPlugin(), "time": TimePlugin(), "text": TextPlugin()})
|
||||
|
||||
# create an instance of sequential planner.
|
||||
planner = SequentialPlanner(service_id=service_id, kernel=kernel)
|
||||
|
||||
# the ask for which the sequential planner is going to find a relevant function.
|
||||
ask = "What day of the week is today, all uppercase?"
|
||||
|
||||
# ask the sequential planner to identify a suitable function from the list of functions available.
|
||||
plan = await planner.create_plan(goal=ask)
|
||||
|
||||
# ask the sequential planner to execute the identified function.
|
||||
result = await plan.invoke(kernel=kernel)
|
||||
|
||||
for step in plan._steps:
|
||||
print(step.description, ":", step._state.__dict__)
|
||||
|
||||
print("Expected Answer:")
|
||||
print(result)
|
||||
"""
|
||||
Output:
|
||||
SUNDAY
|
||||
"""
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -17,6 +17,8 @@ if TYPE_CHECKING:
|
||||
from semantic_kernel.agents import Agent
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
|
||||
NEWLINE = "\n"
|
||||
|
||||
|
||||
@experimental_class
|
||||
class CustomSelectionStrategy(SelectionStrategy):
|
||||
@@ -80,7 +82,7 @@ Each message in the chat history contains the agent's name and the message conte
|
||||
Initially, the chat history may be empty.
|
||||
|
||||
Here are the agents with their indices, names, and descriptions:
|
||||
{"\n".join(f"[{index}] {agent.name}:\n{agent.description}" for index, agent in enumerate(agents))}
|
||||
{NEWLINE.join(f"[{index}] {agent.name}:{NEWLINE}{agent.description}" for index, agent in enumerate(agents))}
|
||||
|
||||
Your task is to select the next agent based on the conversation history.
|
||||
|
||||
|
||||
@@ -20,6 +20,8 @@ if TYPE_CHECKING:
|
||||
TERMINATE_TRUE_KEYWORD = "yes"
|
||||
TERMINATE_FALSE_KEYWORD = "no"
|
||||
|
||||
NEWLINE = "\n"
|
||||
|
||||
|
||||
class CustomTerminationStrategy(TerminationStrategy):
|
||||
NUM_OF_RETRIES: ClassVar[int] = 3
|
||||
@@ -82,7 +84,7 @@ Each message in the chat history contains the agent's name and the message conte
|
||||
The chat history may start empty as no agents have spoken yet.
|
||||
|
||||
Here are the agents with their indices, names, and descriptions:
|
||||
{"\n".join(f"[{index}] {agent.name}:\n{agent.description}" for index, agent in enumerate(self.agents))}
|
||||
{NEWLINE.join(f"[{index}] {agent.name}:{NEWLINE}{agent.description}" for index, agent in enumerate(self.agents))}
|
||||
|
||||
Your task is NOT to continue the conversation. Determine if the latest content is approved by all agents.
|
||||
If approved, say "{TERMINATE_TRUE_KEYWORD}". Otherwise, say "{TERMINATE_FALSE_KEYWORD}".
|
||||
|
||||
@@ -152,7 +152,7 @@ class Agenda:
|
||||
return "None"
|
||||
agenda_str = "\n".join(
|
||||
[
|
||||
f"{i+1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}"
|
||||
f"{i + 1}. [{format_resource(item['resource'], ResourceConstraintUnit.TURNS)}] {item['title']}"
|
||||
for i, item in enumerate(agenda_items)
|
||||
]
|
||||
)
|
||||
|
||||
@@ -205,7 +205,7 @@ class GCResource:
|
||||
resource_instructions = ""
|
||||
|
||||
if self.resource_constraint.mode == ResourceConstraintMode.EXACT:
|
||||
exact_mode_instructions = f"""There {'are' if is_plural_remaining else 'is'} {formatted_remaining_resource} remaining (including this one) - the conversation will automatically terminate when 0 turns are left. \
|
||||
exact_mode_instructions = f"""There {"are" if is_plural_remaining else "is"} {formatted_remaining_resource} remaining (including this one) - the conversation will automatically terminate when 0 turns are left. \
|
||||
You should continue the conversation until it is automatically terminated. This means you should NOT preemptively end the conversation, \
|
||||
either explicitly (by selecting the "End conversation" action) or implicitly (e.g. by telling the user that you have all required information and they should wait for the next step). \
|
||||
Your goal is not to maximize efficiency (i.e. complete the artifact as quickly as possible then end the conversation), but rather to make the best use of ALL remaining turns available to you"""
|
||||
|
||||
+8
-4
@@ -127,8 +127,10 @@ class GoogleAIChatCompletion(GoogleAIBase, ChatCompletionClientBase):
|
||||
assert isinstance(settings, GoogleAIChatPromptExecutionSettings) # nosec
|
||||
|
||||
genai.configure(api_key=self.service_settings.api_key.get_secret_value())
|
||||
if not self.service_settings.gemini_model_id:
|
||||
raise ServiceInitializationError("The Google AI Gemini model ID is required.")
|
||||
model = GenerativeModel(
|
||||
self.service_settings.gemini_model_id,
|
||||
model_name=self.service_settings.gemini_model_id,
|
||||
system_instruction=filter_system_message(chat_history),
|
||||
)
|
||||
|
||||
@@ -136,7 +138,7 @@ class GoogleAIChatCompletion(GoogleAIBase, ChatCompletionClientBase):
|
||||
contents=self._prepare_chat_history_for_request(chat_history),
|
||||
generation_config=GenerationConfig(**settings.prepare_settings_dict()),
|
||||
tools=settings.tools,
|
||||
tool_config=settings.tool_config,
|
||||
tool_config=settings.tool_config, # type: ignore
|
||||
)
|
||||
|
||||
return [self._create_chat_message_content(response, candidate) for candidate in response.candidates]
|
||||
@@ -154,8 +156,10 @@ class GoogleAIChatCompletion(GoogleAIBase, ChatCompletionClientBase):
|
||||
assert isinstance(settings, GoogleAIChatPromptExecutionSettings) # nosec
|
||||
|
||||
genai.configure(api_key=self.service_settings.api_key.get_secret_value())
|
||||
if not self.service_settings.gemini_model_id:
|
||||
raise ServiceInitializationError("The Google AI Gemini model ID is required.")
|
||||
model = GenerativeModel(
|
||||
self.service_settings.gemini_model_id,
|
||||
model_name=self.service_settings.gemini_model_id,
|
||||
system_instruction=filter_system_message(chat_history),
|
||||
)
|
||||
|
||||
@@ -163,7 +167,7 @@ class GoogleAIChatCompletion(GoogleAIBase, ChatCompletionClientBase):
|
||||
contents=self._prepare_chat_history_for_request(chat_history),
|
||||
generation_config=GenerationConfig(**settings.prepare_settings_dict()),
|
||||
tools=settings.tools,
|
||||
tool_config=settings.tool_config,
|
||||
tool_config=settings.tool_config, # type: ignore
|
||||
stream=True,
|
||||
)
|
||||
|
||||
|
||||
+6
-2
@@ -99,8 +99,10 @@ class GoogleAITextCompletion(GoogleAIBase, TextCompletionClientBase):
|
||||
assert isinstance(settings, GoogleAITextPromptExecutionSettings) # nosec
|
||||
|
||||
genai.configure(api_key=self.service_settings.api_key.get_secret_value())
|
||||
if not self.service_settings.gemini_model_id:
|
||||
raise ServiceInitializationError("The Google AI Gemini model ID is required.")
|
||||
model = GenerativeModel(
|
||||
self.service_settings.gemini_model_id,
|
||||
model_name=self.service_settings.gemini_model_id,
|
||||
)
|
||||
|
||||
response: AsyncGenerateContentResponse = await model.generate_content_async(
|
||||
@@ -122,8 +124,10 @@ class GoogleAITextCompletion(GoogleAIBase, TextCompletionClientBase):
|
||||
assert isinstance(settings, GoogleAITextPromptExecutionSettings) # nosec
|
||||
|
||||
genai.configure(api_key=self.service_settings.api_key.get_secret_value())
|
||||
if not self.service_settings.gemini_model_id:
|
||||
raise ServiceInitializationError("The Google AI Gemini model ID is required.")
|
||||
model = GenerativeModel(
|
||||
self.service_settings.gemini_model_id,
|
||||
model_name=self.service_settings.gemini_model_id,
|
||||
)
|
||||
|
||||
response: AsyncGenerateContentResponse = await model.generate_content_async(
|
||||
|
||||
+3
-1
@@ -93,7 +93,9 @@ class GoogleAITextEmbedding(GoogleAIBase, EmbeddingGeneratorBase):
|
||||
assert isinstance(settings, GoogleAIEmbeddingPromptExecutionSettings) # nosec
|
||||
|
||||
genai.configure(api_key=self.service_settings.api_key.get_secret_value())
|
||||
response: BatchEmbeddingDict = await genai.embed_content_async(
|
||||
if not self.service_settings.embedding_model_id:
|
||||
raise ServiceInitializationError("The Google AI embedding model ID is required.")
|
||||
response: BatchEmbeddingDict = await genai.embed_content_async( # type: ignore
|
||||
model=self.service_settings.embedding_model_id,
|
||||
content=texts,
|
||||
**settings.prepare_settings_dict(),
|
||||
|
||||
@@ -2,10 +2,16 @@
|
||||
|
||||
from typing import Any
|
||||
|
||||
from transformers import GenerationConfig
|
||||
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
|
||||
try:
|
||||
from transformers import GenerationConfig
|
||||
|
||||
ready = True
|
||||
except ImportError:
|
||||
GenerationConfig = Any
|
||||
ready = False
|
||||
|
||||
|
||||
class HuggingFacePromptExecutionSettings(PromptExecutionSettings):
|
||||
"""Hugging Face prompt execution settings."""
|
||||
@@ -19,8 +25,10 @@ class HuggingFacePromptExecutionSettings(PromptExecutionSettings):
|
||||
temperature: float = 1.0
|
||||
top_p: float = 1.0
|
||||
|
||||
def get_generation_config(self) -> GenerationConfig:
|
||||
def get_generation_config(self) -> "GenerationConfig":
|
||||
"""Get the generation config."""
|
||||
if not ready:
|
||||
raise ImportError("transformers is not installed.")
|
||||
return GenerationConfig(
|
||||
**self.model_dump(
|
||||
include={"max_new_tokens", "pad_token_id", "eos_token_id", "temperature", "top_p"},
|
||||
|
||||
@@ -11,6 +11,7 @@ if sys.version_info >= (3, 12):
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
|
||||
import torch
|
||||
from transformers import AutoTokenizer, TextIteratorStreamer, pipeline
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ if TYPE_CHECKING:
|
||||
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
||||
@@ -5,13 +5,18 @@ import os
|
||||
from collections.abc import AsyncGenerator
|
||||
from typing import Any
|
||||
|
||||
import onnxruntime_genai as OnnxRuntimeGenAi
|
||||
|
||||
from semantic_kernel.connectors.ai.onnx.onnx_gen_ai_prompt_execution_settings import OnnxGenAIPromptExecutionSettings
|
||||
from semantic_kernel.contents import ImageContent
|
||||
from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvalidResponseError
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
|
||||
try:
|
||||
import onnxruntime_genai as OnnxRuntimeGenAi
|
||||
|
||||
ready = True
|
||||
except ImportError:
|
||||
ready = False
|
||||
|
||||
|
||||
class OnnxGenAICompletionBase(KernelBaseModel):
|
||||
"""Base class for OnnxGenAI Completion services."""
|
||||
@@ -31,6 +36,8 @@ class OnnxGenAICompletionBase(KernelBaseModel):
|
||||
Raises:
|
||||
ServiceInitializationError: When model cannot be loaded
|
||||
"""
|
||||
if not ready:
|
||||
raise ImportError("onnxruntime-genai is not installed.")
|
||||
try:
|
||||
json_gen_ai_config = os.path.join(ai_model_path + "/genai_config.json")
|
||||
with open(json_gen_ai_config) as file:
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class ApiKeyAuthentication(AzureChatRequestBase):
|
||||
"""API key authentication."""
|
||||
|
||||
type: Annotated[Literal["APIKey", "api_key"], AfterValidator(to_snake)] = "api_key"
|
||||
key: str | None = None
|
||||
key: str
|
||||
|
||||
|
||||
class SystemAssignedManagedIdentityAuthentication(AzureChatRequestBase):
|
||||
|
||||
@@ -68,20 +68,20 @@ class BinaryContent(KernelContent):
|
||||
ai_model_id (str | None): The id of the AI model that generated this response.
|
||||
metadata (dict[str, Any]): Any metadata that should be attached to the response.
|
||||
"""
|
||||
_data_uri = None
|
||||
temp_data_uri = None
|
||||
if data_uri:
|
||||
_data_uri = DataUri.from_data_uri(data_uri, self.default_mime_type)
|
||||
temp_data_uri = DataUri.from_data_uri(data_uri, self.default_mime_type)
|
||||
if "metadata" in kwargs:
|
||||
kwargs["metadata"].update(_data_uri.parameters)
|
||||
kwargs["metadata"].update(temp_data_uri.parameters)
|
||||
else:
|
||||
kwargs["metadata"] = _data_uri.parameters
|
||||
kwargs["metadata"] = temp_data_uri.parameters
|
||||
elif data:
|
||||
if isinstance(data, str):
|
||||
_data_uri = DataUri(
|
||||
temp_data_uri = DataUri(
|
||||
data_str=data, data_format=data_format, mime_type=mime_type or self.default_mime_type
|
||||
)
|
||||
else:
|
||||
_data_uri = DataUri(
|
||||
temp_data_uri = DataUri(
|
||||
data_bytes=data, data_format=data_format, mime_type=mime_type or self.default_mime_type
|
||||
)
|
||||
|
||||
@@ -92,7 +92,7 @@ class BinaryContent(KernelContent):
|
||||
uri = Url(uri)
|
||||
|
||||
super().__init__(uri=uri, **kwargs)
|
||||
self._data_uri = _data_uri
|
||||
self._data_uri = temp_data_uri
|
||||
|
||||
@computed_field # type: ignore
|
||||
@property
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@ class ACASessionsSettings(KernelBaseSettings):
|
||||
else:
|
||||
endpoint_parsed = urlsplit(endpoint)._asdict()
|
||||
if endpoint_parsed["path"]:
|
||||
endpoint_parsed["path"] = re.sub("/{2,}", "/", endpoint_parsed["path"])
|
||||
endpoint_parsed["path"] = re.sub(r"/{2,}", "/", endpoint_parsed["path"])
|
||||
else:
|
||||
endpoint_parsed["path"] = "/"
|
||||
return str(urlunsplit(endpoint_parsed.values()))
|
||||
|
||||
@@ -5,7 +5,7 @@ import contextlib
|
||||
import json
|
||||
import logging
|
||||
import uuid
|
||||
from collections.abc import Callable
|
||||
from collections.abc import Callable, MutableSequence, Sequence
|
||||
from queue import Queue
|
||||
from typing import Any
|
||||
|
||||
@@ -59,8 +59,8 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
super().__init__(ctx, actor_id, kernel, factories)
|
||||
self.kernel = kernel
|
||||
self.factories = factories
|
||||
self.steps: list[StepInterface] = []
|
||||
self.step_infos: list[DaprStepInfo] = []
|
||||
self.steps: MutableSequence[StepInterface] = []
|
||||
self.step_infos: MutableSequence[DaprStepInfo] = []
|
||||
self.initialize_task: bool | None = False
|
||||
self.external_event_queue: Queue = Queue()
|
||||
self.process_task: asyncio.Task | None = None
|
||||
@@ -133,7 +133,7 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
if not self.process_task or self.process_task.done():
|
||||
self.process_task = asyncio.create_task(self.internal_execute(keep_alive=keep_alive))
|
||||
|
||||
async def run_once(self, process_event: str) -> None:
|
||||
async def run_once(self, process_event: KernelProcessEvent | str | None) -> None:
|
||||
"""Starts the process with an initial event and waits for it to finish.
|
||||
|
||||
Args:
|
||||
@@ -148,7 +148,9 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
actor_interface=ExternalEventBufferInterface,
|
||||
)
|
||||
try:
|
||||
await external_event_queue.enqueue(process_event)
|
||||
await external_event_queue.enqueue(
|
||||
process_event.model_dump_json() if isinstance(process_event, KernelProcessEvent) else process_event
|
||||
)
|
||||
|
||||
logger.info(f"Run once for process event: {process_event}")
|
||||
|
||||
@@ -172,7 +174,7 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await self.process_task
|
||||
|
||||
async def initialize_step(self):
|
||||
async def initialize_step(self, input: str) -> None:
|
||||
"""Initializes the step."""
|
||||
# The process does not need any further initialization
|
||||
pass
|
||||
@@ -218,9 +220,13 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
|
||||
process_state = KernelProcessState(self.name, self.id.id)
|
||||
step_tasks = [step.to_dapr_step_info() for step in self.steps]
|
||||
steps = await asyncio.gather(*step_tasks)
|
||||
steps: Sequence[str] = await asyncio.gather(*step_tasks)
|
||||
return DaprProcessInfo(
|
||||
inner_step_python_type=self.inner_step_type, edges=self.process.edges, state=process_state, steps=steps
|
||||
inner_step_python_type=self.inner_step_type,
|
||||
edges=self.process.edges,
|
||||
state=process_state,
|
||||
# steps are model dumps of the classes, which pydantic can parse back.
|
||||
steps=steps, # type: ignore
|
||||
)
|
||||
|
||||
async def handle_message(self, message: ProcessMessage) -> None:
|
||||
@@ -254,7 +260,7 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
self.output_edges = {kvp[0]: list(kvp[1]) for kvp in self.process.edges.items()}
|
||||
|
||||
for step in self.step_infos:
|
||||
step_actor = None
|
||||
step_actor: StepInterface | None = None
|
||||
|
||||
# The current step should already have a name.
|
||||
assert step.state and step.state.name is not None # nosec
|
||||
@@ -286,7 +292,7 @@ class ProcessActor(StepActor, ProcessInterface):
|
||||
assert step.state and step.state.id is not None # nosec
|
||||
|
||||
scoped_step_id = self._scoped_actor_id(ActorId(step.state.id))
|
||||
step_actor: StepInterface = ActorProxy.create( # type: ignore
|
||||
step_actor = ActorProxy.create( # type: ignore
|
||||
actor_type=f"{StepActor.__name__}",
|
||||
actor_id=scoped_step_id,
|
||||
actor_interface=StepInterface,
|
||||
|
||||
@@ -332,7 +332,7 @@ class StepActor(Actor, StepInterface, KernelProcessMessageChannel):
|
||||
raise ProcessFunctionNotFoundException(f"Function {target_function} not found in plugin {self.name}")
|
||||
|
||||
invoke_result = None
|
||||
event_name = None
|
||||
event_name: str = ""
|
||||
event_value = None
|
||||
|
||||
try:
|
||||
@@ -340,6 +340,8 @@ class StepActor(Actor, StepInterface, KernelProcessMessageChannel):
|
||||
f"Invoking plugin `{function.plugin_name}` and function `{function.name}` with arguments: {arguments}"
|
||||
)
|
||||
invoke_result = await self.invoke_function(function, self.kernel, arguments)
|
||||
if invoke_result is None:
|
||||
raise KernelException(f"Function {target_function} returned None.")
|
||||
event_name = f"{target_function}.OnResult"
|
||||
event_value = invoke_result.value
|
||||
|
||||
@@ -406,7 +408,9 @@ class StepActor(Actor, StepInterface, KernelProcessMessageChannel):
|
||||
raise ValueError("The inner step type must be initialized before converting to DaprStepInfo.")
|
||||
|
||||
step_info = DaprStepInfo(
|
||||
inner_step_python_type=self.inner_step_type, state=self.step_info.state, edges=self.step_info.edges
|
||||
inner_step_python_type=self.inner_step_type,
|
||||
state=self.step_info.state,
|
||||
edges=self.step_info.edges,
|
||||
)
|
||||
|
||||
return step_info.model_dump_json()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
|
||||
from collections.abc import MutableSequence
|
||||
from typing import Literal
|
||||
|
||||
from pydantic import Field
|
||||
@@ -16,8 +17,8 @@ from semantic_kernel.utils.experimental_decorator import experimental_class
|
||||
class DaprProcessInfo(DaprStepInfo):
|
||||
"""A Dapr process info."""
|
||||
|
||||
type: Literal["DaprProcessInfo"] = Field("DaprProcessInfo") # type: ignore
|
||||
steps: list["DaprStepInfo | DaprProcessInfo"] = Field(default_factory=list)
|
||||
type: Literal["DaprProcessInfo"] = Field(default="DaprProcessInfo") # type: ignore
|
||||
steps: MutableSequence["DaprStepInfo | DaprProcessInfo"] = Field(default_factory=list)
|
||||
|
||||
def to_kernel_process(self) -> KernelProcess:
|
||||
"""Converts the Dapr process info to a kernel process."""
|
||||
@@ -40,7 +41,7 @@ class DaprProcessInfo(DaprStepInfo):
|
||||
raise ValueError("Kernel process must be provided")
|
||||
|
||||
dapr_step_info = DaprStepInfo.from_kernel_step_info(kernel_process)
|
||||
dapr_steps: list[DaprProcessInfo | DaprStepInfo] = []
|
||||
dapr_steps: MutableSequence[DaprProcessInfo | DaprStepInfo] = []
|
||||
|
||||
for step in kernel_process.steps:
|
||||
if isinstance(step, KernelProcess):
|
||||
|
||||
@@ -18,7 +18,7 @@ from semantic_kernel.utils.experimental_decorator import experimental_class
|
||||
class DaprStepInfo(KernelBaseModel):
|
||||
"""A Dapr step info."""
|
||||
|
||||
type: Literal["DaprStepInfo"] = Field("DaprStepInfo")
|
||||
type: Literal["DaprStepInfo"] = Field(default="DaprStepInfo")
|
||||
inner_step_python_type: str
|
||||
state: KernelProcessStepState
|
||||
edges: dict[str, list[KernelProcessEdge]] = Field(default_factory=dict)
|
||||
|
||||
@@ -2,13 +2,16 @@
|
||||
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from dapr.actor import ActorInterface, actormethod
|
||||
|
||||
from semantic_kernel.processes.dapr_runtime.dapr_process_info import DaprProcessInfo
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
|
||||
from semantic_kernel.utils.experimental_decorator import experimental_class
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel.processes.dapr_runtime.dapr_process_info import DaprProcessInfo
|
||||
from semantic_kernel.processes.kernel_process.kernel_process_event import KernelProcessEvent
|
||||
|
||||
|
||||
@experimental_class
|
||||
class ProcessInterface(ActorInterface, ABC):
|
||||
@@ -35,7 +38,7 @@ class ProcessInterface(ActorInterface, ABC):
|
||||
|
||||
@abstractmethod
|
||||
@actormethod(name="run_once")
|
||||
async def run_once(self, process_event: KernelProcessEvent) -> None:
|
||||
async def run_once(self, process_event: "KernelProcessEvent | str | None") -> None:
|
||||
"""Starts the process with an initial event and then waits for the process to finish.
|
||||
|
||||
:param process_event: Required. The KernelProcessEvent to start the process with.
|
||||
|
||||
@@ -17,9 +17,9 @@ class KernelProcessStepContext(KernelBaseModel):
|
||||
|
||||
def __init__(self, channel: KernelProcessMessageChannel):
|
||||
"""Initialize the step context."""
|
||||
super().__init__(step_message_channel=channel)
|
||||
super().__init__(step_message_channel=channel) # type: ignore
|
||||
|
||||
async def emit_event(self, process_event: "KernelProcessEvent | str | Enum", **kwargs) -> None:
|
||||
async def emit_event(self, process_event: "KernelProcessEvent | str | Enum | None", **kwargs) -> None:
|
||||
"""Emit an event from the current step.
|
||||
|
||||
It is possible to either specify a `KernelProcessEvent` object or the ID of the event
|
||||
@@ -34,10 +34,9 @@ class KernelProcessStepContext(KernelBaseModel):
|
||||
if process_event is None:
|
||||
raise ProcessEventUndefinedException("Process event cannot be None")
|
||||
|
||||
if isinstance(process_event, Enum):
|
||||
process_event = process_event.value
|
||||
|
||||
if not isinstance(process_event, KernelProcessEvent):
|
||||
process_event = KernelProcessEvent(id=process_event, **kwargs)
|
||||
process_event = KernelProcessEvent(
|
||||
id=process_event.value if isinstance(process_event, Enum) else process_event, **kwargs
|
||||
)
|
||||
|
||||
await self.step_message_channel.emit_event(process_event)
|
||||
|
||||
@@ -36,7 +36,7 @@ class LocalKernelProcessContext(KernelBaseModel):
|
||||
factories=process.factories,
|
||||
)
|
||||
|
||||
super().__init__(local_process=local_process)
|
||||
super().__init__(local_process=local_process) # type: ignore
|
||||
|
||||
async def start_with_event(self, initial_event: "KernelProcessEvent") -> None:
|
||||
"""Starts the local process with an initial event."""
|
||||
|
||||
@@ -145,7 +145,7 @@ class LocalProcess(LocalStep):
|
||||
assert step.state and step.state.id is not None # nosec
|
||||
|
||||
# Create a LocalStep for the step
|
||||
local_step = LocalStep(
|
||||
local_step = LocalStep( # type: ignore
|
||||
step_info=step,
|
||||
kernel=self.kernel,
|
||||
factories=self.factories,
|
||||
|
||||
@@ -150,7 +150,7 @@ class LocalStep(KernelProcessMessageChannel, KernelBaseModel):
|
||||
raise ProcessFunctionNotFoundException(f"Function {target_function} not found in plugin {self.name}")
|
||||
|
||||
invoke_result = None
|
||||
event_name = None
|
||||
event_name: str = ""
|
||||
event_value = None
|
||||
|
||||
try:
|
||||
@@ -158,6 +158,8 @@ class LocalStep(KernelProcessMessageChannel, KernelBaseModel):
|
||||
f"Invoking plugin `{function.plugin_name}` and function `{function.name}` with arguments: {arguments}"
|
||||
)
|
||||
invoke_result = await self.invoke_function(function, self.kernel, arguments)
|
||||
if invoke_result is None:
|
||||
raise KernelException(f"Function {target_function} returned None.")
|
||||
event_name = f"{target_function}.OnResult"
|
||||
event_value = invoke_result.value
|
||||
except Exception as ex:
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from collections.abc import Sequence
|
||||
from html import escape
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
@@ -62,7 +63,7 @@ class PromptTemplateBase(KernelBaseModel, ABC):
|
||||
allow_dangerously_set_content = True
|
||||
return allow_dangerously_set_content
|
||||
|
||||
def _should_escape(self, name: str, input_variables: list["InputVariable"]) -> bool:
|
||||
def _should_escape(self, name: str, input_variables: Sequence["InputVariable"]) -> bool:
|
||||
"""Check if the variable should be escaped.
|
||||
|
||||
If the PromptTemplate allows dangerously set content, then the variable will not be escaped,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import logging
|
||||
from collections.abc import Mapping, Sequence
|
||||
from collections.abc import MutableMapping, MutableSequence, Sequence
|
||||
from typing import TypeVar
|
||||
|
||||
from pydantic import Field, field_validator, model_validator
|
||||
@@ -16,6 +16,8 @@ PromptExecutionSettingsT = TypeVar("PromptExecutionSettingsT", bound=PromptExecu
|
||||
|
||||
logger: logging.Logger = logging.getLogger(__name__)
|
||||
|
||||
_T = TypeVar("_T", bound="PromptTemplateConfig")
|
||||
|
||||
|
||||
class PromptTemplateConfig(KernelBaseModel):
|
||||
"""Configuration for a prompt template.
|
||||
@@ -37,9 +39,9 @@ class PromptTemplateConfig(KernelBaseModel):
|
||||
description: str | None = ""
|
||||
template: str | None = None
|
||||
template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME
|
||||
input_variables: list[InputVariable] = Field(default_factory=list)
|
||||
input_variables: MutableSequence[InputVariable] = Field(default_factory=list)
|
||||
allow_dangerously_set_content: bool = False
|
||||
execution_settings: dict[str, PromptExecutionSettings] = Field(default_factory=dict)
|
||||
execution_settings: MutableMapping[str, PromptExecutionSettings] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def check_input_variables(self):
|
||||
@@ -52,12 +54,12 @@ class PromptTemplateConfig(KernelBaseModel):
|
||||
@field_validator("execution_settings", mode="before")
|
||||
@classmethod
|
||||
def rewrite_execution_settings(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
settings: PromptExecutionSettings
|
||||
| Sequence[PromptExecutionSettings]
|
||||
| Mapping[str, PromptExecutionSettings]
|
||||
| MutableMapping[str, PromptExecutionSettings]
|
||||
| None,
|
||||
) -> Mapping[str, PromptExecutionSettings]:
|
||||
) -> MutableMapping[str, PromptExecutionSettings]:
|
||||
"""Rewrite execution settings to a dictionary."""
|
||||
if not settings:
|
||||
return {}
|
||||
@@ -81,14 +83,14 @@ class PromptTemplateConfig(KernelBaseModel):
|
||||
name=variable.name,
|
||||
description=variable.description,
|
||||
default_value=variable.default,
|
||||
type_=variable.json_schema, # TODO (moonbox3): update to handle complex JSON schemas
|
||||
type=variable.json_schema, # TODO (moonbox3): update to handle complex JSON schemas
|
||||
is_required=variable.is_required,
|
||||
)
|
||||
for variable in self.input_variables
|
||||
]
|
||||
|
||||
@classmethod
|
||||
def from_json(cls, json_str: str) -> "PromptTemplateConfig":
|
||||
def from_json(cls: type[_T], json_str: str) -> _T:
|
||||
"""Create a PromptTemplateConfig instance from a JSON string."""
|
||||
if not json_str:
|
||||
raise ValueError("json_str is empty")
|
||||
@@ -102,15 +104,15 @@ class PromptTemplateConfig(KernelBaseModel):
|
||||
|
||||
@classmethod
|
||||
def restore(
|
||||
cls,
|
||||
cls: type[_T],
|
||||
name: str,
|
||||
description: str,
|
||||
template: str,
|
||||
template_format: TEMPLATE_FORMAT_TYPES = KERNEL_TEMPLATE_FORMAT_NAME,
|
||||
input_variables: Sequence[InputVariable] = [],
|
||||
execution_settings: Mapping[str, PromptExecutionSettings] = {},
|
||||
input_variables: MutableSequence[InputVariable] = [],
|
||||
execution_settings: MutableMapping[str, PromptExecutionSettings] = {},
|
||||
allow_dangerously_set_content: bool = False,
|
||||
) -> "PromptTemplateConfig":
|
||||
) -> _T:
|
||||
"""Restore a PromptTemplateConfig instance from the specified parameters.
|
||||
|
||||
Args:
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Protocol, runtime_checkable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -11,6 +12,7 @@ if TYPE_CHECKING:
|
||||
class CodeRenderer(Protocol):
|
||||
"""Protocol for dynamic code blocks that need async IO to be rendered."""
|
||||
|
||||
@abstractmethod
|
||||
async def render_code(self, kernel: "Kernel", arguments: "KernelArguments") -> str:
|
||||
"""Render the block using the given context.
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import TYPE_CHECKING, Optional, Protocol, runtime_checkable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -11,6 +12,7 @@ if TYPE_CHECKING:
|
||||
class TextRenderer(Protocol):
|
||||
"""Protocol for static (text) blocks that don't need async rendering."""
|
||||
|
||||
@abstractmethod
|
||||
def render(self, kernel: "Kernel", arguments: Optional["KernelArguments"] = None) -> str:
|
||||
"""Render the block using only the given variables.
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ from semantic_kernel.connectors.ai.completion_usage import CompletionUsage
|
||||
from semantic_kernel.contents.chat_history import ChatHistory
|
||||
from semantic_kernel.contents.chat_message_content import ChatMessageContent
|
||||
from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent
|
||||
from semantic_kernel.contents.streaming_content_mixin import StreamingContentMixin
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.contents.text_content import TextContent
|
||||
from semantic_kernel.utils.experimental_decorator import experimental_function
|
||||
@@ -434,9 +435,9 @@ def _set_completion_response(
|
||||
"message": completion.to_dict(),
|
||||
}
|
||||
|
||||
if hasattr(completion, "finish_reason"):
|
||||
if isinstance(completion, ChatMessageContent):
|
||||
full_response["finish_reason"] = completion.finish_reason
|
||||
if hasattr(completion, "choice_index"):
|
||||
if isinstance(completion, StreamingContentMixin):
|
||||
full_response["index"] = completion.choice_index
|
||||
|
||||
logger.info(
|
||||
|
||||
@@ -5,6 +5,11 @@ import os
|
||||
import sys
|
||||
from typing import Annotated
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
import pytest
|
||||
from azure.ai.inference.aio import ChatCompletionsClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
@@ -41,11 +46,6 @@ from semantic_kernel.utils.authentication.entra_id_authentication import get_ent
|
||||
from tests.integration.completions.completion_test_base import CompletionTestBase, ServiceType
|
||||
from tests.utils import is_service_setup_for_testing
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
from typing import override # pragma: no cover
|
||||
else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
# Make sure all services are setup for before running the tests
|
||||
# The following exceptions apply:
|
||||
# 1. OpenAI and Azure OpenAI services are always setup for testing.
|
||||
|
||||
@@ -12,10 +12,8 @@ else:
|
||||
from typing_extensions import override # pragma: no cover
|
||||
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
from semantic_kernel.contents import ChatMessageContent, TextContent
|
||||
from semantic_kernel.contents.chat_history import ChatHistory
|
||||
from semantic_kernel.contents.utils.author_role import AuthorRole
|
||||
from semantic_kernel.connectors.ai import PromptExecutionSettings
|
||||
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent, TextContent
|
||||
from semantic_kernel.kernel_pydantic import KernelBaseModel
|
||||
from tests.integration.completions.chat_completion_test_base import (
|
||||
ChatCompletionTestBase,
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import sys
|
||||
from functools import partial
|
||||
from importlib import util
|
||||
from typing import Any
|
||||
|
||||
if sys.version_info >= (3, 12):
|
||||
@@ -32,6 +33,9 @@ from semantic_kernel.utils.authentication.entra_id_authentication import get_ent
|
||||
from tests.integration.completions.completion_test_base import CompletionTestBase, ServiceType
|
||||
from tests.utils import is_service_setup_for_testing, is_test_running_on_supported_platforms, retry
|
||||
|
||||
hugging_face_setup = util.find_spec("torch") is not None
|
||||
|
||||
|
||||
azure_openai_setup = True
|
||||
ollama_setup: bool = is_service_setup_for_testing(["OLLAMA_TEXT_MODEL_ID"]) and is_test_running_on_supported_platforms([
|
||||
"Linux"
|
||||
@@ -219,7 +223,9 @@ class TestTextCompletion(CompletionTestBase):
|
||||
service_id="patrickvonplaten/t5-tiny-random",
|
||||
ai_model_id="patrickvonplaten/t5-tiny-random",
|
||||
task="text2text-generation",
|
||||
),
|
||||
)
|
||||
if hugging_face_setup
|
||||
else None,
|
||||
HuggingFacePromptExecutionSettings,
|
||||
),
|
||||
"hf_summ": (
|
||||
@@ -227,7 +233,9 @@ class TestTextCompletion(CompletionTestBase):
|
||||
service_id="jotamunz/billsum_tiny_summarization",
|
||||
ai_model_id="jotamunz/billsum_tiny_summarization",
|
||||
task="summarization",
|
||||
),
|
||||
)
|
||||
if hugging_face_setup
|
||||
else None,
|
||||
HuggingFacePromptExecutionSettings,
|
||||
),
|
||||
"hf_gen": (
|
||||
@@ -235,7 +243,9 @@ class TestTextCompletion(CompletionTestBase):
|
||||
service_id="HuggingFaceM4/tiny-random-LlamaForCausalLM",
|
||||
ai_model_id="HuggingFaceM4/tiny-random-LlamaForCausalLM",
|
||||
task="text-generation",
|
||||
),
|
||||
)
|
||||
if hugging_face_setup
|
||||
else None,
|
||||
HuggingFacePromptExecutionSettings,
|
||||
),
|
||||
"onnx_gen_ai": (
|
||||
|
||||
@@ -733,7 +733,7 @@ async def test_openapi_put_light_by_id(kernel: Kernel):
|
||||
|
||||
assert request_content.get("method") == "PUT"
|
||||
assert request_content.get("url") == "https://127.0.0.1/Lights/1"
|
||||
assert request_content.get("body") == '{"hexColor": "11EE11"}'
|
||||
assert request_content.get("body") == '{"hexColor":"11EE11"}'
|
||||
|
||||
|
||||
# endregion
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from importlib import util
|
||||
|
||||
import pytest
|
||||
from azure.ai.inference.aio import EmbeddingsClient
|
||||
from azure.identity import DefaultAzureCredential
|
||||
@@ -32,6 +34,8 @@ from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecut
|
||||
from semantic_kernel.utils.authentication.entra_id_authentication import get_entra_auth_token
|
||||
from tests.utils import is_service_setup_for_testing
|
||||
|
||||
hugging_face_setup = util.find_spec("torch") is not None
|
||||
|
||||
# Make sure all services are setup for before running the tests
|
||||
# The following exceptions apply:
|
||||
# 1. OpenAI and Azure OpenAI services are always setup for testing.
|
||||
@@ -91,7 +95,9 @@ class EmbeddingServiceTestBase:
|
||||
PromptExecutionSettings,
|
||||
),
|
||||
"hugging_face": (
|
||||
HuggingFaceTextEmbedding(ai_model_id="sentence-transformers/all-MiniLM-L6-v2"),
|
||||
HuggingFaceTextEmbedding(ai_model_id="sentence-transformers/all-MiniLM-L6-v2")
|
||||
if hugging_face_setup
|
||||
else None,
|
||||
PromptExecutionSettings,
|
||||
),
|
||||
"ollama": (OllamaTextEmbedding() if ollama_setup else None, OllamaEmbeddingPromptExecutionSettings),
|
||||
|
||||
@@ -34,13 +34,6 @@ from samples.concepts.local_models.lm_studio_text_embedding import main as lm_st
|
||||
from samples.concepts.local_models.ollama_chat_completion import main as ollama_chat_completion
|
||||
from samples.concepts.memory.azure_cognitive_search_memory import main as azure_cognitive_search_memory
|
||||
from samples.concepts.memory.memory import main as memory
|
||||
from samples.concepts.planners.azure_openai_function_calling_stepwise_planner import (
|
||||
main as azure_openai_function_calling_stepwise_planner,
|
||||
)
|
||||
from samples.concepts.planners.openai_function_calling_stepwise_planner import (
|
||||
main as openai_function_calling_stepwise_planner,
|
||||
)
|
||||
from samples.concepts.planners.sequential_planner import main as sequential_planner
|
||||
from samples.concepts.plugins.openai_function_calling_with_custom_plugin import (
|
||||
main as openai_function_calling_with_custom_plugin,
|
||||
)
|
||||
@@ -161,30 +154,6 @@ concepts = [
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
azure_openai_function_calling_stepwise_planner,
|
||||
[],
|
||||
id="azure_openai_function_calling_stepwise_planner",
|
||||
marks=pytest.mark.skipif(
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
openai_function_calling_stepwise_planner,
|
||||
[],
|
||||
id="openai_function_calling_stepwise_planner",
|
||||
marks=pytest.mark.skipif(
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
sequential_planner,
|
||||
[],
|
||||
id="sequential_planner",
|
||||
marks=pytest.mark.skipif(
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
openai_function_calling_with_custom_plugin,
|
||||
[],
|
||||
|
||||
@@ -9,7 +9,6 @@ from samples.learn_resources.ai_services import main as ai_services
|
||||
from samples.learn_resources.configuring_prompts import main as configuring_prompts
|
||||
from samples.learn_resources.creating_functions import main as creating_functions
|
||||
from samples.learn_resources.functions_within_prompts import main as functions_within_prompts
|
||||
from samples.learn_resources.planner import main as planner
|
||||
from samples.learn_resources.plugin import main as plugin
|
||||
from samples.learn_resources.serializing_prompts import main as serializing_prompts
|
||||
from samples.learn_resources.templates import main as templates
|
||||
@@ -54,14 +53,6 @@ learn_resources = [
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
planner,
|
||||
[],
|
||||
id="planner",
|
||||
marks=mark.skipif(
|
||||
os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples."
|
||||
),
|
||||
),
|
||||
param(
|
||||
plugin,
|
||||
[],
|
||||
|
||||
@@ -11,6 +11,15 @@ from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvali
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from tests.unit.connectors.ai.onnx.conftest import gen_ai_config, gen_ai_config_vision
|
||||
|
||||
try:
|
||||
import onnxruntime_genai # noqa: F401
|
||||
|
||||
ready = True
|
||||
except ImportError:
|
||||
ready = False
|
||||
|
||||
pytestmark = pytest.mark.skipif(not ready, reason="ONNX Runtime is not installed.")
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
|
||||
@@ -12,6 +12,15 @@ from semantic_kernel.contents import TextContent
|
||||
from semantic_kernel.exceptions import ServiceInitializationError
|
||||
from tests.unit.connectors.ai.onnx.conftest import gen_ai_config
|
||||
|
||||
try:
|
||||
import onnxruntime_genai # noqa: F401
|
||||
|
||||
ready = True
|
||||
except ImportError:
|
||||
ready = False
|
||||
|
||||
pytestmark = pytest.mark.skipif(not ready, reason="ONNX Runtime is not installed.")
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
|
||||
@@ -311,6 +311,20 @@ def test_azure_open_ai_chat_prompt_execution_settings_with_aisearch_data_sources
|
||||
pytest.param({"type": "access_token"}, marks=pytest.mark.xfail),
|
||||
pytest.param({"type": "invalid"}, marks=pytest.mark.xfail),
|
||||
],
|
||||
ids=[
|
||||
"APIKey",
|
||||
"api_key",
|
||||
"api_key_no_key",
|
||||
"SystemAssignedManagedIdentity",
|
||||
"system_assigned_managed_identity",
|
||||
"UserAssignedManagedIdentity",
|
||||
"user_assigned_managed_identity",
|
||||
"user_assigned_managed_identity_no_id",
|
||||
"AccessToken",
|
||||
"access_token",
|
||||
"access_token_no_token",
|
||||
"invalid",
|
||||
],
|
||||
)
|
||||
def test_aisearch_data_source_parameters(authentication) -> None:
|
||||
AzureAISearchDataSourceParameters(index_name="test_index", authentication=authentication)
|
||||
|
||||
@@ -113,14 +113,14 @@ def kernel_factory() -> t.Callable[[t.Type[_Serializable]], _Serializable]:
|
||||
|
||||
|
||||
PROTOCOLS = [
|
||||
pytest.param(ConversationSummaryPlugin, marks=pytest.mark.xfail(reason="Contains data")),
|
||||
ConversationSummaryPlugin,
|
||||
HttpPlugin,
|
||||
MathPlugin,
|
||||
TextMemoryPlugin,
|
||||
TextPlugin,
|
||||
TimePlugin,
|
||||
WaitPlugin,
|
||||
pytest.param(WebSearchEnginePlugin, marks=pytest.mark.xfail(reason="Contains data")),
|
||||
WebSearchEnginePlugin,
|
||||
]
|
||||
|
||||
BASE_CLASSES = [
|
||||
@@ -146,17 +146,20 @@ PYDANTIC_MODELS = [
|
||||
KernelParameterMetadata,
|
||||
KernelFunctionMetadata,
|
||||
ChatHistory,
|
||||
]
|
||||
KERNEL_FUNCTION_OPTIONAL = [KernelFunction]
|
||||
KERNEL_FUNCTION_REQUIRED = [
|
||||
pytest.param(
|
||||
KernelFunction,
|
||||
marks=pytest.mark.xfail(reason="Need to implement Pickle serialization."),
|
||||
),
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class TestUsageInPydanticFields:
|
||||
@pytest.mark.parametrize(
|
||||
"kernel_type",
|
||||
BASE_CLASSES + PROTOCOLS + ENUMS + PYDANTIC_MODELS + STATELESS_CLASSES,
|
||||
BASE_CLASSES + PROTOCOLS + ENUMS + PYDANTIC_MODELS + STATELESS_CLASSES + KERNEL_FUNCTION_OPTIONAL,
|
||||
)
|
||||
def test_usage_as_optional_field(
|
||||
self,
|
||||
@@ -170,11 +173,11 @@ class TestUsageInPydanticFields:
|
||||
class TestModel(KernelBaseModel):
|
||||
"""A test model."""
|
||||
|
||||
field: t.Optional[kernel_type] = None
|
||||
field: kernel_type | None = None
|
||||
|
||||
assert_serializable(TestModel(), TestModel)
|
||||
|
||||
@pytest.mark.parametrize("kernel_type", PYDANTIC_MODELS + STATELESS_CLASSES)
|
||||
@pytest.mark.parametrize("kernel_type", PYDANTIC_MODELS + STATELESS_CLASSES + KERNEL_FUNCTION_REQUIRED)
|
||||
def test_usage_as_required_field(
|
||||
self,
|
||||
kernel_factory: t.Callable[[t.Type[KernelBaseModelFieldT]], KernelBaseModelFieldT],
|
||||
|
||||
Generated
+372
-330
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user