diff --git a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py deleted file mode 100644 index 9685591dc73f..000000000000 --- a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py +++ /dev/null @@ -1,295 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -import json -import os -import platform -from functools import reduce - -import httpx -from aiohttp import ClientSession - -from samples.concepts.plugins.azure_key_vault_settings import AzureKeyVaultSettings -from semantic_kernel import Kernel -from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior -from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings -from semantic_kernel.connectors.openai_plugin import OpenAIAuthenticationType, OpenAIFunctionExecutionParameters -from semantic_kernel.contents import ChatHistory -from semantic_kernel.contents.chat_message_content import ChatMessageContent -from semantic_kernel.contents.function_call_content import FunctionCallContent -from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent -from semantic_kernel.contents.utils.author_role import AuthorRole -from semantic_kernel.functions import KernelArguments, KernelFunction, KernelPlugin - -# region Helper functions - - -def get_file_url(relative_path): - absolute_path = os.path.abspath(relative_path) - if platform.system() == "Windows": - backslash_char = "\\" - return f"file:///{absolute_path.replace(backslash_char, '/')}" - return f"file://{absolute_path}" - - -def load_and_update_openai_spec(): - # Construct the path to the OpenAI spec file - openai_spec_file = os.path.join( - os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "open_ai_plugins", "akv-openai.json" - ) - - # Read the OpenAI spec file - with open(openai_spec_file) as file: - openai_spec = json.load(file) - - # Adjust the OpenAI spec file to use the correct file URL based on platform - openapi_yaml_path = os.path.join( - os.path.dirname(os.path.dirname(os.path.realpath(__file__))), "resources", "open_ai_plugins", "akv-openapi.yaml" - ) - openai_spec["api"]["url"] = get_file_url(openapi_yaml_path) - - return json.dumps(openai_spec, indent=4) - - -def print_tool_calls(message: ChatMessageContent) -> None: - # A helper method to pretty print the tool calls from the message. - # This is only triggered if auto invoke tool calls is disabled. - items = message.items - formatted_tool_calls = [] - for i, item in enumerate(items, start=1): - if isinstance(item, FunctionCallContent): - tool_call_id = item.id - function_name = item.name - function_arguments = item.arguments - formatted_str = ( - f"tool_call {i} id: {tool_call_id}\n" - f"tool_call {i} function name: {function_name}\n" - f"tool_call {i} arguments: {function_arguments}" - ) - formatted_tool_calls.append(formatted_str) - print("Tool calls:\n" + "\n\n".join(formatted_tool_calls)) - - -# endregion - -# region Sample Authentication Provider - - -class OpenAIAuthenticationProvider: - """A Sample Authentication Provider for an OpenAI/OpenAPI plugin""" - - def __init__( - self, oauth_values: dict[str, dict[str, str]] | None = None, credentials: dict[str, str] | None = None - ): - """Initializes the OpenAIAuthenticationProvider.""" - self.oauth_values = oauth_values or {} - self.credentials = credentials or {} - - async def authenticate_request( - self, - plugin_name: str, - openai_auth_config: OpenAIAuthenticationType, - **kwargs, - ) -> 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 None - - scheme = "" - credential = "" - - if openai_auth_config.type == OpenAIAuthenticationType.OAuth: - if not openai_auth_config.authorization_url: - raise ValueError("Authorization URL is required for OAuth.") - - domain = openai_auth_config.authorization_url.host - domain_oauth_values = self.oauth_values.get(domain) - - if not domain_oauth_values: - raise ValueError("No OAuth values found for the provided authorization URL.") - - values = domain_oauth_values | {"scope": openai_auth_config.scope or ""} - - content_type = openai_auth_config.authorization_content_type or "application/x-www-form-urlencoded" - async with ClientSession() as session: - authorization_url = str(openai_auth_config.authorization_url) - - if content_type == "application/x-www-form-urlencoded": - response = await session.post(authorization_url, data=values) - elif content_type == "application/json": - response = await session.post(authorization_url, json=values) - else: - raise ValueError(f"Unsupported authorization content type: {content_type}") - - response.raise_for_status() - - token_response = await response.json() - scheme = token_response.get("token_type", "") - credential = token_response.get("access_token", "") - - else: - token = openai_auth_config.verification_tokens.get(plugin_name, "") - scheme = openai_auth_config.authorization_type.value - credential = token - - auth_header = f"{scheme} {credential}" - return {"Authorization": auth_header} - - -# endregion - -# region AKV Plugin Functions - - -async def add_secret_to_key_vault(kernel: Kernel, plugin: KernelPlugin): - """Adds a secret to the Azure Key Vault.""" - arguments = KernelArguments() - arguments["secret_name"] = "Foo" # nosec - arguments["api_version"] = "7.0" - arguments["value"] = "Bar" - arguments["enabled"] = True - result = await kernel.invoke( - function=plugin["SetSecret"], - arguments=arguments, - ) - - print(f"Secret added to Key Vault: {result}") - - -async def get_secret_from_key_vault(kernel: Kernel, plugin: KernelPlugin): - """Gets a secret from the Azure Key Vault.""" - arguments = KernelArguments() - arguments["secret_name"] = "Foo" # nosec - arguments["api_version"] = "7.0" - result = await kernel.invoke( - function=plugin["GetSecret"], - arguments=arguments, - ) - - print(f"Secret retrieved from Key Vault: {result}") - - -# endregion - - -kernel = Kernel() - -kernel.add_service(OpenAIChatCompletion(service_id="chat")) - -chat_function = kernel.add_function( - prompt="{{$chat_history}}{{$user_input}}", - plugin_name="ChatBot", - function_name="Chat", -) - -execution_settings = OpenAIChatPromptExecutionSettings( - service_id="chat", - max_tokens=2000, - temperature=0.7, - top_p=0.8, - function_choice_behavior=FunctionChoiceBehavior.Auto(filters={"included_plugins": ["AzureKeyVaultPlugin"]}), -) - -history = ChatHistory() -history.add_system_message("Use Api-version 7.0, if needed.") - -arguments = KernelArguments(settings=execution_settings) - - -async def handle_streaming( - kernel: Kernel, - chat_function: "KernelFunction", - arguments: KernelArguments, -) -> None: - """Handle streaming chat messages.""" - response = kernel.invoke_stream( - chat_function, - return_function_results=False, - arguments=arguments, - ) - - print("Security Agent:> ", end="") - streamed_chunks: list[StreamingChatMessageContent] = [] - async for message in response: - if ( - not execution_settings.function_choice_behavior.auto_invoke_kernel_functions - and isinstance(message[0], StreamingChatMessageContent) - and message[0].role == AuthorRole.ASSISTANT - ): - streamed_chunks.append(message[0]) - elif isinstance(message[0], StreamingChatMessageContent) and message[0].role == AuthorRole.ASSISTANT: - print(str(message[0]), end="") - - if streamed_chunks: - streaming_chat_message = reduce(lambda first, second: first + second, streamed_chunks) - print("Auto tool calls is disabled, printing returned tool calls...") - print_tool_calls(streaming_chat_message) - - print("\n") - - -async def main() -> None: - """Main function to run the chat bot.""" - azure_keyvault_settings = AzureKeyVaultSettings.create() - client_id = azure_keyvault_settings.client_id - client_secret = azure_keyvault_settings.client_secret.get_secret_value() - endpoint = azure_keyvault_settings.endpoint - - authentication_provider = OpenAIAuthenticationProvider({ - "login.microsoftonline.com": { - "client_id": client_id, - "client_secret": client_secret, - "grant_type": "client_credentials", - } - }) - - openai_spec = load_and_update_openai_spec() - - http_client = httpx.AsyncClient(timeout=5) - - await kernel.add_plugin_from_openai( - plugin_name="AzureKeyVaultPlugin", - plugin_str=openai_spec, - execution_parameters=OpenAIFunctionExecutionParameters( - http_client=http_client, - auth_callback=authentication_provider.authenticate_request, - server_url_override=str(endpoint), - enable_dynamic_payload=True, - ), - ) - - chatting = True - print( - "Welcome to the chat bot!\ - \n Type 'exit' to exit.\ - \n Try chatting about Azure Key Vault!" - ) - while chatting: - chatting = await chat() - - -async def chat() -> bool: - """Chat with the bot.""" - try: - user_input = input("User:> ") - except KeyboardInterrupt: - print("\n\nExiting chat...") - return False - except EOFError: - print("\n\nExiting chat...") - return False - - if user_input == "exit": - print("\n\nExiting chat...") - return False - arguments["user_input"] = user_input - arguments["chat_history"] = history - - await handle_streaming(kernel, chat_function, arguments=arguments) - - return True - - -if __name__ == "__main__": - import asyncio - - asyncio.run(main()) diff --git a/python/samples/concepts/plugins/openai_plugin_klarna.py b/python/samples/concepts/plugins/openai_plugin_klarna.py deleted file mode 100644 index fa4cb34277c4..000000000000 --- a/python/samples/concepts/plugins/openai_plugin_klarna.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -import asyncio - -from semantic_kernel.kernel import Kernel - - -async def main(): - # This is an example of how to import a plugin from OpenAI and invoke a function from the plugin - # It does not require authentication - - kernel = Kernel() - plugin = await kernel.add_plugin_from_openai( - plugin_name="Klarna", - plugin_url="https://www.klarna.com/.well-known/ai-plugin.json", - ) - - # Query parameters for the function - # q = Category or product that needs to be searched for - # size = Number of results to be returned - # budget = Maximum price of the matching product in Local Currency - # countryCode = currently, only US, GB, DE, SE, and DK are supported - query_params = {"q": "Laptop", "size": "3", "budget": "200", "countryCode": "US"} - - result = await kernel.invoke(plugin["productsUsingGET"], **query_params) - - print(f"Function execution result: {result!s}") - - -if __name__ == "__main__": - asyncio.run(main()) diff --git a/python/semantic_kernel/connectors/openai_plugin/__init__.py b/python/semantic_kernel/connectors/openai_plugin/__init__.py deleted file mode 100644 index 4d89e5ac2279..000000000000 --- a/python/semantic_kernel/connectors/openai_plugin/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -from semantic_kernel.connectors.openai_plugin.openai_authentication_config import ( - OpenAIAuthenticationConfig, - OpenAIAuthenticationType, -) -from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, -) -from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils - -__all__ = [ - "OpenAIAuthenticationConfig", - "OpenAIAuthenticationType", - "OpenAIFunctionExecutionParameters", - "OpenAIUtils", -] diff --git a/python/semantic_kernel/connectors/openai_plugin/openai_authentication_config.py b/python/semantic_kernel/connectors/openai_plugin/openai_authentication_config.py deleted file mode 100644 index e9025334d159..000000000000 --- a/python/semantic_kernel/connectors/openai_plugin/openai_authentication_config.py +++ /dev/null @@ -1,38 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - - -from enum import Enum - -from pydantic import HttpUrl -from typing_extensions import deprecated - -from semantic_kernel.kernel_pydantic import KernelBaseModel - - -@deprecated("The `OpenAIAuthenticationType` class is deprecated; use the `OpenAPI` plugin instead.", category=None) -class OpenAIAuthenticationType(str, Enum): - """OpenAI authentication types.""" - - OAuth = "oauth" - NoneType = "none" - - -@deprecated("The `OpenAIAuthenticationType` class is deprecated; use the `OpenAPI` plugin instead.", category=None) -class OpenAIAuthorizationType(str, Enum): - """OpenAI authorization types.""" - - Bearer = "Bearer" - Basic = "Basic" - - -@deprecated("The `OpenAIAuthenticationConfig` class is deprecated; use the `OpenAPI` plugin instead.", category=None) -class OpenAIAuthenticationConfig(KernelBaseModel): - """OpenAI authentication configuration.""" - - type: OpenAIAuthenticationType | None = None - authorization_type: OpenAIAuthorizationType | None = None - client_url: HttpUrl | None = None - authorization_url: HttpUrl | None = None - authorization_content_type: str | None = None - scope: str | None = None - verification_tokens: dict[str, str] | None = None diff --git a/python/semantic_kernel/connectors/openai_plugin/openai_function_execution_parameters.py b/python/semantic_kernel/connectors/openai_plugin/openai_function_execution_parameters.py deleted file mode 100644 index b590f3f28898..000000000000 --- a/python/semantic_kernel/connectors/openai_plugin/openai_function_execution_parameters.py +++ /dev/null @@ -1,48 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - - -from collections.abc import Awaitable, Callable -from typing import TYPE_CHECKING, Any -from urllib.parse import urlparse - -import httpx -from pydantic import Field -from typing_extensions import deprecated - -from semantic_kernel.kernel_pydantic import KernelBaseModel - -if TYPE_CHECKING: - from semantic_kernel.connectors.openapi_plugin import ( - OperationSelectionPredicateContext, - ) - -OpenAIAuthCallbackType = Callable[..., Awaitable[Any]] - - -@deprecated( - "The `OpenAIFunctionExecutionParameters` class is deprecated; use the `OpenAPI` plugin instead.", category=None -) -class OpenAIFunctionExecutionParameters(KernelBaseModel): - """OpenAI function execution parameters.""" - - auth_callback: OpenAIAuthCallbackType | None = None - http_client: httpx.AsyncClient | None = None - server_url_override: str | None = None - ignore_non_compliant_errors: bool = False - user_agent: str | None = None - enable_dynamic_payload: bool = True - enable_payload_namespacing: bool = False - operations_to_exclude: list[str] = Field(default_factory=list, description="The operationId(s) to exclude") - operation_selection_predicate: Callable[["OperationSelectionPredicateContext"], bool] | None = None - - def model_post_init(self, __context: Any) -> None: - """Post initialization method for the model.""" - from semantic_kernel.utils.telemetry.user_agent import HTTP_USER_AGENT - - if self.server_url_override: - parsed_url = urlparse(self.server_url_override) - if not parsed_url.scheme or not parsed_url.netloc: - raise ValueError(f"Invalid server_url_override: {self.server_url_override}") - - if not self.user_agent: - self.user_agent = HTTP_USER_AGENT diff --git a/python/semantic_kernel/connectors/openai_plugin/openai_utils.py b/python/semantic_kernel/connectors/openai_plugin/openai_utils.py deleted file mode 100644 index 9e95374f7f5a..000000000000 --- a/python/semantic_kernel/connectors/openai_plugin/openai_utils.py +++ /dev/null @@ -1,32 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - - -import logging -from typing import Any - -from typing_extensions import deprecated - -from semantic_kernel.exceptions.function_exceptions import PluginInitializationError - -logger: logging.Logger = logging.getLogger(__name__) - - -@deprecated("The `OpenAIUtils` class is deprecated; use the `OpenAPI` plugin instead.", category=None) -class OpenAIUtils: - """Utility functions for OpenAI plugins.""" - - @staticmethod - def parse_openai_manifest_for_openapi_spec_url(plugin_json: dict[str, Any]) -> str: - """Extract the OpenAPI Spec URL from the plugin JSON.""" - try: - api_type = plugin_json["api"]["type"] - except KeyError as ex: - raise PluginInitializationError("OpenAI manifest is missing the API type.") from ex - - if api_type != "openapi": - raise PluginInitializationError("OpenAI manifest is not of type OpenAPI.") - - try: - return plugin_json["api"]["url"] - except KeyError as ex: - raise PluginInitializationError("OpenAI manifest is missing the OpenAPI Spec URL.") from ex diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py index b5ff0ec2ae4e..d671d3b25573 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_function_execution_parameters.py @@ -1,20 +1,18 @@ # Copyright (c) Microsoft. All rights reserved. from collections.abc import Awaitable, Callable -from typing import TYPE_CHECKING, Any +from typing import Any from urllib.parse import urlparse import httpx from pydantic import Field +from semantic_kernel.connectors.openapi_plugin.operation_selection_predicate_context import ( + OperationSelectionPredicateContext, +) from semantic_kernel.kernel_pydantic import KernelBaseModel from semantic_kernel.utils.experimental_decorator import experimental_class -if TYPE_CHECKING: - from semantic_kernel.connectors.openapi_plugin import ( - OperationSelectionPredicateContext, - ) - AuthCallbackType = Callable[..., Awaitable[Any]] @@ -30,7 +28,7 @@ class OpenAPIFunctionExecutionParameters(KernelBaseModel): enable_dynamic_payload: bool = True enable_payload_namespacing: bool = False operations_to_exclude: list[str] = Field(default_factory=list, description="The operationId(s) to exclude") - operation_selection_predicate: Callable[["OperationSelectionPredicateContext"], bool] | None = None + operation_selection_predicate: Callable[[OperationSelectionPredicateContext], bool] | None = None def model_post_init(self, __context: Any) -> None: """Post initialization method for the model.""" diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_manager.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_manager.py index c5823c7d559f..e2bb641b601b 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_manager.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_manager.py @@ -21,9 +21,6 @@ from semantic_kernel.utils.experimental_decorator import experimental_function if TYPE_CHECKING: - from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, - ) from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import ( OpenAPIFunctionExecutionParameters, ) @@ -36,7 +33,7 @@ def create_functions_from_openapi( plugin_name: str, openapi_document_path: str | None = None, openapi_parsed_spec: dict[str, Any] | None = None, - execution_settings: "OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None" = None, + execution_settings: "OpenAPIFunctionExecutionParameters | None" = None, ) -> list[KernelFunctionFromMethod]: """Creates the functions from OpenAPI document. @@ -106,7 +103,7 @@ def _create_function_from_operation( runner: OpenApiRunner, operation: RestApiOperation, plugin_name: str | None = None, - execution_parameters: "OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None" = None, + execution_parameters: "OpenAPIFunctionExecutionParameters | None" = None, document_uri: str | None = None, security: list[RestApiSecurityRequirement] | None = None, ) -> KernelFunctionFromMethod: diff --git a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py index a08276de387d..67df6f168a7e 100644 --- a/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py +++ b/python/semantic_kernel/connectors/openapi_plugin/openapi_parser.py @@ -24,9 +24,6 @@ from semantic_kernel.exceptions.function_exceptions import PluginInitializationError if TYPE_CHECKING: - from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, - ) from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import ( OpenAPIFunctionExecutionParameters, ) @@ -197,7 +194,7 @@ def _create_security_requirements( def create_rest_api_operations( self, parsed_document: Any, - execution_settings: "OpenAIFunctionExecutionParameters | OpenAPIFunctionExecutionParameters | None" = None, + execution_settings: "OpenAPIFunctionExecutionParameters | None" = None, ) -> dict[str, RestApiOperation]: """Create REST API operations from the parsed OpenAPI document. @@ -218,12 +215,13 @@ def create_rest_api_operations( servers = parsed_document.get("servers", []) + server_urls: list[dict[str, Any]] = [] + if execution_settings and execution_settings.server_url_override: # Override the servers with the provided URL server_urls = [{"url": execution_settings.server_url_override, "variables": {}}] elif servers: # Process servers, ensuring we capture their variables - server_urls = [] for server in servers: server_entry = { "url": server.get("url", "/"), diff --git a/python/semantic_kernel/functions/kernel_function_extension.py b/python/semantic_kernel/functions/kernel_function_extension.py index 6be93259122d..84439dc7fea1 100644 --- a/python/semantic_kernel/functions/kernel_function_extension.py +++ b/python/semantic_kernel/functions/kernel_function_extension.py @@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any, Literal from pydantic import Field, field_validator -from typing_extensions import deprecated from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.exceptions import KernelFunctionNotFoundError, KernelPluginNotFoundError @@ -19,9 +18,6 @@ from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig if TYPE_CHECKING: - from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, - ) from semantic_kernel.connectors.openapi_plugin.openapi_function_execution_parameters import ( OpenAPIFunctionExecutionParameters, ) @@ -238,43 +234,6 @@ def add_plugin_from_openapi( ) ) - @deprecated( - "The `add_plugin_from_openai` method is deprecated; use the `add_plugin_from_openapi` method instead.", - category=None, - ) - async def add_plugin_from_openai( - self, - plugin_name: str, - plugin_url: str | None = None, - plugin_str: str | None = None, - execution_parameters: "OpenAIFunctionExecutionParameters | None" = None, - description: str | None = None, - ) -> KernelPlugin: - """Add a plugin from an OpenAPI document. - - Args: - plugin_name (str): The name of the plugin - plugin_url (str | None): The URL of the plugin - plugin_str (str | None): The JSON string of the plugin - execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters - description (str | None): The description of the plugin - - Returns: - KernelPlugin: The imported plugin - - Raises: - PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided - """ - return self.add_plugin( - await KernelPlugin.from_openai( - plugin_name=plugin_name, - plugin_url=plugin_url, - plugin_str=plugin_str, - execution_parameters=execution_parameters, - description=description, - ) - ) - def get_plugin(self, plugin_name: str) -> "KernelPlugin": """Get a plugin by name. diff --git a/python/semantic_kernel/functions/kernel_plugin.py b/python/semantic_kernel/functions/kernel_plugin.py index f4bbb7b71342..33e81c4ed8ee 100644 --- a/python/semantic_kernel/functions/kernel_plugin.py +++ b/python/semantic_kernel/functions/kernel_plugin.py @@ -2,7 +2,6 @@ import importlib import inspect -import json import logging import os from collections.abc import Generator, ItemsView @@ -11,17 +10,8 @@ from types import MethodType from typing import TYPE_CHECKING, Annotated, Any, TypeVar -import httpx from pydantic import Field, StringConstraints -from typing_extensions import deprecated - -from semantic_kernel.connectors.openai_plugin.openai_authentication_config import OpenAIAuthenticationConfig -from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, -) -from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils -from semantic_kernel.connectors.openapi_plugin.openapi_manager import create_functions_from_openapi -from semantic_kernel.connectors.utils.document_loader import DocumentLoader + from semantic_kernel.data.text_search.text_search import TextSearch from semantic_kernel.exceptions import PluginInitializationError from semantic_kernel.exceptions.function_exceptions import FunctionInitializationError @@ -82,13 +72,6 @@ class KernelPlugin(KernelBaseModel): execution_settings: OpenAPIFunctionExecutionParameters | None = None, description: str | None = None): Create a plugin from an OpenAPI document. - from_openai( - plugin_name: str, - plugin_url: str | None = None, - plugin_str: str | None = None, - execution_parameters: OpenAIFunctionExecutionParameters | None = None, - description: str | None = None): - Create a plugin from the Open AI manifest. """ @@ -378,6 +361,8 @@ def from_openapi( Raises: PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided """ + from semantic_kernel.connectors.openapi_plugin.openapi_manager import create_functions_from_openapi + if not openapi_document_path and not openapi_parsed_spec: raise PluginInitializationError("Either the OpenAPI document path or a parsed OpenAPI spec is required.") @@ -392,77 +377,6 @@ def from_openapi( ), ) - @deprecated( - "The `OpenAI` plugin is deprecated; use the `from_openapi` method to add an `OpenAPI` plugin instead.", - category=None, - ) - @classmethod - async def from_openai( - cls: type[_T], - plugin_name: str, - plugin_url: str | None = None, - plugin_str: str | None = None, - execution_parameters: "OpenAIFunctionExecutionParameters | None" = None, - description: str | None = None, - ) -> _T: - """Create a plugin from the Open AI manifest. - - Args: - plugin_name (str): The name of the plugin - plugin_url (str | None): The URL of the plugin - plugin_str (str | None): The JSON string of the plugin - execution_parameters (OpenAIFunctionExecutionParameters | None): The execution parameters - description (str | None): The description of the plugin - - Returns: - KernelPlugin: The created plugin - - Raises: - PluginInitializationError: if the plugin URL or plugin JSON/YAML is not provided - """ - if execution_parameters is None: - execution_parameters = OpenAIFunctionExecutionParameters() - - if plugin_str is not None: - # Load plugin from the provided JSON string/YAML string - openai_manifest = plugin_str - elif plugin_url is not None: - # Load plugin from the URL - http_client = ( - execution_parameters.http_client if execution_parameters.http_client else httpx.AsyncClient(timeout=5) - ) - openai_manifest = await DocumentLoader.from_uri( - url=plugin_url, http_client=http_client, auth_callback=None, user_agent=execution_parameters.user_agent - ) - else: - raise PluginInitializationError("Either plugin_url or plugin_json must be provided.") - - try: - plugin_json = json.loads(openai_manifest) - except json.JSONDecodeError as ex: - raise PluginInitializationError("Parsing of Open AI manifest for auth config failed.") from ex - openai_auth_config = OpenAIAuthenticationConfig(**plugin_json["auth"]) - openapi_spec_url = OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json=plugin_json) - - # Modify the auth callback in execution parameters if it's provided - if execution_parameters and execution_parameters.auth_callback: - initial_auth_callback = execution_parameters.auth_callback - - async def custom_auth_callback(**kwargs: Any): - return await initial_auth_callback(plugin_name, openai_auth_config, **kwargs) # pragma: no cover - - execution_parameters.auth_callback = custom_auth_callback - - return cls( - name=plugin_name, - description=description, - functions=create_functions_from_openapi( # type: ignore - plugin_name=plugin_name, - openapi_document_path=openapi_spec_url, - execution_settings=execution_parameters, - ), - ) - @classmethod def from_python_file( cls: type[_T], diff --git a/python/tests/conftest.py b/python/tests/conftest.py index aba80113c333..3be0430a8ad4 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -15,9 +15,6 @@ from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import ( OpenAIEmbeddingPromptExecutionSettings, ) -from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, -) from semantic_kernel.data.record_definition.vector_store_model_decorator import vectorstoremodel from semantic_kernel.data.record_definition.vector_store_model_definition import VectorStoreRecordDefinition from semantic_kernel.data.record_definition.vector_store_record_fields import ( @@ -651,10 +648,3 @@ class DataModelClass(BaseModel): key: Annotated[str, VectorStoreRecordKeyField()] return DataModelClass - - -@fixture -def define_openai_predicate_context(): - from semantic_kernel.connectors.openapi_plugin import OperationSelectionPredicateContext # noqa: F401 - - OpenAIFunctionExecutionParameters.model_rebuild() diff --git a/python/tests/samples/test_concepts.py b/python/tests/samples/test_concepts.py index e108221d6217..d2455d4f4d6d 100644 --- a/python/tests/samples/test_concepts.py +++ b/python/tests/samples/test_concepts.py @@ -44,8 +44,6 @@ from samples.concepts.plugins.openai_function_calling_with_custom_plugin import ( main as openai_function_calling_with_custom_plugin, ) -from samples.concepts.plugins.openai_plugin_azure_key_vault import main as openai_plugin_azure_key_vault -from samples.concepts.plugins.openai_plugin_klarna import main as openai_plugin_klarna from samples.concepts.plugins.plugins_from_dir import main as plugins_from_dir from samples.concepts.prompt_templates.azure_chat_gpt_api_handlebars import main as azure_chat_gpt_api_handlebars from samples.concepts.prompt_templates.azure_chat_gpt_api_jinja2 import main as azure_chat_gpt_api_jinja2 @@ -195,22 +193,6 @@ os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples." ), ), - param( - openai_plugin_azure_key_vault, - ["Create a secret with the name 'Foo' and value 'Bar'", "exit"], - id="openai_plugin_azure_key_vault", - marks=pytest.mark.skipif( - os.getenv(COMPLETIONS_CONCEPT_SAMPLE, None) is None, reason="Not running completion samples." - ), - ), - param( - openai_plugin_klarna, - [], - id="openai_plugin_klarna", - marks=pytest.mark.skip( - reason="Temporarily: https://www.klarna.com/us/shopping/public/openai/v0/api-docs/ returns 404" - ), - ), param( plugins_from_dir, [], diff --git a/python/tests/unit/connectors/openai_plugin/test_openai_plugin.py b/python/tests/unit/connectors/openai_plugin/test_openai_plugin.py deleted file mode 100644 index 000463070721..000000000000 --- a/python/tests/unit/connectors/openai_plugin/test_openai_plugin.py +++ /dev/null @@ -1,31 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - - -import pytest - -from semantic_kernel.connectors.openai_plugin.openai_utils import OpenAIUtils -from semantic_kernel.exceptions import PluginInitializationError - - -def test_parse_openai_manifest_for_openapi_spec_url_valid(): - plugin_json = {"api": {"type": "openapi", "url": "https://example.com/openapi.json"}} - result = OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json) - assert result == "https://example.com/openapi.json" - - -def test_parse_openai_manifest_for_openapi_spec_url_missing_api_type(): - plugin_json = {"api": {}} - with pytest.raises(PluginInitializationError, match="OpenAI manifest is missing the API type."): - OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json) - - -def test_parse_openai_manifest_for_openapi_spec_url_invalid_api_type(): - plugin_json = {"api": {"type": "other", "url": "https://example.com/openapi.json"}} - with pytest.raises(PluginInitializationError, match="OpenAI manifest is not of type OpenAPI."): - OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json) - - -def test_parse_openai_manifest_for_openapi_spec_url_missing_url(): - plugin_json = {"api": {"type": "openapi"}} - with pytest.raises(PluginInitializationError, match="OpenAI manifest is missing the OpenAPI Spec URL."): - OpenAIUtils.parse_openai_manifest_for_openapi_spec_url(plugin_json) diff --git a/python/tests/unit/connectors/openapi_plugin/test_sk_openapi.py b/python/tests/unit/connectors/openapi_plugin/test_sk_openapi.py index 094d57619c53..1d25486b5a86 100644 --- a/python/tests/unit/connectors/openapi_plugin/test_sk_openapi.py +++ b/python/tests/unit/connectors/openapi_plugin/test_sk_openapi.py @@ -733,10 +733,6 @@ async def dummy_auth_callback(**kwargs): @pytest.fixture def openapi_runner_with_predicate_callback(): - from semantic_kernel.connectors.openapi_plugin import OperationSelectionPredicateContext # noqa: F401 - - OpenAPIFunctionExecutionParameters.model_rebuild() - # Define a dummy predicate callback def predicate_callback(context): # Skip operations with DELETE method or containing 'internal' in the path @@ -753,7 +749,7 @@ def predicate_callback(context): return runner, operations, exec_settings -def test_predicate_callback_applied(openapi_runner_with_predicate_callback, define_openai_predicate_context): +def test_predicate_callback_applied(openapi_runner_with_predicate_callback): _, operations, exec_settings = openapi_runner_with_predicate_callback skipped_operations = [] @@ -813,10 +809,6 @@ async def test_run_operation_with_error(mock_request, openapi_runner): def test_invalid_server_url_override(): - from semantic_kernel.connectors.openapi_plugin import OperationSelectionPredicateContext # noqa: F401 - - OpenAPIFunctionExecutionParameters.model_rebuild() - with pytest.raises(ValueError, match="Invalid server_url_override: invalid_url"): params = OpenAPIFunctionExecutionParameters(server_url_override="invalid_url") params.model_post_init(None) diff --git a/python/tests/unit/functions/test_kernel_plugins.py b/python/tests/unit/functions/test_kernel_plugins.py index 8e487e7022cd..cdcae62915b2 100644 --- a/python/tests/unit/functions/test_kernel_plugins.py +++ b/python/tests/unit/functions/test_kernel_plugins.py @@ -3,16 +3,11 @@ import os from collections.abc import Callable from typing import Any -from unittest.mock import AsyncMock, patch -import httpx import pytest from pytest import raises from semantic_kernel.connectors.ai import PromptExecutionSettings -from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, -) from semantic_kernel.connectors.openapi_plugin.openapi_parser import OpenApiParser from semantic_kernel.exceptions.function_exceptions import PluginInitializationError from semantic_kernel.functions import kernel_function @@ -22,7 +17,6 @@ from semantic_kernel.functions.kernel_plugin import KernelPlugin from semantic_kernel.prompt_template.input_variable import InputVariable from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig -from semantic_kernel.utils.telemetry.user_agent import HTTP_USER_AGENT @pytest.fixture @@ -497,80 +491,6 @@ def test_from_object_class(custom_plugin_class): assert plugin.functions.get("getLightStatus") is not None -@patch("semantic_kernel.connectors.openai_plugin.openai_utils.OpenAIUtils.parse_openai_manifest_for_openapi_spec_url") -async def test_from_openai_from_file(mock_parse_openai_manifest, define_openai_predicate_context): - openai_spec_file = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins") - with open(os.path.join(openai_spec_file, "TestOpenAIPlugin", "akv-openai.json")) as file: - openai_spec = file.read() - - openapi_spec_file_path = os.path.join( - os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml" - ) - mock_parse_openai_manifest.return_value = openapi_spec_file_path - - plugin = await KernelPlugin.from_openai( - plugin_name="TestOpenAIPlugin", - plugin_str=openai_spec, - execution_parameters=OpenAIFunctionExecutionParameters( - http_client=AsyncMock(spec=httpx.AsyncClient), - auth_callback=AsyncMock(), - server_url_override="http://localhost", - enable_dynamic_payload=True, - ), - ) - assert plugin is not None - assert plugin.name == "TestOpenAIPlugin" - assert plugin.functions.get("GetSecret") is not None - assert plugin.functions.get("SetSecret") is not None - - -@patch("httpx.AsyncClient.get") -@patch("semantic_kernel.connectors.openai_plugin.openai_utils.OpenAIUtils.parse_openai_manifest_for_openapi_spec_url") -async def test_from_openai_plugin_from_url(mock_parse_openai_manifest, mock_get, define_openai_predicate_context): - openai_spec_file_path = os.path.join( - os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAIPlugin", "akv-openai.json" - ) - with open(openai_spec_file_path) as file: - openai_spec = file.read() - - openapi_spec_file_path = os.path.join( - os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml" - ) - mock_parse_openai_manifest.return_value = openapi_spec_file_path - - request = httpx.Request(method="GET", url="http://fake-url.com/akv-openai.json") - - response = httpx.Response(200, text=openai_spec, request=request) - mock_get.return_value = response - - fake_plugin_url = "http://fake-url.com/akv-openai.json" - plugin = await KernelPlugin.from_openai( - plugin_name="TestOpenAIPlugin", - plugin_url=fake_plugin_url, - execution_parameters=OpenAIFunctionExecutionParameters( - auth_callback=AsyncMock(), - server_url_override="http://localhost", - enable_dynamic_payload=True, - ), - ) - assert plugin is not None - assert plugin.name == "TestOpenAIPlugin" - assert plugin.functions.get("GetSecret") is not None - assert plugin.functions.get("SetSecret") is not None - - mock_get.assert_awaited_once_with(fake_plugin_url, headers={"User-Agent": HTTP_USER_AGENT}) - - -async def test_from_openai_fail(define_openai_predicate_context): - with raises(PluginInitializationError): - await KernelPlugin.from_openai(plugin_name="TestOpenAIPlugin") - - -async def test_from_openai_fail_json_parsing(define_openai_predicate_context): - with raises(PluginInitializationError): - await KernelPlugin.from_openai(plugin_name="TestOpenAIPlugin", plugin_str="test") - - def test_from_openapi(): openapi_spec_file = os.path.join( os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml" diff --git a/python/tests/unit/kernel/test_kernel.py b/python/tests/unit/kernel/test_kernel.py index ef935c030b57..0e7148ddf2c0 100644 --- a/python/tests/unit/kernel/test_kernel.py +++ b/python/tests/unit/kernel/test_kernel.py @@ -4,16 +4,12 @@ from typing import Union from unittest.mock import AsyncMock, MagicMock, patch -import httpx import pytest from semantic_kernel import Kernel from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings -from semantic_kernel.connectors.openai_plugin.openai_function_execution_parameters import ( - OpenAIFunctionExecutionParameters, -) from semantic_kernel.const import METADATA_EXCEPTION_KEY from semantic_kernel.contents import ChatMessageContent from semantic_kernel.contents.chat_history import ChatHistory @@ -588,34 +584,6 @@ def func2(arg1: str) -> str: assert len(plugin.functions) == 2 -@patch("semantic_kernel.connectors.openai_plugin.openai_utils.OpenAIUtils.parse_openai_manifest_for_openapi_spec_url") -async def test_add_plugin_from_openai(mock_parse_openai_manifest, kernel: Kernel, define_openai_predicate_context): - base_folder = os.path.join(os.path.dirname(__file__), "../../assets/test_plugins") - with open(os.path.join(base_folder, "TestOpenAIPlugin", "akv-openai.json")) as file: - openai_spec = file.read() - - openapi_spec_file_path = os.path.join( - os.path.dirname(__file__), base_folder, "TestOpenAPIPlugin", "akv-openapi.yaml" - ) - mock_parse_openai_manifest.return_value = openapi_spec_file_path - - await kernel.add_plugin_from_openai( - plugin_name="TestOpenAIPlugin", - plugin_str=openai_spec, - execution_parameters=OpenAIFunctionExecutionParameters( - http_client=AsyncMock(spec=httpx.AsyncClient), - auth_callback=AsyncMock(), - server_url_override="http://localhost", - enable_dynamic_payload=True, - ), - ) - plugin = kernel.get_plugin(plugin_name="TestOpenAIPlugin") - assert plugin is not None - assert plugin.name == "TestOpenAIPlugin" - assert plugin.functions.get("GetSecret") is not None - assert plugin.functions.get("SetSecret") is not None - - def test_import_plugin_from_openapi(kernel: Kernel): openapi_spec_file = os.path.join( os.path.dirname(__file__), "../../assets/test_plugins", "TestOpenAPIPlugin", "akv-openapi.yaml"