diff --git a/.github/workflows/python-integration-tests.yml b/.github/workflows/python-integration-tests.yml index 856c01d156..b02fc8eae1 100644 --- a/.github/workflows/python-integration-tests.yml +++ b/.github/workflows/python-integration-tests.yml @@ -76,25 +76,21 @@ jobs: env: # Set Azure credentials secret as an input HNSWLIB_NO_NATIVE: 1 Python_Integration_Tests: Python_Integration_Tests - AzureOpenAI__Label: azure-text-davinci-003 - AzureOpenAIEmbedding__Label: azure-text-embedding-ada-002 - AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }} - AzureOpenAI__Text__DeploymentName: ${{ vars.AZUREOPENAI__TEXT__DEPLOYMENTNAME }} - AzureOpenAIChat__DeploymentName: ${{ vars.AZUREOPENAI__CHAT__DEPLOYMENTNAME }} - AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDINGS__DEPLOYMENTNAME2 }} - AzureOpenAIEmbeddings_EastUS__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDINGS_EASTUS__DEPLOYMENTNAME}} - AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }} - AzureOpenAI_EastUS__Endpoint: ${{ secrets.AZUREOPENAI_EASTUS__ENDPOINT }} - AzureOpenAI_EastUS__ApiKey: ${{ secrets.AZUREOPENAI_EASTUS__APIKEY }} - AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }} - AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }} - AzureOpenAIEmbeddings__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }} - Bing__ApiKey: ${{ secrets.BING__APIKEY }} - OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }} - Pinecone__ApiKey: ${{ secrets.PINECONE__APIKEY }} - Postgres__Connectionstr: ${{secrets.POSTGRES__CONNECTIONSTR}} - AZURE_COGNITIVE_SEARCH_ADMIN_KEY: ${{secrets.AZURE_COGNITIVE_SEARCH_ADMIN_KEY}} - AZURE_COGNITIVE_SEARCH_ENDPOINT: ${{secrets.AZURE_COGNITIVE_SEARCH_ENDPOINT}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }} # azure-text-embedding-ada-002 + AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }} + AZURE_OPENAI_TEXT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_DEPLOYMENT_NAME }} + AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION }} + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + BING_API_KEY: ${{ secrets.BING_API_KEY }} + OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI_CHAT_MODEL_ID }} + OPENAI_TEXT_MODEL_ID: ${{ vars.OPENAI_TEXT_MODEL_ID }} + OPENAI_EMBEDDING_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + PINECONE_API_KEY: ${{ secrets.PINECONE__APIKEY }} + POSTGRES_CONNECTION_STRING: ${{secrets.POSTGRES__CONNECTIONSTR}} + AZURE_AI_SEARCH_API_KEY: ${{secrets.AZURE_AI_SEARCH_API_KEY}} + AZURE_AI_SEARCH_ENDPOINT: ${{secrets.AZURE_AI_SEARCH_ENDPOINT}} MONGODB_ATLAS_CONNECTION_STRING: ${{secrets.MONGODB_ATLAS_CONNECTION_STRING}} run: | if ${{ matrix.os == 'ubuntu-latest' }}; then @@ -142,25 +138,21 @@ jobs: env: # Set Azure credentials secret as an input HNSWLIB_NO_NATIVE: 1 Python_Integration_Tests: Python_Integration_Tests - AzureOpenAI__Label: azure-text-davinci-003 - AzureOpenAIEmbedding__Label: azure-text-embedding-ada-002 - AzureOpenAI__DeploymentName: ${{ vars.AZUREOPENAI__DEPLOYMENTNAME }} - AzureOpenAI__Text__DeploymentName: ${{ vars.AZUREOPENAI__TEXT__DEPLOYMENTNAME }} - AzureOpenAIChat__DeploymentName: ${{ vars.AZUREOPENAI__CHAT__DEPLOYMENTNAME }} - AzureOpenAIEmbeddings__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDINGS__DEPLOYMENTNAME2 }} - AzureOpenAIEmbeddings_EastUS__DeploymentName: ${{ vars.AZUREOPENAIEMBEDDINGS_EASTUS__DEPLOYMENTNAME}} - AzureOpenAI__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }} - AzureOpenAIEmbeddings__Endpoint: ${{ secrets.AZUREOPENAI__ENDPOINT }} - AzureOpenAI__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }} - AzureOpenAI_EastUS__Endpoint: ${{ secrets.AZUREOPENAI_EASTUS__ENDPOINT }} - AzureOpenAI_EastUS__ApiKey: ${{ secrets.AZUREOPENAI_EASTUS__APIKEY }} - AzureOpenAIEmbeddings__ApiKey: ${{ secrets.AZUREOPENAI__APIKEY }} - Bing__ApiKey: ${{ secrets.BING__APIKEY }} - OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }} - Pinecone__ApiKey: ${{ secrets.PINECONE__APIKEY }} - Postgres__Connectionstr: ${{secrets.POSTGRES__CONNECTIONSTR}} - AZURE_COGNITIVE_SEARCH_ADMIN_KEY: ${{secrets.AZURE_COGNITIVE_SEARCH_ADMIN_KEY}} - AZURE_COGNITIVE_SEARCH_ENDPOINT: ${{secrets.AZURE_COGNITIVE_SEARCH_ENDPOINT}} + AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME }} # azure-text-embedding-ada-002 + AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_CHAT_DEPLOYMENT_NAME }} + AZURE_OPENAI_TEXT_DEPLOYMENT_NAME: ${{ vars.AZURE_OPENAI_TEXT_DEPLOYMENT_NAME }} + AZURE_OPENAI_API_VERSION: ${{ vars.AZURE_OPENAI_API_VERSION }} + AZURE_OPENAI_ENDPOINT: ${{ secrets.AZURE_OPENAI_ENDPOINT }} + AZURE_OPENAI_API_KEY: ${{ secrets.AZURE_OPENAI_API_KEY }} + BING_API_KEY: ${{ secrets.BING_API_KEY }} + OPENAI_CHAT_MODEL_ID: ${{ vars.OPENAI_CHAT_MODEL_ID }} + OPENAI_TEXT_MODEL_ID: ${{ vars.OPENAI_TEXT_MODEL_ID }} + OPENAI_EMBEDDING_MODEL_ID: ${{ vars.OPENAI_EMBEDDING_MODEL_ID }} + OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} + PINECONE_API_KEY: ${{ secrets.PINECONE__APIKEY }} + POSTGRES_CONNECTION_STRING: ${{secrets.POSTGRES__CONNECTIONSTR}} + AZURE_AI_SEARCH_API_KEY: ${{secrets.AZURE_AI_SEARCH_API_KEY}} + AZURE_AI_SEARCH_ENDPOINT: ${{secrets.AZURE_AI_SEARCH_ENDPOINT}} MONGODB_ATLAS_CONNECTION_STRING: ${{secrets.MONGODB_ATLAS_CONNECTION_STRING}} run: | if ${{ matrix.os == 'ubuntu-latest' }}; then diff --git a/python/.env.example b/python/.env.example index b7154cdb70..d6a0e18dff 100644 --- a/python/.env.example +++ b/python/.env.example @@ -46,4 +46,9 @@ ASTRADB_APP_TOKEN="" ASTRADB_ID="" ASTRADB_REGION="" ASTRADB_KEYSPACE="" -ACA_POOL_MANAGEMENT_ENDPOINT="" \ No newline at end of file +ACA_POOL_MANAGEMENT_ENDPOINT="" +BOOKING_SAMPLE_CLIENT_ID="" +BOOKING_SAMPLE_TENANT_ID="" +BOOKING_SAMPLE_CLIENT_SECRET="" +BOOKING_SAMPLE_BUSINESS_ID="" +BOOKING_SAMPLE_SERVICE_ID="" \ No newline at end of file diff --git a/python/DEV_SETUP.md b/python/DEV_SETUP.md index 126fd62d2b..76cbcb8987 100644 --- a/python/DEV_SETUP.md +++ b/python/DEV_SETUP.md @@ -10,17 +10,31 @@ Make sure you have an [OpenAI API Key](https://platform.openai.com) or [Azure OpenAI service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=rest-api) -Copy those keys into a `.env` file (see the `.env.example` file): +There are two methods to manage keys, secrets, and endpoints: -```bash +1. Store them in environment variables. SK Python leverages pydantic settings to load keys, secrets, and endpoints. This means that there is a first attempt to load them from environment variables. The `.env` file naming applies to how the names should be stored as environment variables. + +2. If you'd like to use the `.env` file, you will need to configure the `.env` file with the following keys into a `.env` file (see the `.env.example` file): + +``` OPENAI_API_KEY="" OPENAI_ORG_ID="" -AZURE_OPENAI_DEPLOYMENT_NAME="" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_TEXT_DEPLOYMENT_NAME="" +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME="" AZURE_OPENAI_ENDPOINT="" AZURE_OPENAI_API_KEY="" ``` -We suggest adding a copy of the `.env` file under these folders: +You will then configure the Text/ChatCompletion class with the keyword argument `env_file_path`: + +```python +chat_completion = OpenAIChatCompletion(service_id="test", env_file_path=) +``` + +This optional `env_file_path` parameter will allow pydantic settings to use the `.env` file as a fallback to read the settings. + +If using the second method, we suggest adding a copy of the `.env` file under these folders: - [python/tests](tests) - [./samples/getting_started](./samples/getting_started). diff --git a/python/README.md b/python/README.md index 57e55c290e..db821e29dd 100644 --- a/python/README.md +++ b/python/README.md @@ -20,16 +20,30 @@ Make sure you have an [OpenAI API Key](https://platform.openai.com) or [Azure OpenAI service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=rest-api) -Copy those keys into a `.env` file (see the `.env.example` file): +There are two methods to manage keys, secrets, and endpoints: + +1. Store them in environment variables. SK Python leverages pydantic settings to load keys, secrets, and endpoints. This means that there is a first attempt to load them from environment variables. The `.env` file naming applies to how the names should be stored as environment variables. + +2. If you'd like to use the `.env` file, you will need to configure the `.env` file with the following keys in the file (see the `.env.example` file): ``` OPENAI_API_KEY="" OPENAI_ORG_ID="" -AZURE_OPENAI_DEPLOYMENT_NAME="" +AZURE_OPENAI_CHAT_DEPLOYMENT_NAME="" +AZURE_OPENAI_TEXT_DEPLOYMENT_NAME="" +AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME="" AZURE_OPENAI_ENDPOINT="" AZURE_OPENAI_API_KEY="" ``` +You will then configure the Text/ChatCompletion class with the keyword argument `env_file_path`: + +```python +chat_completion = OpenAIChatCompletion(service_id="test", env_file_path=) +``` + +This optional `env_file_path` parameter will allow pydantic settings to use the `.env` file as a fallback to read the settings. + # Running a prompt ```python @@ -37,30 +51,21 @@ import asyncio from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, AzureChatCompletion from semantic_kernel.prompt_template import PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env, azure_openai_settings_from_dot_env kernel = Kernel() # Prepare OpenAI service using credentials stored in the `.env` file -api_key, org_id = openai_settings_from_dot_env() service_id="chat-gpt" kernel.add_service( OpenAIChatCompletion( service_id=service_id, - ai_model_id="gpt-3.5-turbo", - api_key=api_key, - org_id=org_id ) ) # Alternative using Azure: -# deployment, api_key, endpoint = azure_openai_settings_from_dot_env() # kernel.add_service( # AzureChatCompletion( # service_id=service_id, -# deployment_name=deployment, -# endpoint=endpoint, -# api_key=api_key # ) # ) @@ -112,10 +117,10 @@ if __name__ == "__main__": ```python # Create a reusable function summarize function summarize = kernel.add_function( - function_name="tldr_function", - plugin_name="tldr_plugin", - prompt="{{$input}}\n\nOne line TLDR with the fewest words.", - prompt_template_settings=req_settings, + function_name="tldr_function", + plugin_name="tldr_plugin", + prompt="{{$input}}\n\nOne line TLDR with the fewest words.", + prompt_template_settings=req_settings, ) # Summarize the laws of thermodynamics diff --git a/python/poetry.lock b/python/poetry.lock index 3a2e4bb21e..5d3a489d6c 100644 --- a/python/poetry.lock +++ b/python/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "aiohttp" @@ -4430,6 +4430,25 @@ files = [ [package.dependencies] typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" +[[package]] +name = "pydantic-settings" +version = "2.2.1" +description = "Settings management using Pydantic" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pydantic_settings-2.2.1-py3-none-any.whl", hash = "sha256:0235391d26db4d2190cb9b31051c4b46882d28a51533f97440867f012d4da091"}, + {file = "pydantic_settings-2.2.1.tar.gz", hash = "sha256:00b9f6a5e95553590434c0fa01ead0b216c3e10bc54ae02e37f359948643c5ed"}, +] + +[package.dependencies] +pydantic = ">=2.3.0" +python-dotenv = ">=0.21.0" + +[package.extras] +toml = ["tomli (>=2.0.1)"] +yaml = ["pyyaml (>=6.0.1)"] + [[package]] name = "pygments" version = "2.17.2" @@ -4759,7 +4778,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -6831,4 +6849,4 @@ weaviate = ["weaviate-client"] [metadata] lock-version = "2.0" python-versions = "^3.10,<3.13" -content-hash = "947f0d69d4a2086ff91e5b4eebf2349ea11049579e05645a04a20cce15fd6e08" +content-hash = "8f37912da67cd7728e5b3555e5286fa4fe7a2faf63b240d26b6ae6360c3d2d7f" diff --git a/python/pyproject.toml b/python/pyproject.toml index 07ddcc700e..c4716ec24c 100644 --- a/python/pyproject.toml +++ b/python/pyproject.toml @@ -24,11 +24,11 @@ grpcio = [ { version = ">=1.60.0", python = ">=3.12" } ] openai = ">=1.0" -python-dotenv = "^1.0.1" regex = "^2023.6.3" openapi_core = ">=0.18,<0.20" prance = "^23.6.21.0" pydantic = "^2" +pydantic-settings = "^2.2.1" motor = "^3.3.2" defusedxml = "^0.7.1" pybars4 = "^0.9.13" diff --git a/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py b/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py index 8280faeea2..baae3b2f05 100644 --- a/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py +++ b/python/samples/concepts/auto_function_calling/azure_python_code_interpreter_function_calling.py @@ -20,10 +20,6 @@ from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.kernel import Kernel -from semantic_kernel.utils.settings import ( - azure_container_apps_settings_from_dot_env_as_dict, - azure_openai_settings_from_dot_env_as_dict, -) auth_token: AccessToken | None = None @@ -56,12 +52,11 @@ kernel = Kernel() service_id = "sessions-tool" chat_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) kernel.add_service(chat_service) sessions_tool = SessionsPythonTool( - **azure_container_apps_settings_from_dot_env_as_dict(), auth_callback=auth_callback, ) diff --git a/python/samples/concepts/auto_function_calling/chat_gpt_api_function_calling.py b/python/samples/concepts/auto_function_calling/chat_gpt_api_function_calling.py index 81e6f37bef..6c0f44a9c2 100644 --- a/python/samples/concepts/auto_function_calling/chat_gpt_api_function_calling.py +++ b/python/samples/concepts/auto_function_calling/chat_gpt_api_function_calling.py @@ -17,7 +17,6 @@ from semantic_kernel.contents.function_call_content import FunctionCallContent from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.core_plugins import MathPlugin, TimePlugin from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import openai_settings_from_dot_env if TYPE_CHECKING: from semantic_kernel.functions import KernelFunction @@ -38,12 +37,10 @@ you will return a full answer to me as soon as possible. kernel = Kernel() # Note: the underlying gpt-35/gpt-4 model version needs to be at least version 0613 to support tools. -api_key, org_id = openai_settings_from_dot_env() kernel.add_service( OpenAIChatCompletion( service_id="chat", ai_model_id="gpt-3.5-turbo-1106", - api_key=api_key, ), ) diff --git a/python/samples/concepts/chat_completion/azure_chat_gpt_api.py b/python/samples/concepts/chat_completion/azure_chat_gpt_api.py index 46acdbe54f..8771d135bb 100644 --- a/python/samples/concepts/chat_completion/azure_chat_gpt_api.py +++ b/python/samples/concepts/chat_completion/azure_chat_gpt_api.py @@ -7,7 +7,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion from semantic_kernel.contents import ChatHistory -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict logging.basicConfig(level=logging.WARNING) @@ -24,7 +23,7 @@ kernel = Kernel() service_id = "chat-gpt" chat_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/chat_completion/chat.py b/python/samples/concepts/chat_completion/chat.py index 21911b9298..1c51702cc8 100644 --- a/python/samples/concepts/chat_completion/chat.py +++ b/python/samples/concepts/chat_completion/chat.py @@ -6,7 +6,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import ChatHistory from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env prompt = """ ChatBot can have a conversation with you about any topic. @@ -21,11 +20,8 @@ ChatBot:> kernel = Kernel() -api_key, org_id = openai_settings_from_dot_env() service_id = "chat" -kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo-1106", api_key=api_key, org_id=org_id) -) +kernel.add_service(OpenAIChatCompletion(service_id=service_id)) settings = kernel.get_prompt_execution_settings_from_service_id(service_id) settings.max_tokens = 2000 diff --git a/python/samples/concepts/chat_completion/chat_gpt_api.py b/python/samples/concepts/chat_completion/chat_gpt_api.py index a229935095..cb231a4d03 100644 --- a/python/samples/concepts/chat_completion/chat_gpt_api.py +++ b/python/samples/concepts/chat_completion/chat_gpt_api.py @@ -6,7 +6,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import openai_settings_from_dot_env system_message = """ You are a chat bot. Your name is Mosscap and @@ -19,11 +18,8 @@ flowery prose. kernel = Kernel() -api_key, org_id = openai_settings_from_dot_env() service_id = "chat-gpt" -kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) -) +kernel.add_service(OpenAIChatCompletion(service_id=service_id)) settings = kernel.get_prompt_execution_settings_from_service_id(service_id) settings.max_tokens = 2000 diff --git a/python/samples/concepts/chat_completion/openai_logit_bias.py b/python/samples/concepts/chat_completion/openai_logit_bias.py index eb9f4d3901..0d2a7480a4 100644 --- a/python/samples/concepts/chat_completion/openai_logit_bias.py +++ b/python/samples/concepts/chat_completion/openai_logit_bias.py @@ -9,7 +9,6 @@ from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITe from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env """ Logit bias enables prioritizing certain tokens within a given output. @@ -31,10 +30,11 @@ def _prepare_input_chat(chat: ChatHistory): return "".join([f"{msg.role}: {msg.content}\n" for msg in chat]) -async def chat_request_example(kernel: Kernel, api_key, org_id): +async def chat_request_example(kernel: Kernel): service_id = "chat_service" openai_chat_completion = OpenAIChatCompletion( - service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id + service_id=service_id, + ai_model_id="gpt-3.5-turbo", ) kernel.add_service(openai_chat_completion) @@ -111,10 +111,11 @@ async def chat_request_example(kernel: Kernel, api_key, org_id): return chat, banned_words -async def text_complete_request_example(kernel: Kernel, api_key, org_id): +async def text_complete_request_example(kernel: Kernel): service_id = "text_service" openai_text_completion = OpenAITextCompletion( - service_id=service_id, ai_model_id="gpt-3.5-turbo-instruct", api_key=api_key, org_id=org_id + service_id=service_id, + ai_model_id="gpt-3.5-turbo-instruct", ) kernel.add_service(openai_text_completion) @@ -210,18 +211,17 @@ def _format_output(chat, banned_words) -> None: async def main() -> None: kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() print("Chat completion example:") print("------------------------") - chat, banned_words = await chat_request_example(kernel, api_key, org_id) + chat, banned_words = await chat_request_example(kernel) _format_output(chat, banned_words) print("------------------------") print("\nText completion example:") print("------------------------") - chat, banned_words = await text_complete_request_example(kernel, api_key, org_id) + chat, banned_words = await text_complete_request_example(kernel) _format_output(chat, banned_words) return diff --git a/python/samples/concepts/grounding/grounded.py b/python/samples/concepts/grounding/grounded.py index ed89c161d2..73ee6e117d 100644 --- a/python/samples/concepts/grounding/grounded.py +++ b/python/samples/concepts/grounding/grounded.py @@ -5,7 +5,6 @@ from samples.utils import Colors from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env def get_grounding_text(): @@ -56,22 +55,16 @@ def setup(use_azure: bool = False, plugin_name: str = "GroundingPlugin"): # Configure AI service used by the kernel if use_azure: - deployment, api_key, endpoint = azure_openai_settings_from_dot_env() service_id = "chat_completion" kernel.add_service( AzureChatCompletion( service_id=service_id, - deployment_name=deployment, - endpoint=endpoint, - api_key=api_key, - api_version="2023-12-01-preview", ), ) else: - api_key, org_id = openai_settings_from_dot_env() service_id = "chat-gpt" kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id), + OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo"), ) # note: using plugins from the samples folder diff --git a/python/samples/concepts/logging/setup_logging.py b/python/samples/concepts/logging/setup_logging.py index f3d2eb4c7c..3b189ad867 100644 --- a/python/samples/concepts/logging/setup_logging.py +++ b/python/samples/concepts/logging/setup_logging.py @@ -6,7 +6,6 @@ import os from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.utils.logging import setup_logging -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): @@ -17,12 +16,8 @@ async def main(): kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() - service_id = "chat-gpt" - kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) - ) + kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo")) plugins_directory = os.path.join(__file__, "../../../../../prompt_template_samples/") plugin = kernel.add_plugin(parent_directory=plugins_directory, plugin_name="FunPlugin") diff --git a/python/samples/concepts/memory/azure_cognitive_search_memory.py b/python/samples/concepts/memory/azure_cognitive_search_memory.py index adc9699d87..0580125185 100644 --- a/python/samples/concepts/memory/azure_cognitive_search_memory.py +++ b/python/samples/concepts/memory/azure_cognitive_search_memory.py @@ -2,11 +2,10 @@ import asyncio -from dotenv import dotenv_values - from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureTextCompletion, AzureTextEmbedding from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.core_plugins import TextMemoryPlugin from semantic_kernel.memory import SemanticTextMemory @@ -44,12 +43,8 @@ async def search_acs_memory_questions(memory: SemanticTextMemory) -> None: async def main() -> None: kernel = Kernel() - config = dotenv_values(".env") + azure_ai_search_settings = AzureAISearchSettings() - AZURE_COGNITIVE_SEARCH_ENDPOINT = config["AZURE_COGNITIVE_SEARCH_ENDPOINT"] - AZURE_COGNITIVE_SEARCH_ADMIN_KEY = config["AZURE_COGNITIVE_SEARCH_ADMIN_KEY"] - AZURE_OPENAI_API_KEY = config["AZURE_OPENAI_API_KEY"] - AZURE_OPENAI_ENDPOINT = config["AZURE_OPENAI_ENDPOINT"] vector_size = 1536 # Setting up OpenAI services for text completion and text embedding @@ -57,24 +52,20 @@ async def main() -> None: kernel.add_service( AzureTextCompletion( service_id=text_complete_service_id, - deployment_name="text-embedding-ada-002", - endpoint=AZURE_OPENAI_ENDPOINT, - api_key=AZURE_OPENAI_API_KEY, ), ) embedding_service_id = "ada" embedding_gen = AzureTextEmbedding( service_id=embedding_service_id, - deployment_name="text-embedding-ada-002", - endpoint=AZURE_OPENAI_ENDPOINT, - api_key=AZURE_OPENAI_API_KEY, ) kernel.add_service( embedding_gen, ) acs_connector = AzureCognitiveSearchMemoryStore( - vector_size, AZURE_COGNITIVE_SEARCH_ENDPOINT, AZURE_COGNITIVE_SEARCH_ADMIN_KEY + vector_size=vector_size, + search_endpoint=azure_ai_search_settings.endpoint, + admin_key=azure_ai_search_settings.api_key, ) memory = SemanticTextMemory(storage=acs_connector, embeddings_generator=embedding_gen) diff --git a/python/samples/concepts/memory/google_palm_chat_with_memory.py b/python/samples/concepts/memory/google_palm_chat_with_memory.py index eedc9214c8..0599826353 100644 --- a/python/samples/concepts/memory/google_palm_chat_with_memory.py +++ b/python/samples/concepts/memory/google_palm_chat_with_memory.py @@ -8,7 +8,6 @@ from semantic_kernel.core_plugins import TextMemoryPlugin from semantic_kernel.functions import KernelFunction from semantic_kernel.memory import SemanticTextMemory, VolatileMemoryStore from semantic_kernel.prompt_template import PromptTemplateConfig -from semantic_kernel.utils.settings import google_palm_settings_from_dot_env collection_id = "generic" @@ -82,12 +81,11 @@ async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool: async def main() -> None: kernel = Kernel() - apikey = google_palm_settings_from_dot_env() model_id = "models/embedding-gecko-001" - palm_text_embed = sk_gp.GooglePalmTextEmbedding(model_id, apikey) + palm_text_embed = sk_gp.GooglePalmTextEmbedding(model_id) kernel.add_service(palm_text_embed) chat_service_id = "models/chat-bison-001" - palm_chat_completion = sk_gp.GooglePalmChatCompletion(chat_service_id, apikey) + palm_chat_completion = sk_gp.GooglePalmChatCompletion(chat_service_id) kernel.add_service(palm_chat_completion) memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=palm_text_embed) diff --git a/python/samples/concepts/memory/memory.py b/python/samples/concepts/memory/memory.py index 01b570f5e4..980c36f7af 100644 --- a/python/samples/concepts/memory/memory.py +++ b/python/samples/concepts/memory/memory.py @@ -8,7 +8,6 @@ from semantic_kernel.core_plugins import TextMemoryPlugin from semantic_kernel.functions import KernelFunction from semantic_kernel.memory import SemanticTextMemory, VolatileMemoryStore from semantic_kernel.prompt_template import PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env collection_id = "generic" @@ -83,13 +82,11 @@ async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool: async def main() -> None: kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() service_id = "chat-gpt" - kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) - ) + kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo")) embedding_gen = OpenAITextEmbedding( - service_id="ada", ai_model_id="text-embedding-ada-002", api_key=api_key, org_id=org_id + service_id="ada", + ai_model_id="text-embedding-ada-002", ) kernel.add_service(embedding_gen) diff --git a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api.py b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api.py index 94e5b81076..92a6d0c6ec 100644 --- a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api.py +++ b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api.py @@ -10,28 +10,20 @@ from semantic_kernel.connectors.ai.open_ai import ( AzureChatPromptExecutionSettings, ExtraBody, ) +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import ( - azure_aisearch_settings_from_dot_env_as_dict, - azure_openai_settings_from_dot_env_as_dict, -) kernel = Kernel() logging.basicConfig(level=logging.INFO) -# Load Azure OpenAI Settings -aoai_settings = azure_openai_settings_from_dot_env_as_dict(include_api_version=True) - # For example, AI Search index may contain the following document: # Emily and David, two passionate scientists, met during a research expedition to Antarctica. # Bonded by their love for the natural world and shared curiosity, they uncovered a # groundbreaking phenomenon in glaciology that could potentially reshape our understanding of climate change. -azure_ai_search_settings = azure_aisearch_settings_from_dot_env_as_dict() - # Depending on the index that you use, you might need to enable the below # and adapt it so that it accurately reflects your index. @@ -43,15 +35,15 @@ azure_ai_search_settings = azure_aisearch_settings_from_dot_env_as_dict() # } # Create the data source settings +azure_ai_search_settings = AzureAISearchSettings.create() -az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings) +az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings.model_dump()) extra = ExtraBody(data_sources=[az_source]) req_settings = AzureChatPromptExecutionSettings(service_id="default", extra_body=extra) # When using data, use the 2024-02-15-preview API version. chat_service = AzureChatCompletion( service_id="chat-gpt", - **aoai_settings, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_function_calling.py b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_function_calling.py index f5d8ff8ee0..55cfa5a495 100644 --- a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_function_calling.py +++ b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_function_calling.py @@ -12,6 +12,7 @@ from semantic_kernel.connectors.ai.open_ai import ( AzureChatPromptExecutionSettings, ExtraBody, ) +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.contents import ChatHistory from semantic_kernel.core_plugins import TimePlugin from semantic_kernel.functions import KernelArguments @@ -25,12 +26,9 @@ logging.basicConfig(level=logging.DEBUG) kernel = sk.Kernel() -# Load Azure OpenAI Settings -deployment, api_key, endpoint = sk.azure_openai_settings_from_dot_env(include_deployment=True) - # Create the data source settings -azure_ai_search_settings = sk.azure_aisearch_settings_from_dot_env_as_dict() -az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings) +azure_ai_search_settings = AzureAISearchSettings() +az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings.model_dump()) extra = ExtraBody(data_sources=[az_source]) req_settings = AzureChatPromptExecutionSettings(service_id="chat-gpt", extra_body=extra, tool_choice="auto") @@ -42,10 +40,6 @@ req_settings = AzureChatPromptExecutionSettings(service_id="chat-gpt", extra_bod chat_service = AzureChatCompletion( service_id="chat-gpt", - deployment_name=deployment, - api_key=api_key, - endpoint=endpoint, - api_version="2024-02-15-preview", ) kernel.add_service( chat_service, diff --git a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_vector_search.py b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_vector_search.py index 2f823d572c..9e0cf43643 100644 --- a/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_vector_search.py +++ b/python/samples/concepts/on_your_data/azure_chat_gpt_with_data_api_vector_search.py @@ -9,28 +9,22 @@ from semantic_kernel.connectors.ai.open_ai import ( AzureChatPromptExecutionSettings, ExtraBody, ) +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments from semantic_kernel.kernel import Kernel from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import ( - azure_aisearch_settings_from_dot_env_as_dict, - azure_openai_settings_from_dot_env_as_dict, -) kernel = Kernel() logging.basicConfig(level=logging.DEBUG) -# Load Azure OpenAI Settings -aoai_settings = azure_openai_settings_from_dot_env_as_dict(include_api_version=True) - # For example, AI Search index may contain the following document: # Emily and David, two passionate scientists, met during a research expedition to Antarctica. # Bonded by their love for the natural world and shared curiosity, they uncovered a # groundbreaking phenomenon in glaciology that could potentially reshape our understanding of climate change. -azure_ai_search_settings = azure_aisearch_settings_from_dot_env_as_dict() +azure_ai_search_settings = AzureAISearchSettings() # This example index has fields "title", "chunk", and "vector". # Add fields mapping to the settings. @@ -48,7 +42,7 @@ azure_ai_search_settings["embeddingDependency"] = { azure_ai_search_settings["query_type"] = "vector" # Create the data source settings -az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings) +az_source = AzureAISearchDataSource(parameters=azure_ai_search_settings.model_dump()) extra = ExtraBody(data_sources=[az_source]) service_id = "chat-gpt" req_settings = AzureChatPromptExecutionSettings(service_id=service_id, extra_body=extra) @@ -56,7 +50,6 @@ req_settings = AzureChatPromptExecutionSettings(service_id=service_id, extra_bod # When using data, use the 2024-02-15-preview API version. chat_service = AzureChatCompletion( service_id="chat-gpt", - **aoai_settings, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/planners/azure_openai_function_calling_stepwise_planner.py b/python/samples/concepts/planners/azure_openai_function_calling_stepwise_planner.py index 66cd1d55b2..dbc19b2faa 100644 --- a/python/samples/concepts/planners/azure_openai_function_calling_stepwise_planner.py +++ b/python/samples/concepts/planners/azure_openai_function_calling_stepwise_planner.py @@ -7,7 +7,6 @@ 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 -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict async def main(): @@ -16,7 +15,7 @@ async def main(): service_id = "planner" kernel.add_service( AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ), ) diff --git a/python/samples/concepts/planners/openai_function_calling_stepwise_planner.py b/python/samples/concepts/planners/openai_function_calling_stepwise_planner.py index 4a5d07e788..88e994dfda 100644 --- a/python/samples/concepts/planners/openai_function_calling_stepwise_planner.py +++ b/python/samples/concepts/planners/openai_function_calling_stepwise_planner.py @@ -7,19 +7,16 @@ 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 -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): kernel = Kernel() service_id = "planner" - api_key, _ = openai_settings_from_dot_env() kernel.add_service( OpenAIChatCompletion( service_id=service_id, ai_model_id="gpt-3.5-turbo-1106", - api_key=api_key, ), ) diff --git a/python/samples/concepts/planners/sequential_planner.py b/python/samples/concepts/planners/sequential_planner.py index 385a7fd432..3715daab9c 100644 --- a/python/samples/concepts/planners/sequential_planner.py +++ b/python/samples/concepts/planners/sequential_planner.py @@ -6,17 +6,13 @@ 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 -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() service_id = "gpt-3.5" - kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) - ) + 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. diff --git a/python/samples/concepts/plugins/azure_key_vault_settings.py b/python/samples/concepts/plugins/azure_key_vault_settings.py new file mode 100644 index 0000000000..c23135afe3 --- /dev/null +++ b/python/samples/concepts/plugins/azure_key_vault_settings.py @@ -0,0 +1,26 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings +from semantic_kernel.kernel_pydantic import HttpsUrl + + +class AzureKeyVaultSettings(BaseModelSettings): + """Azure Key Vault model settings + + Optional: + - vault_url: HttpsUrl - Azure Key Vault URL + (Env var AZURE_KEY_VAULT_VAULT_URL) + - client_id: str - Azure Key Vault client ID + (Env var AZURE_KEY_VAULT_CLIENT_ID) + - client_secret: SecretStr - Azure Key Vault client secret + (Env var AZURE_KEY_VAULT_CLIENT_SECRET) + """ + + endpoint: HttpsUrl + client_id: str + client_secret: SecretStr + + class Config(BaseModelSettings.Config): + env_prefix = "AZURE_KEY_VAULT_" diff --git a/python/samples/concepts/plugins/azure_python_code_interpreter.py b/python/samples/concepts/plugins/azure_python_code_interpreter.py index 6c773afe93..ae276297bd 100644 --- a/python/samples/concepts/plugins/azure_python_code_interpreter.py +++ b/python/samples/concepts/plugins/azure_python_code_interpreter.py @@ -13,10 +13,6 @@ from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_plugin im ) from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException from semantic_kernel.kernel import Kernel -from semantic_kernel.utils.settings import ( - azure_container_apps_settings_from_dot_env_as_dict, - azure_openai_settings_from_dot_env_as_dict, -) auth_token: AccessToken | None = None @@ -50,13 +46,11 @@ async def main(): service_id = "python-code-interpreter" chat_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) kernel.add_service(chat_service) - python_code_interpreter = SessionsPythonTool( - **azure_container_apps_settings_from_dot_env_as_dict(), auth_callback=auth_callback - ) + python_code_interpreter = SessionsPythonTool(auth_callback=auth_callback) sessions_tool = kernel.add_plugin(python_code_interpreter, "PythonCodeInterpreter") diff --git a/python/samples/concepts/plugins/google_palm_chat_with_plugin.py b/python/samples/concepts/plugins/google_palm_chat_with_plugin.py index a1c97db51b..648f384eaf 100644 --- a/python/samples/concepts/plugins/google_palm_chat_with_plugin.py +++ b/python/samples/concepts/plugins/google_palm_chat_with_plugin.py @@ -6,7 +6,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.google_palm import GooglePalmChatCompletion from semantic_kernel.contents import ChatHistory from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import google_palm_settings_from_dot_env """ System messages prime the assistant with different personalities or behaviors. @@ -30,9 +29,8 @@ Bartholomew "Blackbeard" Thorne. """ kernel = Kernel() -api_key = google_palm_settings_from_dot_env() service_id = "models/chat-bison-001" -palm_chat_completion = GooglePalmChatCompletion(service_id, api_key) +palm_chat_completion = GooglePalmChatCompletion(service_id) kernel.add_service(palm_chat_completion) req_settings = kernel.get_prompt_execution_settings_from_service_id(service_id=service_id) diff --git a/python/samples/concepts/plugins/openai_function_calling_with_custom_plugin.py b/python/samples/concepts/plugins/openai_function_calling_with_custom_plugin.py index c364e8e6bd..cef76ce689 100644 --- a/python/samples/concepts/plugins/openai_function_calling_with_custom_plugin.py +++ b/python/samples/concepts/plugins/openai_function_calling_with_custom_plugin.py @@ -3,12 +3,7 @@ from __future__ import annotations import asyncio -import sys - -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated +from typing import Annotated from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion @@ -21,7 +16,6 @@ from semantic_kernel.core_plugins.time_plugin import TimePlugin from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.functions.kernel_function_decorator import kernel_function from semantic_kernel.kernel import Kernel -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict, openai_settings_from_dot_env class WeatherPlugin: @@ -55,14 +49,12 @@ async def main(): if use_azure_openai: # Please make sure your AzureOpenAI Deployment allows for function calling ai_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) else: - api_key, _ = openai_settings_from_dot_env() ai_service = OpenAIChatCompletion( service_id=service_id, ai_model_id="gpt-3.5-turbo-1106", - api_key=api_key, ) kernel.add_service(ai_service) diff --git a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py index fe8a7f5083..877c39960a 100644 --- a/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py +++ b/python/samples/concepts/plugins/openai_plugin_azure_key_vault.py @@ -7,12 +7,12 @@ from typing import Dict, Optional import httpx from aiohttp import ClientSession +from azure_key_vault_settings import AzureKeyVaultSettings from semantic_kernel import Kernel from semantic_kernel.connectors.openai_plugin import OpenAIAuthenticationType, OpenAIFunctionExecutionParameters from semantic_kernel.functions import KernelPlugin from semantic_kernel.functions.kernel_arguments import KernelArguments -from semantic_kernel.utils.settings import azure_key_vault_settings_from_dot_env async def add_secret_to_key_vault(kernel: Kernel, plugin: KernelPlugin): @@ -125,7 +125,10 @@ async def main(): # 4. Replace your tenant ID with the "TENANT_ID" placeholder in # python/samples/kernel-syntax-examples/resources/akv-openai.json - endpoint, client_id, client_secret = azure_key_vault_settings_from_dot_env() + 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( { diff --git a/python/samples/concepts/plugins/plugins_from_dir.py b/python/samples/concepts/plugins/plugins_from_dir.py index 93fca9467f..621820709a 100644 --- a/python/samples/concepts/plugins/plugins_from_dir.py +++ b/python/samples/concepts/plugins/plugins_from_dir.py @@ -6,7 +6,6 @@ import os from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureTextCompletion, OpenAITextCompletion from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env async def main(): @@ -18,14 +17,12 @@ async def main(): # Configure AI service used by the kernel if useAzureOpenAI: - deployment_name, api_key, endpoint = azure_openai_settings_from_dot_env() kernel.add_service( - AzureTextCompletion(service_id=service_id, deployment_name=model, api_key=api_key, endpoint=endpoint), + AzureTextCompletion(service_id=service_id), ) else: - api_key, org_id = openai_settings_from_dot_env() kernel.add_service( - OpenAITextCompletion(service_id=service_id, ai_model_id=model, api_key=api_key, org_id=org_id), + OpenAITextCompletion(service_id=service_id, ai_model_id=model), ) # note: using plugins from the samples folder diff --git a/python/samples/concepts/prompt_templates/azure_chat_gpt_api_handlebars.py b/python/samples/concepts/prompt_templates/azure_chat_gpt_api_handlebars.py index 1c4e824e0e..14c7382411 100644 --- a/python/samples/concepts/prompt_templates/azure_chat_gpt_api_handlebars.py +++ b/python/samples/concepts/prompt_templates/azure_chat_gpt_api_handlebars.py @@ -7,7 +7,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict logging.basicConfig(level=logging.WARNING) @@ -24,7 +23,7 @@ kernel = Kernel() service_id = "chat-gpt" chat_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/prompt_templates/azure_chat_gpt_api_jinja2.py b/python/samples/concepts/prompt_templates/azure_chat_gpt_api_jinja2.py index 13c9f5fc79..3ad656c853 100644 --- a/python/samples/concepts/prompt_templates/azure_chat_gpt_api_jinja2.py +++ b/python/samples/concepts/prompt_templates/azure_chat_gpt_api_jinja2.py @@ -7,7 +7,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env_as_dict logging.basicConfig(level=logging.WARNING) @@ -24,7 +23,7 @@ kernel = Kernel() service_id = "chat-gpt" chat_service = AzureChatCompletion( - service_id=service_id, **azure_openai_settings_from_dot_env_as_dict(include_api_version=True) + service_id=service_id, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/prompt_templates/configuring_prompts.py b/python/samples/concepts/prompt_templates/configuring_prompts.py index 63538c7d5b..3e15101273 100644 --- a/python/samples/concepts/prompt_templates/configuring_prompts.py +++ b/python/samples/concepts/prompt_templates/configuring_prompts.py @@ -7,7 +7,6 @@ from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAICh from semantic_kernel.contents import ChatHistory from semantic_kernel.functions import KernelArguments from semantic_kernel.prompt_template import InputVariable, PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): @@ -17,18 +16,17 @@ async def main(): model = "gpt-35-turbo" if useAzureOpenAI else "gpt-3.5-turbo-1106" service_id = model - api_key, org_id = openai_settings_from_dot_env() kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id=model, api_key=api_key, org_id=org_id), + OpenAIChatCompletion(service_id=service_id, ai_model_id=model), ) template = """ Previous information from chat: {{$chat_history}} - + User: {{$request}} - Assistant: + Assistant: """ print("--- Rendered Prompt ---") diff --git a/python/samples/concepts/prompt_templates/load_yaml_prompt.py b/python/samples/concepts/prompt_templates/load_yaml_prompt.py index 2ef6432b0d..b721fbc183 100644 --- a/python/samples/concepts/prompt_templates/load_yaml_prompt.py +++ b/python/samples/concepts/prompt_templates/load_yaml_prompt.py @@ -6,19 +6,15 @@ import os from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.contents import ChatHistory -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): kernel = Kernel() - api_key, _ = openai_settings_from_dot_env() - service_id = "default" chat_service = OpenAIChatCompletion( ai_model_id="gpt-4-0613", service_id=service_id, - api_key=api_key, ) kernel.add_service(chat_service) diff --git a/python/samples/concepts/prompt_templates/template_language.py b/python/samples/concepts/prompt_templates/template_language.py index 2b3599bcaa..fb733357d5 100644 --- a/python/samples/concepts/prompt_templates/template_language.py +++ b/python/samples/concepts/prompt_templates/template_language.py @@ -6,7 +6,6 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAIChatPromptExecutionSettings from semantic_kernel.core_plugins import TimePlugin from semantic_kernel.prompt_template import KernelPromptTemplate, PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): @@ -16,9 +15,8 @@ async def main(): model = "gpt-35-turbo" if useAzureOpenAI else "gpt-3.5-turbo-1106" service_id = model - api_key, org_id = openai_settings_from_dot_env() kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id=model, api_key=api_key, org_id=org_id), + OpenAIChatCompletion(service_id=service_id, ai_model_id=model), ) kernel.add_plugin(TimePlugin(), "time") diff --git a/python/samples/concepts/rag/rag_with_text_memory_plugin.py b/python/samples/concepts/rag/rag_with_text_memory_plugin.py index e0bf67aef9..8fefc17c09 100644 --- a/python/samples/concepts/rag/rag_with_text_memory_plugin.py +++ b/python/samples/concepts/rag/rag_with_text_memory_plugin.py @@ -5,19 +5,16 @@ from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding from semantic_kernel.core_plugins import TextMemoryPlugin from semantic_kernel.memory import SemanticTextMemory, VolatileMemoryStore -from semantic_kernel.utils.settings import openai_settings_from_dot_env async def main(): kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() service_id = "default" - kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) - ) + kernel.add_service(OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo")) embedding_gen = OpenAITextEmbedding( - service_id="ada", ai_model_id="text-embedding-ada-002", api_key=api_key, org_id=org_id + service_id="ada", + ai_model_id="text-embedding-ada-002", ) kernel.add_service(embedding_gen) diff --git a/python/samples/concepts/rag/self-critique_rag.py b/python/samples/concepts/rag/self-critique_rag.py index c125e2981c..be1aec5261 100644 --- a/python/samples/concepts/rag/self-critique_rag.py +++ b/python/samples/concepts/rag/self-critique_rag.py @@ -2,11 +2,10 @@ import asyncio -from dotenv import dotenv_values - from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, AzureTextEmbedding -from semantic_kernel.connectors.memory import AzureCognitiveSearchMemoryStore +from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.contents import ChatHistory from semantic_kernel.core_plugins import TextMemoryPlugin from semantic_kernel.memory import SemanticTextMemory @@ -30,35 +29,26 @@ async def populate_memory(memory: SemanticTextMemory) -> None: async def main() -> None: kernel = Kernel() - config = dotenv_values(".env") - - AZURE_COGNITIVE_SEARCH_ENDPOINT = config["AZURE_AISEARCH_URL"] - AZURE_COGNITIVE_SEARCH_ADMIN_KEY = config["AZURE_AISEARCH_API_KEY"] - AZURE_OPENAI_API_KEY = config["AZURE_OPENAI_API_KEY"] - AZURE_OPENAI_ENDPOINT = config["AZURE_OPENAI_ENDPOINT"] + azure_ai_search_settings = AzureAISearchSettings() vector_size = 1536 # Setting up OpenAI services for text completion and text embedding kernel.add_service( AzureChatCompletion( service_id="dv", - deployment_name="gpt-35-turbo", - endpoint=AZURE_OPENAI_ENDPOINT, - api_key=AZURE_OPENAI_API_KEY, ), ) embedding_gen = AzureTextEmbedding( service_id="ada", - deployment_name="text-embedding-ada-002", - endpoint=AZURE_OPENAI_ENDPOINT, - api_key=AZURE_OPENAI_API_KEY, ) kernel.add_service( embedding_gen, ) acs_connector = AzureCognitiveSearchMemoryStore( - vector_size, AZURE_COGNITIVE_SEARCH_ENDPOINT, AZURE_COGNITIVE_SEARCH_ADMIN_KEY + vector_size=vector_size, + search_endpoint=azure_ai_search_settings.endpoint, + admin_key=azure_ai_search_settings.api_key, ) memory = SemanticTextMemory(storage=acs_connector, embeddings_generator=embedding_gen) diff --git a/python/samples/concepts/search/bing_plugin_examples.py b/python/samples/concepts/search/bing_plugin_examples.py index 7443df6244..6482a3a6d7 100644 --- a/python/samples/concepts/search/bing_plugin_examples.py +++ b/python/samples/concepts/search/bing_plugin_examples.py @@ -8,7 +8,6 @@ from semantic_kernel.connectors.search_engine import BingConnector from semantic_kernel.core_plugins import WebSearchEnginePlugin from semantic_kernel.functions import KernelArguments from semantic_kernel.prompt_template import KernelPromptTemplate, PromptTemplateConfig -from semantic_kernel.utils.settings import bing_search_settings_from_dot_env, openai_settings_from_dot_env async def example1(kernel: Kernel, search_plugin_name: str): @@ -101,15 +100,11 @@ async def main(): model = "gpt-3.5-turbo-1106" service_id = model - api_key, org_id = openai_settings_from_dot_env() kernel.add_service( - OpenAIChatCompletion(service_id=service_id, ai_model_id=model, api_key=api_key, org_id=org_id), + OpenAIChatCompletion(service_id=service_id, ai_model_id=model), ) - bing_api_key = bing_search_settings_from_dot_env() - assert bing_api_key is not None - - bing_connector = BingConnector(api_key=bing_api_key) + bing_connector = BingConnector() bing = WebSearchEnginePlugin(bing_connector) kernel.add_plugin(bing, "bing") diff --git a/python/samples/concepts/search/bing_search_plugin.py b/python/samples/concepts/search/bing_search_plugin.py index 3f2a185f4a..f93b181c02 100644 --- a/python/samples/concepts/search/bing_search_plugin.py +++ b/python/samples/concepts/search/bing_search_plugin.py @@ -1,33 +1,22 @@ # Copyright (c) Microsoft. All rights reserved. -import os - -from dotenv import load_dotenv from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion from semantic_kernel.connectors.search_engine import BingConnector from semantic_kernel.core_plugins import WebSearchEnginePlugin from semantic_kernel.prompt_template import PromptTemplateConfig -from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env - -load_dotenv() async def main(): kernel = Kernel() - deployment, key, endpoint, api_version = azure_openai_settings_from_dot_env(include_api_version=True) service_id = "chat-gpt" kernel.add_service( AzureChatCompletion( service_id=service_id, - deployment_name=deployment, - api_key=key, - endpoint=endpoint, - api_version=api_version, ), ) - connector = BingConnector(api_key=os.getenv("BING_API_KEY")) + connector = BingConnector() web_plugin = kernel.add_plugin(WebSearchEnginePlugin(connector), "WebSearch") print("---------------- Question 1 -----------------\n") diff --git a/python/samples/concepts/search/google_search_plugin.py b/python/samples/concepts/search/google_search_plugin.py index b77227d9e8..0c24f34238 100644 --- a/python/samples/concepts/search/google_search_plugin.py +++ b/python/samples/concepts/search/google_search_plugin.py @@ -10,17 +10,13 @@ from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion from semantic_kernel.connectors.search_engine import GoogleConnector from semantic_kernel.core_plugins import WebSearchEnginePlugin from semantic_kernel.functions import KernelArguments -from semantic_kernel.utils.settings import openai_settings_from_dot_env load_dotenv() async def main(): kernel = Kernel() - api_key, org_id = openai_settings_from_dot_env() - kernel.add_service( - OpenAIChatCompletion(service_id="chat-gpt", ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id) - ) + kernel.add_service(OpenAIChatCompletion(service_id="chat-gpt", ai_model_id="gpt-3.5-turbo")) """ Instantiate a Google Connector diff --git a/python/samples/concepts/text_generation/google_palm_text_completion.py b/python/samples/concepts/text_generation/google_palm_text_completion.py index 282b1cad3c..0c14c32a7d 100644 --- a/python/samples/concepts/text_generation/google_palm_text_completion.py +++ b/python/samples/concepts/text_generation/google_palm_text_completion.py @@ -4,14 +4,13 @@ import asyncio from semantic_kernel.connectors.ai.google_palm import GooglePalmTextCompletion, GooglePalmTextPromptExecutionSettings from semantic_kernel.kernel import Kernel -from semantic_kernel.utils.settings import google_palm_settings_from_dot_env -async def text_completion_example_complete(kernel, api_key, user_mssg, settings): +async def text_completion_example_complete(kernel, user_mssg, settings): """ Complete a text prompt using the Google PaLM model and print the results. """ - palm_text_completion = GooglePalmTextCompletion("models/text-bison-001", api_key) + palm_text_completion = GooglePalmTextCompletion("models/text-bison-001") kernel.add_service(palm_text_completion) answer = await palm_text_completion.complete(user_mssg, settings) return answer @@ -19,7 +18,6 @@ async def text_completion_example_complete(kernel, api_key, user_mssg, settings) async def main() -> None: kernel = Kernel() - apikey = google_palm_settings_from_dot_env() settings = GooglePalmTextPromptExecutionSettings() user_mssg1 = ( @@ -29,13 +27,13 @@ async def main() -> None: "boxes have 98 coins in total. How many coins are there in each box? " "Think about it step by step, and show your work." ) - response = await text_completion_example_complete(kernel, apikey, user_mssg1, settings) + response = await text_completion_example_complete(kernel, user_mssg1, settings) print(f"User:> {user_mssg1}\n\nChatBot:> {response}\n") # Use temperature to influence the variance of the responses settings.number_of_responses = 3 settings.temperature = 1 user_mssg2 = "I need a concise answer. A common method for traversing a binary tree is" - response = await text_completion_example_complete(kernel, apikey, user_mssg2, settings) + response = await text_completion_example_complete(kernel, user_mssg2, settings) print(f"User:> {user_mssg2}\n\nChatBot:> {response}") return diff --git a/python/samples/demos/booking_restaurant/README.md b/python/samples/demos/booking_restaurant/README.md index 88e31608df..37dd9ca2e2 100644 --- a/python/samples/demos/booking_restaurant/README.md +++ b/python/samples/demos/booking_restaurant/README.md @@ -35,11 +35,21 @@ This sample uses function calling capable models and has been tested with the fo ## Configuring the sample -Please make sure your .env file contains the following: +Please make sure either your environment variables or your .env file contains the following: - "BOOKING_SAMPLE_CLIENT_ID" - "BOOKING_SAMPLE_TENANT_ID" - "BOOKING_SAMPLE_CLIENT_SECRET" +- "BOOKING_SAMPLE_BUSINESS_ID" +- "BOOKING_SAMPLE_SERVICE_ID" + +If wanting to use the `.env` file, you must pass the `env_file_path` parameter with a valid path: + +```python +booking_sample_settings = BookingSampleSettings(env_file_path=env_file_path) +``` + +This will tell Pydantic settings to also load the `.env` file instead of just trying to load environment variables. ### Create an App Registration in Azure Active Directory diff --git a/python/samples/demos/booking_restaurant/booking_sample_settings.py b/python/samples/demos/booking_restaurant/booking_sample_settings.py new file mode 100644 index 0000000000..04f9795411 --- /dev/null +++ b/python/samples/demos/booking_restaurant/booking_sample_settings.py @@ -0,0 +1,45 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class BookingSampleSettings(BaseSettings): + """Restaurant Booking Sample settings + + The settings are first loaded from environment variables with the prefix 'BOOKING_'. If the + environment variables are not found, the settings can be loaded from a .env file with the + encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored; + however, validation will fail alerting that the settings are missing. + + Required settings for prefix 'BOOKING_' are: + - client_id = The App Registration Client ID (Env var BOOKING_CLIENT_ID) + - tenant_id = The App Registration Tenant ID (Env var BOOKING_TENANT_ID) + - client_secret = The App Registration Client Secret (Env var BOOKING_CLIENT_SECRET) + - business_id = The sample booking service ID (Env var BOOKING_BUSINESS_ID) + - service_id = The sample booking service ID (Env var BOOKING_SERVICE_ID) + + For more information on these required settings, please see the sample's README.md file. + """ + + env_file_path: str | None = None + client_id: str + tenant_id: str + client_secret: SecretStr + business_id: str + service_id: str + + class Config: + env_prefix = "BOOKING_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/samples/demos/booking_restaurant/restaurant_booking.py b/python/samples/demos/booking_restaurant/restaurant_booking.py index 684907166e..153b9ddab7 100644 --- a/python/samples/demos/booking_restaurant/restaurant_booking.py +++ b/python/samples/demos/booking_restaurant/restaurant_booking.py @@ -3,9 +3,10 @@ import asyncio from azure.identity import ClientSecretCredential +from booking_sample_settings import BookingSampleSettings from bookings_plugin.bookings_plugin import BookingsPlugin -from dotenv import dotenv_values from msgraph import GraphServiceClient +from pydantic import ValidationError from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import ( @@ -13,26 +14,30 @@ from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_pro ) from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion from semantic_kernel.contents.chat_history import ChatHistory +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.kernel import Kernel -from semantic_kernel.utils.settings import booking_sample_settings_from_dot_env_as_dict, openai_settings_from_dot_env kernel = Kernel() service_id = "open_ai" -api_key, _ = openai_settings_from_dot_env() -ai_service = OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo-1106", api_key=api_key) +ai_service = OpenAIChatCompletion(service_id=service_id, ai_model_id="gpt-3.5-turbo-1106") kernel.add_service(ai_service) -client_secret_credential = ClientSecretCredential(**booking_sample_settings_from_dot_env_as_dict()) +try: + booking_sample_settings = BookingSampleSettings.create() +except ValidationError as e: + raise ServiceInitializationError("Failed to initialize the booking sample settings.") from e + +tenant_id = booking_sample_settings.tenant_id +client_id = booking_sample_settings.client_id +client_secret = booking_sample_settings.client_secret +client_secret_credential = ClientSecretCredential(tenant_id=tenant_id, client_id=client_id, client_secret=client_secret) graph_client = GraphServiceClient(credentials=client_secret_credential, scopes=["https://graph.microsoft.com/.default"]) -config = dotenv_values(".env") -booking_business_id = config.get("BOOKING_SAMPLE_BUSINESS_ID") -assert booking_business_id, "BOOKING_SAMPLE_BUSINESS_ID is not set in .env file" -booking_service_id = config.get("BOOKING_SAMPLE_SERVICE_ID") -assert booking_service_id, "BOOKING_SAMPLE_SERVICE_ID is not set in .env file" +booking_business_id = booking_sample_settings.business_id +booking_service_id = booking_sample_settings.service_id bookings_plugin = BookingsPlugin( graph_client=graph_client, diff --git a/python/samples/getting_started/00-getting-started.ipynb b/python/samples/getting_started/00-getting-started.ipynb index e0b19a8c47..34839d98c7 100644 --- a/python/samples/getting_started/00-getting-started.ipynb +++ b/python/samples/getting_started/00-getting-started.ipynb @@ -46,7 +46,7 @@ "from services import Service\n", "\n", "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n", - "selectedService = Service.OpenAI" + "selectedService = Service.AzureOpenAI" ] }, { @@ -56,23 +56,39 @@ "source": [ "## Option 1: using OpenAI\n", "\n", - "**Step 2**: Add your [OpenAI Key](https://openai.com/product/) key to a `.env` file in the same folder (org Id only if you have multiple orgs):\n", + "**Step 2**: Add your [OpenAI Key](https://openai.com/product/) key to either your environment variables or to the `.env` file in the same folder (org Id only if you have multiple orgs):\n", "\n", "```\n", "OPENAI_API_KEY=\"sk-...\"\n", "OPENAI_ORG_ID=\"\"\n", "```\n", + "The environment variables names should match the names used in the `.env` file, as shown above.\n", + "\n", + "If using the `.env` file, please configure the `env_file_path` parameter with a valid path when creating the ChatCompletion class:\n", + "\n", + "```\n", + "chat_completion = OpenAIChatCompletion(service_id=\"test\", env_file_path=)\n", + "```\n", "\n", "Use \"keyword arguments\" to instantiate an OpenAI Chat Completion service and add it to the kernel:\n", "\n", "## Option 2: using Azure OpenAI\n", "\n", - "**Step 2**: Add your [Azure Open AI Service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=programming-language-studio) settings to a `.env` file in the same folder:\n", + "**Step 2**: Add your [Azure Open AI Service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=programming-language-studio) settings to either your system's environment variables or to the `.env` file in the same folder:\n", "\n", "```\n", "AZURE_OPENAI_API_KEY=\"...\"\n", "AZURE_OPENAI_ENDPOINT=\"https://...\"\n", - "AZURE_OPENAI_DEPLOYMENT_NAME=\"...\"\n", + "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=\"...\"\n", + "AZURE_OPENAI_TEXT_DEPLOYMENT_NAME=\"...\"\n", + "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=\"...\"\n", + "```\n", + "The environment variables names should match the names used in the `.env` file, as shown above.\n", + "\n", + "If using the `.env` file, please configure the `env_file_path` parameter with a valid path when creating the ChatCompletion class:\n", + "\n", + "```\n", + "chat_completion = AzureChatCompletion(service_id=\"test\", env_file_path=)\n", "```\n", "\n", "Use \"keyword arguments\" to instantiate an Azure OpenAI Chat Completion service and add it to the kernel:\n" @@ -84,24 +100,20 @@ "metadata": {}, "outputs": [], "source": [ - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", - "\n", "service_id = None\n", "if selectedService == Service.OpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"default\"\n", " kernel.add_service(\n", - " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"default\"\n", " kernel.add_service(\n", - " AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, @@ -155,7 +167,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/python/samples/getting_started/01-basic-loading-the-kernel.ipynb b/python/samples/getting_started/01-basic-loading-the-kernel.ipynb index 2f59281479..644822fa8c 100644 --- a/python/samples/getting_started/01-basic-loading-the-kernel.ipynb +++ b/python/samples/getting_started/01-basic-loading-the-kernel.ipynb @@ -39,6 +39,50 @@ "kernel = Kernel()" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configuring API Keys and Endpoints\n", + "\n", + "#### Option 1: using OpenAI\n", + "\n", + "Add your [OpenAI Key](https://openai.com/product/) key to either your environment variables or to the `.env` file in the same folder (org Id only if you have multiple orgs):\n", + "\n", + "```\n", + "OPENAI_API_KEY=\"sk-...\"\n", + "OPENAI_ORG_ID=\"\"\n", + "```\n", + "The environment variables names should match the names used in the `.env` file, as shown above.\n", + "\n", + "If using the `.env` file, please configure the `env_file_path` parameter with a valid path when creating the ChatCompletion class:\n", + "\n", + "```\n", + "chat_completion = OpenAIChatCompletion(service_id=\"test\", env_file_path=)\n", + "```\n", + "\n", + "Use \"keyword arguments\" to instantiate an OpenAI Chat Completion service and add it to the kernel:\n", + "\n", + "#### Option 2: using Azure OpenAI\n", + "\n", + "Add your [Azure Open AI Service key](https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?pivots=programming-language-studio) settings to either your system's environment variables or to the `.env` file in the same folder:\n", + "\n", + "```\n", + "AZURE_OPENAI_API_KEY=\"...\"\n", + "AZURE_OPENAI_ENDPOINT=\"https://...\"\n", + "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME=\"...\"\n", + "AZURE_OPENAI_TEXT_DEPLOYMENT_NAME=\"...\"\n", + "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=\"...\"\n", + "```\n", + "The environment variables names should match the names used in the `.env` file, as shown above.\n", + "\n", + "If using the `.env` file, please configure the `env_file_path` parameter with a valid path when creating the ChatCompletion class:\n", + "\n", + "```\n", + "chat_completion = AzureChatCompletion(service_id=\"test\", env_file_path=)\n", + "```\n" + ] + }, { "attachments": {}, "cell_type": "markdown", @@ -72,21 +116,17 @@ "service_id = None\n", "if selectedService == Service.OpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", - " from semantic_kernel.utils.settings import openai_settings_from_dot_env\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"oai_chat_gpt\"\n", " kernel.add_service(\n", - " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", - " from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"aoai_chat_completion\"\n", " kernel.add_service(\n", - " AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, @@ -115,7 +155,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.9" }, "polyglot_notebook": { "kernelInfo": { diff --git a/python/samples/getting_started/02-running-prompts-from-file.ipynb b/python/samples/getting_started/02-running-prompts-from-file.ipynb index 769648e74d..abce1d3a83 100644 --- a/python/samples/getting_started/02-running-prompts-from-file.ipynb +++ b/python/samples/getting_started/02-running-prompts-from-file.ipynb @@ -135,21 +135,17 @@ "service_id = None\n", "if selectedService == Service.OpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", - " from semantic_kernel.utils.settings import openai_settings_from_dot_env\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"default\"\n", " kernel.add_service(\n", - " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", - " from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"default\"\n", " kernel.add_service(\n", - " AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, diff --git a/python/samples/getting_started/03-prompt-function-inline.ipynb b/python/samples/getting_started/03-prompt-function-inline.ipynb index b13ecc1fbd..7b42a121d2 100644 --- a/python/samples/getting_started/03-prompt-function-inline.ipynb +++ b/python/samples/getting_started/03-prompt-function-inline.ipynb @@ -77,24 +77,18 @@ "\n", "service_id = None\n", "if selectedService == Service.OpenAI:\n", - " from semantic_kernel.connectors.ai.open_ai import OpenAITextCompletion\n", - " from semantic_kernel.utils.settings import openai_settings_from_dot_env\n", + " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", - " service_id = \"oai_text_completion\"\n", + " service_id = \"oai_chat_completion\"\n", " kernel.add_service(\n", - " OpenAITextCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id\n", - " ),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-instruct\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", - " from semantic_kernel.connectors.ai.open_ai import AzureTextCompletion\n", - " from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env\n", + " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", - " service_id = \"aoai_text_completion\"\n", + " service_id = \"aoai_chat_completion\"\n", " kernel.add_service(\n", - " AzureTextCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, @@ -116,7 +110,7 @@ "metadata": {}, "outputs": [], "source": [ - "from semantic_kernel.connectors.ai.open_ai import OpenAITextPromptExecutionSettings\n", + "from semantic_kernel.connectors.ai.open_ai import OpenAIChatPromptExecutionSettings\n", "from semantic_kernel.prompt_template import PromptTemplateConfig, InputVariable\n", "\n", "\n", @@ -125,16 +119,16 @@ "\"\"\"\n", "\n", "if selectedService == Service.OpenAI:\n", - " execution_settings = OpenAITextPromptExecutionSettings(\n", + " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=\"gpt-3.5-turbo-instruct\",\n", + " ai_model_id=\"gpt-3.5-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", - " execution_settings = OpenAITextPromptExecutionSettings(\n", + " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\"\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", @@ -248,18 +242,16 @@ "if selectedService == Service.OpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"oai_chat_gpt\"\n", " kernel.add_service(\n", - " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"aoai_chat_completion\"\n", " kernel.add_service(\n", - " AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, @@ -301,7 +293,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", @@ -344,7 +336,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.3" + "version": "3.11.9" } }, "nbformat": 4, diff --git a/python/samples/getting_started/04-kernel-arguments-chat.ipynb b/python/samples/getting_started/04-kernel-arguments-chat.ipynb index 0c0a86f814..07d7f19829 100644 --- a/python/samples/getting_started/04-kernel-arguments-chat.ipynb +++ b/python/samples/getting_started/04-kernel-arguments-chat.ipynb @@ -56,21 +56,17 @@ "service_id = None\n", "if selectedService == Service.OpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion\n", - " from semantic_kernel.utils.settings import openai_settings_from_dot_env\n", "\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"oai_chat_gpt\"\n", " kernel.add_service(\n", - " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id),\n", + " OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", " from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion\n", - " from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env\n", "\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"aoai_chat_completion\"\n", " kernel.add_service(\n", - " AzureChatCompletion(service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key),\n", + " AzureChatCompletion(service_id=service_id),\n", " )" ] }, @@ -131,7 +127,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", diff --git a/python/samples/getting_started/05-using-the-planner.ipynb b/python/samples/getting_started/05-using-the-planner.ipynb index 826be2db72..e451b9611c 100644 --- a/python/samples/getting_started/05-using-the-planner.ipynb +++ b/python/samples/getting_started/05-using-the-planner.ipynb @@ -95,26 +95,17 @@ "import semantic_kernel.connectors.ai.open_ai as sk_oai # noqa: F401\n", "from semantic_kernel.functions.kernel_function_from_prompt import KernelFunctionFromPrompt\n", "from semantic_kernel.core_plugins.text_plugin import TextPlugin\n", - "from semantic_kernel.utils.settings import openai_settings_from_dot_env, azure_openai_settings_from_dot_env\n", "\n", "kernel = sk.Kernel()\n", "service_id = \"default\"\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " kernel.add_service(\n", - " sk_oai.OpenAIChatCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id\n", - " ),\n", + " sk_oai.OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint, api_version = azure_openai_settings_from_dot_env(include_api_version=True)\n", " kernel.add_service(\n", " sk_oai.AzureChatCompletion(\n", " service_id=service_id,\n", - " deployment_name=deployment,\n", - " endpoint=endpoint,\n", - " api_key=api_key,\n", - " api_version=api_version,\n", " ),\n", " )\n", "\n", @@ -281,26 +272,17 @@ "source": [ "import semantic_kernel as sk\n", "import semantic_kernel.connectors.ai.open_ai as sk_oai # noqa: F401\n", - "from semantic_kernel.utils.settings import openai_settings_from_dot_env, azure_openai_settings_from_dot_env\n", "\n", "kernel = sk.Kernel()\n", "service_id = \"default\"\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " kernel.add_service(\n", - " sk_oai.OpenAIChatCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\", api_key=api_key, org_id=org_id\n", - " ),\n", + " sk_oai.OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo-1106\"),\n", " )\n", "elif selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint, api_version = azure_openai_settings_from_dot_env(include_api_version=True)\n", " kernel.add_service(\n", " sk_oai.AzureChatCompletion(\n", " service_id=service_id,\n", - " deployment_name=deployment,\n", - " endpoint=endpoint,\n", - " api_key=api_key,\n", - " api_version=api_version,\n", " ),\n", " )" ] diff --git a/python/samples/getting_started/06-memory-and-embeddings.ipynb b/python/samples/getting_started/06-memory-and-embeddings.ipynb index bfd29fd512..38890ce487 100644 --- a/python/samples/getting_started/06-memory-and-embeddings.ipynb +++ b/python/samples/getting_started/06-memory-and-embeddings.ipynb @@ -73,7 +73,6 @@ "from semantic_kernel.kernel import Kernel\n", "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", "from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore\n", - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", "\n", "kernel = Kernel()\n", "\n", @@ -81,20 +80,14 @@ "\n", "# Configure AI service used by the kernel\n", "if selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", - " azure_chat_service = AzureChatCompletion(\n", - " service_id=chat_service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key\n", - " )\n", + " azure_chat_service = AzureChatCompletion(service_id=chat_service_id)\n", " # next line assumes embeddings deployment name is \"text-embedding\", adjust the deployment name to the value of your chat model if needed\n", - " embedding_gen = AzureTextEmbedding(deployment_name=\"text-embedding\", endpoint=endpoint, api_key=api_key)\n", + " embedding_gen = AzureTextEmbedding(deployment_name=\"text-embedding\")\n", " kernel.add_service(azure_chat_service)\n", " kernel.add_service(embedding_gen)\n", "elif selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=chat_service_id, ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id\n", - " )\n", - " embedding_gen = OpenAITextEmbedding(ai_model_id=\"text-embedding-ada-002\", api_key=api_key, org_id=org_id)\n", + " oai_chat_service = OpenAIChatCompletion(service_id=chat_service_id, ai_model_id=\"gpt-3.5-turbo\")\n", + " embedding_gen = OpenAITextEmbedding(ai_model_id=\"text-embedding-ada-002\")\n", " kernel.add_service(oai_chat_service)\n", " kernel.add_service(embedding_gen)\n", "\n", @@ -218,6 +211,7 @@ "from semantic_kernel.functions import KernelFunction\n", "from semantic_kernel.prompt_template import PromptTemplateConfig\n", "\n", + "\n", "async def setup_chat_with_memory(\n", " kernel: Kernel,\n", " service_id: str,\n", @@ -431,9 +425,6 @@ "outputs": [], "source": [ "from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognitiveSearchMemoryStore\n", - "from semantic_kernel.utils.settings import azure_aisearch_settings_from_dot_env\n", - "\n", - "azure_ai_search_api_key, azure_ai_search_url = azure_aisearch_settings_from_dot_env()\n", "\n", "acs_memory_store = AzureCognitiveSearchMemoryStore(\n", " vector_size=1536,\n", diff --git a/python/samples/getting_started/08-native-function-inline.ipynb b/python/samples/getting_started/08-native-function-inline.ipynb index 7855ba627f..e48f003c6d 100644 --- a/python/samples/getting_started/08-native-function-inline.ipynb +++ b/python/samples/getting_started/08-native-function-inline.ipynb @@ -71,25 +71,20 @@ "source": [ "from semantic_kernel import Kernel\n", "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n", - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", "\n", "kernel = Kernel()\n", "\n", "if selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"aoai_chat\" # used later in the notebook\n", " azure_chat_service = AzureChatCompletion(\n", - " service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key\n", + " service_id=service_id\n", " ) # set the deployment name to the value of your chat model\n", " kernel.add_service(azure_chat_service)\n", "\n", "# Configure OpenAI service\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"oai_chat\" # used later in the notebook\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-4-turbo-1106\", api_key=api_key, org_id=org_id\n", - " )\n", + " oai_chat_service = OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-4-turbo-1106\")\n", " kernel.add_service(oai_chat_service)" ] }, @@ -187,7 +182,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", @@ -281,20 +276,16 @@ "kernel = Kernel()\n", "\n", "if selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"aoai_chat\" # used later in the notebook\n", " azure_chat_service = AzureChatCompletion(\n", - " service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key\n", + " service_id=service_id\n", " ) # set the deployment name to the value of your chat model\n", " kernel.add_service(azure_chat_service)\n", "\n", "# Configure OpenAI service\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"oai_chat\" # used later in the notebook\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-4-turbo-1106\", api_key=api_key, org_id=org_id\n", - " )\n", + " oai_chat_service = OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-4-turbo-1106\")\n", " kernel.add_service(oai_chat_service)" ] }, @@ -402,7 +393,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", @@ -574,7 +565,7 @@ "elif selectedService == Service.AzureOpenAI:\n", " execution_settings = OpenAIChatPromptExecutionSettings(\n", " service_id=service_id,\n", - " ai_model_id=deployment,\n", + " ai_model_id=\"gpt-35-turbo\",\n", " max_tokens=2000,\n", " temperature=0.7,\n", " )\n", diff --git a/python/samples/getting_started/09-groundedness-checking.ipynb b/python/samples/getting_started/09-groundedness-checking.ipynb index 91269b140a..20bb6c4591 100644 --- a/python/samples/getting_started/09-groundedness-checking.ipynb +++ b/python/samples/getting_started/09-groundedness-checking.ipynb @@ -94,7 +94,6 @@ "source": [ "from semantic_kernel import Kernel\n", "from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion, OpenAIChatCompletion\n", - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", "\n", "kernel = Kernel()\n", "\n", @@ -102,18 +101,14 @@ "\n", "# Configure AI service used by the kernel\n", "if useAzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " service_id = \"default\"\n", " azure_chat_service = AzureChatCompletion(\n", - " service_id=service_id, deployment_name=deployment, endpoint=endpoint, api_key=api_key\n", + " service_id=service_id\n", " ) # set the deployment name to the value of your chat model\n", " kernel.add_service(azure_chat_service)\n", "else:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " service_id = \"default\"\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=service_id, ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id\n", - " )\n", + " oai_chat_service = OpenAIChatCompletion(service_id=service_id, ai_model_id=\"gpt-3.5-turbo\")\n", " kernel.add_service(oai_chat_service)" ] }, diff --git a/python/samples/getting_started/10-multiple-results-per-prompt.ipynb b/python/samples/getting_started/10-multiple-results-per-prompt.ipynb index f942d60571..80d89cc596 100644 --- a/python/samples/getting_started/10-multiple-results-per-prompt.ipynb +++ b/python/samples/getting_started/10-multiple-results-per-prompt.ipynb @@ -81,29 +81,22 @@ "outputs": [], "source": [ "from semantic_kernel import Kernel\n", - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", "\n", "kernel = Kernel()\n", "\n", "# Configure Azure LLM service\n", "if selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " azure_text_service = AzureTextCompletion(\n", - " service_id=\"aoai_text\", deployment_name=\"gpt-35-turbo-instruct\", endpoint=endpoint, api_key=api_key\n", + " service_id=\"aoai_text\"\n", " ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct)\n", " azure_chat_service = AzureChatCompletion(\n", - " service_id=\"aoai_chat\", deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n", + " service_id=\"aoai_chat\"\n", " ) # set the deployment name to the value of your chat model\n", "\n", "# Configure OpenAI service\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", - " oai_text_service = OpenAITextCompletion(\n", - " service_id=\"oai_text\", ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id\n", - " )\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=\"oai_chat\", ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id\n", - " )\n", + " oai_text_service = OpenAITextCompletion(service_id=\"oai_text\", ai_model_id=\"gpt-3.5-turbo-instruct\")\n", + " oai_chat_service = OpenAIChatCompletion(service_id=\"oai_chat\", ai_model_id=\"gpt-3.5-turbo\")\n", "\n", "# Configure Hugging Face service\n", "if selectedService == Service.HuggingFace:\n", @@ -183,7 +176,7 @@ "source": [ "if selectedService == Service.AzureOpenAI:\n", " prompt = \"provide me a list of possible meanings for the acronym 'ORLD'\"\n", - " \n", + "\n", " results = await azure_text_service.complete(prompt=prompt, settings=oai_text_prompt_execution_settings)\n", " i = 1\n", " for result in results:\n", @@ -226,7 +219,7 @@ "source": [ "if selectedService == Service.HuggingFace:\n", " prompt = \"The purpose of a rubber duck is\"\n", - " \n", + "\n", " results = await hf_text_service.complete(prompt=prompt, prompt_execution_settings=hf_prompt_execution_settings)\n", " print(\"\".join(results))" ] diff --git a/python/samples/getting_started/11-streaming-completions.ipynb b/python/samples/getting_started/11-streaming-completions.ipynb index 2855af3440..c74018b2f3 100644 --- a/python/samples/getting_started/11-streaming-completions.ipynb +++ b/python/samples/getting_started/11-streaming-completions.ipynb @@ -77,28 +77,27 @@ "outputs": [], "source": [ "from semantic_kernel import Kernel\n", - "from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env, openai_settings_from_dot_env\n", "\n", "kernel = Kernel()\n", "\n", "# Configure Azure LLM service\n", "if selectedService == Service.AzureOpenAI:\n", - " deployment, api_key, endpoint = azure_openai_settings_from_dot_env()\n", " azure_text_service = AzureTextCompletion(\n", - " service_id=\"aoai_text\", deployment_name=\"gpt-35-turbo-instruct\", endpoint=endpoint, api_key=api_key\n", - " ) # set the deployment name to the value of your text model (e.g. gpt-35-turbo-instruct)\n", + " service_id=\"aoai_text\",\n", + " ) # set the environment variable AZURE_OPENAI_TEXT_DEPLOYMENT_NAME to the value of your text model (e.g. gpt-35-turbo-instruct)\n", " azure_chat_service = AzureChatCompletion(\n", - " service_id=\"aoai_chat\", deployment_name=\"gpt-35-turbo\", endpoint=endpoint, api_key=api_key\n", - " ) # set the deployment name to the value of your chat model\n", + " service_id=\"aoai_chat\",\n", + " ) # set the environment variable AZURE_OPENAI_CHAT_DEPLOYMENT_NAME to the value of your chat model\n", "\n", "# Configure OpenAI service\n", "if selectedService == Service.OpenAI:\n", - " api_key, org_id = openai_settings_from_dot_env()\n", " oai_text_service = OpenAITextCompletion(\n", - " service_id=\"oai_text\", ai_model_id=\"gpt-3.5-turbo-instruct\", api_key=api_key, org_id=org_id\n", + " service_id=\"oai_text\",\n", + " ai_model_id=\"gpt-3.5-turbo-instruct\",\n", " )\n", " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=\"oai_chat\", ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id\n", + " service_id=\"oai_chat\",\n", + " ai_model_id=\"gpt-3.5-turbo\",\n", " )\n", "\n", "# Configure Hugging Face service\n", diff --git a/python/samples/getting_started/third_party/weaviate-persistent-memory.ipynb b/python/samples/getting_started/third_party/weaviate-persistent-memory.ipynb index 6d2326aba7..0640236e0d 100644 --- a/python/samples/getting_started/third_party/weaviate-persistent-memory.ipynb +++ b/python/samples/getting_started/third_party/weaviate-persistent-memory.ipynb @@ -1,508 +1,504 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Introduction\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "This notebook shows how to replace the `VolatileMemoryStore` memory storage used in a [previous notebook](./06-memory-and-embeddings.ipynb) with a `WeaviateMemoryStore`.\n", - "\n", - "`WeaviateMemoryStore` is an example of a persistent (i.e. long-term) memory store backed by the Weaviate vector database.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# About Weaviate\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "[Weaviate](https://weaviate.io/) is an open-source vector database designed to scale seamlessly into billions of data objects. This implementation supports hybrid search out-of-the-box (meaning it will perform better for keyword searches).\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "You can run Weaviate in 5 ways:\n", - "\n", - "- **SaaS** – with [Weaviate Cloud Services (WCS)](https://weaviate.io/pricing).\n", - "\n", - " WCS is a fully managed service that takes care of hosting, scaling, and updating your Weaviate instance. You can try it out for free with a sandbox that lasts for 14 days.\n", - "\n", - " To set up a SaaS Weaviate instance with WCS:\n", - "\n", - " 1. Navigate to [Weaviate Cloud Console](https://console.weaviate.cloud/).\n", - " 2. Register or sign in to your WCS account.\n", - " 3. Create a new cluster with the following settings:\n", - " - `Subscription Tier` – Free sandbox for a free trial, or contact [hello@weaviate.io](mailto:hello@weaviate.io) for other options.\n", - " - `Cluster name` – a unique name for your cluster. The name will become part of the URL used to access this instance.\n", - " - `Enable Authentication?` – Enabled by default. This will generate a static API key that you can use to authenticate.\n", - " 4. Wait for a few minutes until your cluster is ready. You will see a green tick ✔️ when it's done. Copy your cluster URL.\n", - "\n", - "- **Hybrid SaaS**\n", - "\n", - " > If you need to keep your data on-premise for security or compliance reasons, Weaviate also offers a Hybrid SaaS option: Weaviate runs within your cloud instances, but the cluster is managed remotely by Weaviate. This gives you the benefits of a managed service without sending data to an external party.\n", - "\n", - " The Weaviate Hybrid SaaS is a custom solution. If you are interested in this option, please reach out to [hello@weaviate.io](mailto:hello@weaviate.io).\n", - "\n", - "- **Self-hosted** – with a Docker container\n", - "\n", - " To set up a Weaviate instance with Docker:\n", - "\n", - " 1. [Install Docker](https://docs.docker.com/engine/install/) on your local machine if it is not already installed.\n", - " 2. [Install the Docker Compose Plugin](https://docs.docker.com/compose/install/)\n", - " 3. Download a `docker-compose.yml` file with this `curl` command:\n", - "\n", - " ```\n", - " curl -o docker-compose.yml \"https://configuration.weaviate.io/v2/docker-compose/docker-compose.yml?modules=standalone&runtime=docker-compose&weaviate_version=v1.19.6\"\n", - " ```\n", - "\n", - " Alternatively, you can use Weaviate's docker compose [configuration tool](https://weaviate.io/developers/weaviate/installation/docker-compose) to generate your own `docker-compose.yml` file.\n", - "\n", - " 4. Run `docker compose up -d` to spin up a Weaviate instance.\n", - "\n", - " > To shut it down, run `docker compose down`.\n", - "\n", - "- **Self-hosted** – with a Kubernetes cluster\n", - "\n", - " To configure a self-hosted instance with Kubernetes, follow Weaviate's [documentation](https://weaviate.io/developers/weaviate/installation/kubernetes).|\n", - "\n", - "- **Embedded** - start a weaviate instance right from your application code using the client library\n", - "\n", - " This code snippet shows how to instantiate an embedded weaviate instance and upload a document:\n", - "\n", - " ```python\n", - " import weaviate\n", - " from weaviate.embedded import EmbeddedOptions\n", - "\n", - " client = weaviate.Client(\n", - " embedded_options=EmbeddedOptions()\n", - " )\n", - "\n", - " data_obj = {\n", - " \"name\": \"Chardonnay\",\n", - " \"description\": \"Goes with fish\"\n", - " }\n", - "\n", - " client.data_object.create(data_obj, \"Wine\")\n", - " ```\n", - "\n", - " Refer to the [documentation](https://weaviate.io/developers/weaviate/installation/embedded) for more details about this deployment method.\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Setup\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!pip install semantic-kernel==0.9.8b1\n", - "!pip install weaviate-client\n", - "!pip install python-dotenv" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## OS-specific notes:\n", - "\n", - "- if you run into SSL errors when connecting to OpenAI on macOS, see this issue for a [potential solution](https://github.com/microsoft/semantic-kernel/issues/627#issuecomment-1580912248)\n", - "- on Windows, you may need to run Docker Desktop as administrator\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "First, we instantiate the Weaviate memory store. Uncomment ONE of the options below, depending on how you want to use Weaviate:\n", - "\n", - "- from a Docker instance\n", - "- from WCS\n", - "- directly from the client (embedded Weaviate), which works on Linux only at the moment\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from dotenv import load_dotenv\n", - "\n", - "from semantic_kernel.connectors.memory.weaviate import weaviate_memory_store\n", - "\n", - "load_dotenv(override=True)\n", - "\n", - "# Using Docker\n", - "config = weaviate_memory_store.WeaviateConfig(url=\"http://localhost:8080\")\n", - "\n", - "# Using WCS. Make sure the environment variables `WEAVIATE_URL` and `WEAVIATE_API_KEY`\n", - "# were set in the `.env` file.\n", - "#\n", - "# weaviate_api, weaviate_url = sk.weaviate_settings_from_dot_env()\n", - "#\n", - "# config = weaviate_memory_store.WeaviateConfig(\n", - "# url=weaviate_url,\n", - "# api_key=weaviate_api\n", - "# )\n", - "\n", - "# Using Embedded Weaviate\n", - "# config = weaviate_memory_store.WeaviateConfig(use_embed=True)\n", - "\n", - "store = weaviate_memory_store.WeaviateMemoryStore(config=config)\n", - "store.client.schema.delete_all()" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Then, we register the memory store to the kernel:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from services import Service\n", - "\n", - "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n", - "selectedService = Service.OpenAI" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "kernel = sk.Kernel()\n", - "\n", - "chat_service_id = \"chat\"\n", - "if selectedService == Service.OpenAI:\n", - " api_key, org_id = sk.openai_settings_from_dot_env()\n", - " oai_chat_service = OpenAIChatCompletion(\n", - " service_id=chat_service_id, ai_model_id=\"gpt-3.5-turbo\", api_key=api_key, org_id=org_id\n", - " )\n", - " embedding_gen = OpenAITextEmbedding(ai_model_id=\"text-embedding-ada-002\", api_key=api_key, org_id=org_id)\n", - " kernel.add_service(oai_chat_service)\n", - " kernel.add_service(embedding_gen)\n", - "\n", - "memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_gen)\n", - "kernel.add_plugin(TextMemoryPlugin(memory), \"TextMemoryPlugin\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Manually adding memories\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Let's create some initial memories \"About Me\". We can add memories to our weaviate memory store by using `save_information`\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "collection_id = \"generic\"\n", - "\n", - "\n", - "async def populate_memory(memory: SemanticTextMemory) -> None:\n", - " # Add some documents to the semantic memory\n", - " await memory.save_information(collection=collection_id, id=\"info1\", text=\"Your budget for 2024 is $100,000\")\n", - " await memory.save_information(collection=collection_id, id=\"info2\", text=\"Your savings from 2023 are $50,000\")\n", - " await memory.save_information(collection=collection_id, id=\"info3\", text=\"Your investments are $80,000\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await populate_memory(memory)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Searching is done through `search`:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def search_memory_examples(memory: SemanticTextMemory) -> None:\n", - " questions = [\"What is my budget for 2024?\", \"What are my savings from 2023?\", \"What are my investments?\"]\n", - "\n", - " for question in questions:\n", - " print(f\"Question: {question}\")\n", - " result = await memory.search(collection_id, question)\n", - " print(f\"Answer: {result[0].text}\\n\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "await search_memory_examples(memory)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Here's how to use the weaviate memory store in a chat application:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def setup_chat_with_memory(\n", - " kernel: sk.Kernel,\n", - " service_id: str,\n", - ") -> sk.KernelFunction:\n", - " prompt = \"\"\"\n", - " ChatBot can have a conversation with you about any topic.\n", - " It can give explicit instructions or say 'I don't know' if\n", - " it does not have an answer.\n", - "\n", - " Information about me, from previous conversations:\n", - " - {{recall 'budget by year'}} What is my budget for 2024?\n", - " - {{recall 'savings from previous year'}} What are my savings from 2023?\n", - " - {{recall 'investments'}} What are my investments?\n", - "\n", - " {{$request}}\n", - " \"\"\".strip()\n", - "\n", - " prompt_template_config = PromptTemplateConfig(\n", - " template=prompt,\n", - " execution_settings={\n", - " service_id: kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)\n", - " },\n", - " )\n", - "\n", - " chat_func = kernel.add_function(\n", - " function_name=\"chat_with_memory\",\n", - " plugin_name=\"TextMemoryPlugin\",\n", - " prompt_template_config=prompt_template_config,\n", - " )\n", - "\n", - " return chat_func" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "async def chat(kernel: sk.Kernel, chat_func: sk.KernelFunction) -> bool:\n", - " try:\n", - " user_input = input(\"User:> \")\n", - " except KeyboardInterrupt:\n", - " print(\"\\n\\nExiting chat...\")\n", - " return False\n", - " except EOFError:\n", - " print(\"\\n\\nExiting chat...\")\n", - " return False\n", - "\n", - " if user_input == \"exit\":\n", - " print(\"\\n\\nExiting chat...\")\n", - " return False\n", - "\n", - " answer = await kernel.invoke(chat_func, request=user_input)\n", - "\n", - " print(f\"ChatBot:> {answer}\")\n", - " return True" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Populating memory...\")\n", - "await populate_memory(memory)\n", - "\n", - "print(\"Asking questions... (manually)\")\n", - "await search_memory_examples(memory)\n", - "\n", - "print(\"Setting up a chat (with memory!)\")\n", - "chat_func = await setup_chat_with_memory(kernel, chat_service_id)\n", - "\n", - "print(\"Begin chatting (type 'exit' to exit):\\n\")\n", - "print(\n", - " \"Welcome to the chat bot!\\\n", - " \\n Type 'exit' to exit.\\\n", - " \\n Try asking a question about your finances (i.e. \\\"talk to me about my finances\\\").\"\n", - ")\n", - "chatting = True\n", - "while chatting:\n", - " chatting = await chat(kernel, chat_func)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Adding documents to your memory\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Create a dictionary to hold some files. The key is the hyperlink to the file and the value is the file's content:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "github_files = {}\n", - "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"] = (\n", - " \"README: Installation, getting started, and how to contribute\"\n", - ")\n", - "github_files[\n", - " \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n", - "] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n", - "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"] = (\n", - " \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n", - ")\n", - "github_files[\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"] = (\n", - " \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n", - ")\n", - "github_files[\n", - " \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n", - "] = \"C# class that defines a volatile embedding store\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use `save_reference` to save the file:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "COLLECTION = \"SKGitHub\"\n", - "\n", - "print(\"Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.\")\n", - "i = 0\n", - "for entry, value in github_files.items():\n", - " await memory.save_reference(\n", - " collection=COLLECTION,\n", - " description=value,\n", - " text=value,\n", - " external_id=entry,\n", - " external_source_name=\"GitHub\",\n", - " )\n", - " i += 1\n", - " print(\" URL {} saved\".format(i))" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Use `search` to ask a question:\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "ask = \"I love Jupyter notebooks, how should I get started?\"\n", - "print(\"===========================\\n\" + \"Query: \" + ask + \"\\n\")\n", - "\n", - "memories = await memory.search(COLLECTION, ask, limit=5, min_relevance_score=0.77)\n", - "\n", - "i = 0\n", - "for memory in memories:\n", - " i += 1\n", - " print(f\"Result {i}:\")\n", - " print(\" URL: : \" + memory.id)\n", - " print(\" Title : \" + memory.description)\n", - " print(\" Relevance: \" + str(memory.relevance))\n", - " print()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.9.13" - } - }, - "nbformat": 4, - "nbformat_minor": 2 + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Introduction\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This notebook shows how to replace the `VolatileMemoryStore` memory storage used in a [previous notebook](./06-memory-and-embeddings.ipynb) with a `WeaviateMemoryStore`.\n", + "\n", + "`WeaviateMemoryStore` is an example of a persistent (i.e. long-term) memory store backed by the Weaviate vector database.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# About Weaviate\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "[Weaviate](https://weaviate.io/) is an open-source vector database designed to scale seamlessly into billions of data objects. This implementation supports hybrid search out-of-the-box (meaning it will perform better for keyword searches).\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "You can run Weaviate in 5 ways:\n", + "\n", + "- **SaaS** – with [Weaviate Cloud Services (WCS)](https://weaviate.io/pricing).\n", + "\n", + " WCS is a fully managed service that takes care of hosting, scaling, and updating your Weaviate instance. You can try it out for free with a sandbox that lasts for 14 days.\n", + "\n", + " To set up a SaaS Weaviate instance with WCS:\n", + "\n", + " 1. Navigate to [Weaviate Cloud Console](https://console.weaviate.cloud/).\n", + " 2. Register or sign in to your WCS account.\n", + " 3. Create a new cluster with the following settings:\n", + " - `Subscription Tier` – Free sandbox for a free trial, or contact [hello@weaviate.io](mailto:hello@weaviate.io) for other options.\n", + " - `Cluster name` – a unique name for your cluster. The name will become part of the URL used to access this instance.\n", + " - `Enable Authentication?` – Enabled by default. This will generate a static API key that you can use to authenticate.\n", + " 4. Wait for a few minutes until your cluster is ready. You will see a green tick ✔️ when it's done. Copy your cluster URL.\n", + "\n", + "- **Hybrid SaaS**\n", + "\n", + " > If you need to keep your data on-premise for security or compliance reasons, Weaviate also offers a Hybrid SaaS option: Weaviate runs within your cloud instances, but the cluster is managed remotely by Weaviate. This gives you the benefits of a managed service without sending data to an external party.\n", + "\n", + " The Weaviate Hybrid SaaS is a custom solution. If you are interested in this option, please reach out to [hello@weaviate.io](mailto:hello@weaviate.io).\n", + "\n", + "- **Self-hosted** – with a Docker container\n", + "\n", + " To set up a Weaviate instance with Docker:\n", + "\n", + " 1. [Install Docker](https://docs.docker.com/engine/install/) on your local machine if it is not already installed.\n", + " 2. [Install the Docker Compose Plugin](https://docs.docker.com/compose/install/)\n", + " 3. Download a `docker-compose.yml` file with this `curl` command:\n", + "\n", + " ```\n", + " curl -o docker-compose.yml \"https://configuration.weaviate.io/v2/docker-compose/docker-compose.yml?modules=standalone&runtime=docker-compose&weaviate_version=v1.19.6\"\n", + " ```\n", + "\n", + " Alternatively, you can use Weaviate's docker compose [configuration tool](https://weaviate.io/developers/weaviate/installation/docker-compose) to generate your own `docker-compose.yml` file.\n", + "\n", + " 4. Run `docker compose up -d` to spin up a Weaviate instance.\n", + "\n", + " > To shut it down, run `docker compose down`.\n", + "\n", + "- **Self-hosted** – with a Kubernetes cluster\n", + "\n", + " To configure a self-hosted instance with Kubernetes, follow Weaviate's [documentation](https://weaviate.io/developers/weaviate/installation/kubernetes).|\n", + "\n", + "- **Embedded** - start a weaviate instance right from your application code using the client library\n", + "\n", + " This code snippet shows how to instantiate an embedded weaviate instance and upload a document:\n", + "\n", + " ```python\n", + " import weaviate\n", + " from weaviate.embedded import EmbeddedOptions\n", + "\n", + " client = weaviate.Client(\n", + " embedded_options=EmbeddedOptions()\n", + " )\n", + "\n", + " data_obj = {\n", + " \"name\": \"Chardonnay\",\n", + " \"description\": \"Goes with fish\"\n", + " }\n", + "\n", + " client.data_object.create(data_obj, \"Wine\")\n", + " ```\n", + "\n", + " Refer to the [documentation](https://weaviate.io/developers/weaviate/installation/embedded) for more details about this deployment method.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Setup\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install semantic-kernel==0.9.8b1\n", + "!pip install weaviate-client\n", + "!pip install python-dotenv" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OS-specific notes:\n", + "\n", + "- if you run into SSL errors when connecting to OpenAI on macOS, see this issue for a [potential solution](https://github.com/microsoft/semantic-kernel/issues/627#issuecomment-1580912248)\n", + "- on Windows, you may need to run Docker Desktop as administrator\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "First, we instantiate the Weaviate memory store. Uncomment ONE of the options below, depending on how you want to use Weaviate:\n", + "\n", + "- from a Docker instance\n", + "- from WCS\n", + "- directly from the client (embedded Weaviate), which works on Linux only at the moment\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from semantic_kernel.connectors.memory.weaviate import weaviate_memory_store\n", + "\n", + "# Note the Weaviate Config values need to be either configured as environment variables\n", + "# or in the .env file, as a back up. When creating the instance of the `weaviate_memory_store`\n", + "# pass in `env_file_path=` to read the config values from the `.env` file, otherwise\n", + "# the values will be read from environment variables.\n", + "# Env variables or .env file config should look like:\n", + "# WEAVIATE_URL=\"http://localhost:8080\"\n", + "# WEAVIATE_API_KEY=\"\"\n", + "# WEAVIATE_USE_EMBED=True|False\n", + "\n", + "store = weaviate_memory_store.WeaviateMemoryStore()\n", + "store.client.schema.delete_all()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Then, we register the memory store to the kernel:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from services import Service\n", + "\n", + "# Select a service to use for this notebook (available services: OpenAI, AzureOpenAI, HuggingFace)\n", + "selectedService = Service.OpenAI" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from semantic_kernel.kernel import Kernel\n", + "from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion, OpenAITextEmbedding\n", + "from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory\n", + "from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore\n", + "from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin\n", + "\n", + "kernel = Kernel()\n", + "\n", + "chat_service_id = \"chat\"\n", + "if selectedService == Service.OpenAI:\n", + " oai_chat_service = OpenAIChatCompletion(service_id=chat_service_id, ai_model_id=\"gpt-3.5-turbo\")\n", + " embedding_gen = OpenAITextEmbedding(ai_model_id=\"text-embedding-ada-002\")\n", + " kernel.add_service(oai_chat_service)\n", + " kernel.add_service(embedding_gen)\n", + "\n", + "memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=embedding_gen)\n", + "kernel.add_plugin(TextMemoryPlugin(memory), \"TextMemoryPlugin\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Manually adding memories\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's create some initial memories \"About Me\". We can add memories to our weaviate memory store by using `save_information`\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "collection_id = \"generic\"\n", + "\n", + "\n", + "async def populate_memory(memory: SemanticTextMemory) -> None:\n", + " # Add some documents to the semantic memory\n", + " await memory.save_information(collection=collection_id, id=\"info1\", text=\"Your budget for 2024 is $100,000\")\n", + " await memory.save_information(collection=collection_id, id=\"info2\", text=\"Your savings from 2023 are $50,000\")\n", + " await memory.save_information(collection=collection_id, id=\"info3\", text=\"Your investments are $80,000\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await populate_memory(memory)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Searching is done through `search`:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def search_memory_examples(memory: SemanticTextMemory) -> None:\n", + " questions = [\"What is my budget for 2024?\", \"What are my savings from 2023?\", \"What are my investments?\"]\n", + "\n", + " for question in questions:\n", + " print(f\"Question: {question}\")\n", + " result = await memory.search(collection_id, question)\n", + " print(f\"Answer: {result[0].text}\\n\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "await search_memory_examples(memory)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Here's how to use the weaviate memory store in a chat application:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from semantic_kernel.functions.kernel_function import KernelFunction\n", + "from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig\n", + "\n", + "\n", + "async def setup_chat_with_memory(\n", + " kernel: Kernel,\n", + " service_id: str,\n", + ") -> KernelFunction:\n", + " prompt = \"\"\"\n", + " ChatBot can have a conversation with you about any topic.\n", + " It can give explicit instructions or say 'I don't know' if\n", + " it does not have an answer.\n", + "\n", + " Information about me, from previous conversations:\n", + " - {{recall 'budget by year'}} What is my budget for 2024?\n", + " - {{recall 'savings from previous year'}} What are my savings from 2023?\n", + " - {{recall 'investments'}} What are my investments?\n", + "\n", + " {{$request}}\n", + " \"\"\".strip()\n", + "\n", + " prompt_template_config = PromptTemplateConfig(\n", + " template=prompt,\n", + " execution_settings={\n", + " service_id: kernel.get_service(service_id).get_prompt_execution_settings_class()(service_id=service_id)\n", + " },\n", + " )\n", + "\n", + " chat_func = kernel.add_function(\n", + " function_name=\"chat_with_memory\",\n", + " plugin_name=\"TextMemoryPlugin\",\n", + " prompt_template_config=prompt_template_config,\n", + " )\n", + "\n", + " return chat_func" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "async def chat(kernel: Kernel, chat_func: KernelFunction) -> bool:\n", + " try:\n", + " user_input = input(\"User:> \")\n", + " except KeyboardInterrupt:\n", + " print(\"\\n\\nExiting chat...\")\n", + " return False\n", + " except EOFError:\n", + " print(\"\\n\\nExiting chat...\")\n", + " return False\n", + "\n", + " if user_input == \"exit\":\n", + " print(\"\\n\\nExiting chat...\")\n", + " return False\n", + "\n", + " answer = await kernel.invoke(chat_func, request=user_input)\n", + "\n", + " print(f\"ChatBot:> {answer}\")\n", + " return True" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(\"Populating memory...\")\n", + "await populate_memory(memory)\n", + "\n", + "print(\"Asking questions... (manually)\")\n", + "await search_memory_examples(memory)\n", + "\n", + "print(\"Setting up a chat (with memory!)\")\n", + "chat_func = await setup_chat_with_memory(kernel, chat_service_id)\n", + "\n", + "print(\"Begin chatting (type 'exit' to exit):\\n\")\n", + "print(\n", + " \"Welcome to the chat bot!\\\n", + " \\n Type 'exit' to exit.\\\n", + " \\n Try asking a question about your finances (i.e. \\\"talk to me about my finances\\\").\"\n", + ")\n", + "chatting = True\n", + "while chatting:\n", + " chatting = await chat(kernel, chat_func)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Adding documents to your memory\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create a dictionary to hold some files. The key is the hyperlink to the file and the value is the file's content:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "github_files = {}\n", + "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/README.md\"] = (\n", + " \"README: Installation, getting started, and how to contribute\"\n", + ")\n", + "github_files[\n", + " \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/02-running-prompts-from-file.ipynb\"\n", + "] = \"Jupyter notebook describing how to pass prompts from a file to a semantic plugin or function\"\n", + "github_files[\"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/notebooks/00-getting-started.ipynb\"] = (\n", + " \"Jupyter notebook describing how to get started with the Semantic Kernel\"\n", + ")\n", + "github_files[\"https://github.com/microsoft/semantic-kernel/tree/main/samples/plugins/ChatPlugin/ChatGPT\"] = (\n", + " \"Sample demonstrating how to create a chat plugin interfacing with ChatGPT\"\n", + ")\n", + "github_files[\n", + " \"https://github.com/microsoft/semantic-kernel/blob/main/dotnet/src/SemanticKernel/Memory/Volatile/VolatileMemoryStore.cs\"\n", + "] = \"C# class that defines a volatile embedding store\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `save_reference` to save the file:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "COLLECTION = \"SKGitHub\"\n", + "\n", + "print(\"Adding some GitHub file URLs and their descriptions to a volatile Semantic Memory.\")\n", + "i = 0\n", + "for entry, value in github_files.items():\n", + " await memory.save_reference(\n", + " collection=COLLECTION,\n", + " description=value,\n", + " text=value,\n", + " external_id=entry,\n", + " external_source_name=\"GitHub\",\n", + " )\n", + " i += 1\n", + " print(\" URL {} saved\".format(i))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Use `search` to ask a question:\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "ask = \"I love Jupyter notebooks, how should I get started?\"\n", + "print(\"===========================\\n\" + \"Query: \" + ask + \"\\n\")\n", + "\n", + "memories = await memory.search(COLLECTION, ask, limit=5, min_relevance_score=0.77)\n", + "\n", + "i = 0\n", + "for memory in memories:\n", + " i += 1\n", + " print(f\"Result {i}:\")\n", + " print(\" URL: : \" + memory.id)\n", + " print(\" Title : \" + memory.description)\n", + " print(\" Relevance: \" + str(memory.relevance))\n", + " print()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.13" + } + }, + "nbformat": 4, + "nbformat_minor": 2 } diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py index 97522e9a63..f6c381dbec 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_chat_completion.py @@ -1,23 +1,18 @@ # Copyright (c) Microsoft. All rights reserved. import logging -import sys -from typing import Any, List, Optional, Tuple - -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated +from typing import Annotated, Any, List, Tuple import google.generativeai as palm from google.generativeai.types import ChatResponse, MessageDict -from pydantic import PrivateAttr, StringConstraints +from pydantic import PrivateAttr, StringConstraints, ValidationError from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase from semantic_kernel.connectors.ai.google_palm.gp_prompt_execution_settings import ( GooglePalmChatPromptExecutionSettings, GooglePalmPromptExecutionSettings, ) +from semantic_kernel.connectors.ai.google_palm.settings.google_palm_settings import GooglePalmSettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase from semantic_kernel.contents.author_role import AuthorRole @@ -33,14 +28,15 @@ int_to_role = {1: AuthorRole.USER, 2: AuthorRole.SYSTEM, 3: AuthorRole.ASSISTANT class GooglePalmChatCompletion(ChatCompletionClientBase, TextCompletionClientBase): api_key: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] - _message_history: Optional[ChatHistory] = PrivateAttr() - service_id: Optional[str] = None + _message_history: ChatHistory | None = PrivateAttr() + service_id: str | None = None def __init__( self, ai_model_id: str, - api_key: str, - message_history: Optional[ChatHistory] = None, + api_key: str | None = None, + message_history: ChatHistory | None = None, + env_file_path: str | None = None, ): """ Initializes a new instance of the GooglePalmChatCompletion class. @@ -48,10 +44,27 @@ class GooglePalmChatCompletion(ChatCompletionClientBase, TextCompletionClientBas Arguments: ai_model_id {str} -- GooglePalm model name, see https://developers.generativeai.google/models/language - api_key {str} -- GooglePalm API key, see - https://developers.generativeai.google/products/palm - message_history {Optional[ChatHistory]} -- The message history to use for context. (Optional) + api_key {str | None} -- The optional API key to use. If not provided, will be read from either + the env vars or the .env settings file + message_history {ChatHistory | None} -- The message history to use for context. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback to + environment variables. (Optional) """ + google_palm_settings = None + try: + google_palm_settings = GooglePalmSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Error loading Google Palm pydantic settings: {e}") + + api_key = api_key or ( + google_palm_settings.api_key.get_secret_value() + if google_palm_settings and google_palm_settings.api_key + else None + ) + ai_model_id = ai_model_id or ( + google_palm_settings.chat_model_id if google_palm_settings and google_palm_settings.chat_model_id else None + ) + super().__init__( ai_model_id=ai_model_id, api_key=api_key, diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py index 70e4b219ae..ff36bd8231 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_completion.py @@ -1,20 +1,15 @@ # Copyright (c) Microsoft. All rights reserved. import logging -import sys -from typing import List - -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated +from typing import Annotated, List import google.generativeai as palm from google.generativeai.types import Completion from google.generativeai.types.text_types import TextCompletion -from pydantic import StringConstraints +from pydantic import StringConstraints, ValidationError from semantic_kernel.connectors.ai.google_palm.gp_prompt_execution_settings import GooglePalmTextPromptExecutionSettings +from semantic_kernel.connectors.ai.google_palm.settings.google_palm_settings import GooglePalmSettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase from semantic_kernel.contents.text_content import TextContent @@ -26,16 +21,32 @@ logger: logging.Logger = logging.getLogger(__name__) class GooglePalmTextCompletion(TextCompletionClientBase): api_key: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] - def __init__(self, ai_model_id: str, api_key: str): + def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: str | None = None): """ Initializes a new instance of the GooglePalmTextCompletion class. Arguments: ai_model_id {str} -- GooglePalm model name, see https://developers.generativeai.google/models/language - api_key {str} -- GooglePalm API key, see - https://developers.generativeai.google/products/palm + api_key {str | None} -- The optional API key to use. If not provided, will be + read from either the env vars or the .env settings file. + env_file_path {str | None} -- Use the environment settings file as a + fallback to environment variables. (Optional) """ + try: + google_palm_settings = GooglePalmSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Error loading Google Palm pydantic settings: {e}") + + api_key = api_key or ( + google_palm_settings.api_key.get_secret_value() + if google_palm_settings and google_palm_settings.api_key + else None + ) + ai_model_id = ai_model_id or ( + google_palm_settings.text_model_id if google_palm_settings and google_palm_settings.text_model_id else None + ) + super().__init__(ai_model_id=ai_model_id, api_key=api_key) async def complete(self, prompt: str, settings: GooglePalmTextPromptExecutionSettings) -> List[TextContent]: diff --git a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py index c50f58fd14..a4e08efc90 100644 --- a/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/google_palm/services/gp_text_embedding.py @@ -1,35 +1,49 @@ # Copyright (c) Microsoft. All rights reserved. - -import sys -from typing import Any, List - -if sys.version_info >= (3, 9): - from typing import Annotated -else: - from typing_extensions import Annotated +import logging +from typing import Annotated, Any, List import google.generativeai as palm from numpy import array, ndarray -from pydantic import StringConstraints +from pydantic import StringConstraints, ValidationError from semantic_kernel.connectors.ai.embeddings.embedding_generator_base import EmbeddingGeneratorBase +from semantic_kernel.connectors.ai.google_palm.settings.google_palm_settings import GooglePalmSettings from semantic_kernel.exceptions import ServiceInvalidAuthError, ServiceResponseException +logger: logging.Logger = logging.getLogger(__name__) + class GooglePalmTextEmbedding(EmbeddingGeneratorBase): api_key: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] - def __init__(self, ai_model_id: str, api_key: str) -> None: + def __init__(self, ai_model_id: str, api_key: str | None = None, env_file_path: str | None = None) -> None: """ Initializes a new instance of the GooglePalmTextEmbedding class. Arguments: ai_model_id {str} -- GooglePalm model name, see - https://developers.generativeai.google/models/language - api_key {str} -- GooglePalm API key, see - https://developers.generativeai.google/products/palm + https://developers.generativeai.google/models/language + api_key {str | None} -- The optional API key to use. If not provided, will be + read from either the env vars or the .env settings file. + env_file_path {str | None} -- Use the environment settings file + as a fallback to environment variables. (Optional) """ + try: + google_palm_settings = GooglePalmSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.error(f"Error loading Google Palm pydantic settings: {e}") + + api_key = api_key or ( + google_palm_settings.api_key.get_secret_value() + if google_palm_settings and google_palm_settings.api_key + else None + ) + ai_model_id = ai_model_id or ( + google_palm_settings.embedding_model_id + if google_palm_settings and google_palm_settings.embedding_model_id + else None + ) super().__init__(ai_model_id=ai_model_id, api_key=api_key) async def generate_embeddings(self, texts: List[str], **kwargs: Any) -> ndarray: diff --git a/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py b/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py new file mode 100644 index 0000000000..db0cdb2d64 --- /dev/null +++ b/python/semantic_kernel/connectors/ai/google_palm/settings/google_palm_settings.py @@ -0,0 +1,46 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class GooglePalmSettings(BaseSettings): + """Google Palm model settings + + The settings are first loaded from environment variables with the prefix 'GOOGLE_PALM_'. If the + environment variables are not found, the settings can be loaded from a .env file with the + encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored; + however, validation will fail alerting that the settings are missing. + + Optional settings for prefix 'GOOGLE_PALM_' are: + - api_key: SecretStr - GooglePalm API key, see https://developers.generativeai.google/products/palm + (Env var GOOGLE_PALM_API_KEY) + - env_file_path: {str | None} - Use the environment settings file as a fallback to environment variables. (Optional) + - chat_model_id: str | None - The GooglePalm chat model ID to use. + (Env var GOOGLE_PALM_CHAT_MODEL_ID) + - text_model_id: str | None - The GooglePalm text model ID to use. + (Env var GOOGLE_PALM_TEXT_MODEL_ID) + - embedding_model_id: str | None - The GooglePalm embedding model ID to use. + (Env var GOOGLE_PALM_EMBEDDING_MODEL_ID) + """ + + env_file_path: str | None = None + api_key: SecretStr | None = None + chat_model_id: str | None = None + text_model_id: str | None = None + embedding_model_id: str | None = None + + class Config: + env_prefix = "GOOGLE_PALM_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/connectors/ai/open_ai/const.py b/python/semantic_kernel/connectors/ai/open_ai/const.py index 1d9ce6ad89..e8e89f0cc6 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/const.py +++ b/python/semantic_kernel/connectors/ai/open_ai/const.py @@ -2,6 +2,6 @@ from typing import Final -DEFAULT_AZURE_API_VERSION: Final[str] = "2023-05-15" +DEFAULT_AZURE_API_VERSION: Final[str] = "2024-02-01" USER_AGENT: Final[str] = "User-Agent" DEFAULT_CHAT_SYSTEM_PROMPT: Final[str] = "Assistant is a large language model." diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py index c6db13ebcc..3ff528d57b 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_chat_completion.py @@ -2,7 +2,7 @@ import json import logging from copy import deepcopy -from typing import Any, Dict, Mapping, Optional, Union, overload +from typing import Any, Dict, Mapping, Optional, Union from uuid import uuid4 from openai import AsyncAzureOpenAI @@ -10,6 +10,7 @@ from openai.lib.azure import AsyncAzureADTokenProvider from openai.types.chat.chat_completion import ChatCompletion, Choice from openai.types.chat.chat_completion_chunk import ChatCompletionChunk from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import ( @@ -19,6 +20,7 @@ from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import Azu from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base import OpenAIChatCompletionBase from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIModelTypes from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import OpenAITextCompletionBase +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.contents.chat_message_content import ChatMessageContent from semantic_kernel.contents.finish_reason import FinishReason @@ -26,6 +28,7 @@ from semantic_kernel.contents.function_call_content import FunctionCallContent from semantic_kernel.contents.function_result_content import FunctionResultContent from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.contents.text_content import TextContent +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError from semantic_kernel.kernel_pydantic import HttpsUrl logger: logging.Logger = logging.getLogger(__name__) @@ -34,175 +37,70 @@ logger: logging.Logger = logging.getLogger(__name__) class AzureChatCompletion(AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase): """Azure Chat completion class.""" - @overload def __init__( self, - deployment_name: str, - base_url: Union[HttpsUrl, str], - service_id: Optional[str] = None, - api_version: str = DEFAULT_AZURE_API_VERSION, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, + service_id: str | None = None, + api_key: str | None = None, + deployment_name: str | None = None, + endpoint: str | None = None, + base_url: str | None = None, + api_version: str | None = None, + ad_token: str | None = None, + ad_token_provider: AsyncAzureADTokenProvider | None = None, + default_headers: Mapping[str, str] | None = None, + async_client: AsyncAzureOpenAI | None = None, + env_file_path: str | None = None, ) -> None: """ Initialize an AzureChatCompletion service. Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - base_url: The url of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal, the base_url consists of the endpoint, - followed by /openai/deployments/{deployment_name}/, - use endpoint if you only want to supply the endpoint. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to + service_id {str | None}: The service ID for the Azure deployment. (Optional) + api_key {str | None}: The optional api key. If provided, will override the value in the + env vars or .env file. + deployment_name {str | None}: The optional deployment. If provided, will override the value + (chat_deployment_name) in the env vars or .env file. + endpoint {str | None}: The optional deployment endpoint. If provided will override the value + in the env vars or .env file. + base_url {str | None}: The optional deployment base_url. If provided will override the value + in the env vars or .env file. + api_version {str | None}: The optional deployment api version. If provided will override the value + in the env vars or .env file. + ad_token {str | None}: The Azure Active Directory token. (Optional) + ad_token_provider {AsyncAzureADTokenProvider}: The Azure Active Directory token provider. (Optional) + default_headers {Mapping[str, str]}: The default headers mapping of string keys to string values for HTTP requests. (Optional) + async_client {AsyncAzureOpenAI | None} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback to using env vars. """ + azure_openai_settings = None + try: + azure_openai_settings = AzureOpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AzureOpenAI pydantic settings: {e}") - @overload - def __init__( - self, - deployment_name: str, - endpoint: Union[HttpsUrl, str], - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. + base_url = base_url or ( + str(azure_openai_settings.base_url) if azure_openai_settings and azure_openai_settings.base_url else None + ) + endpoint = endpoint or ( + str(azure_openai_settings.endpoint) if azure_openai_settings and azure_openai_settings.endpoint else None + ) + deployment_name = deployment_name or ( + azure_openai_settings.chat_deployment_name if azure_openai_settings else None + ) + api_version = api_version or (azure_openai_settings.api_version if azure_openai_settings else None) + api_key = api_key or ( + azure_openai_settings.api_key.get_secret_value() + if azure_openai_settings and azure_openai_settings.api_key + else None + ) - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal, the endpoint should end in openai.azure.com. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ + if api_version is None: + api_version = DEFAULT_AZURE_API_VERSION - @overload - def __init__( - self, - deployment_name: str, - async_client: AsyncAzureOpenAI, - service_id: Optional[str] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. + if not base_url and not endpoint: + raise ServiceInitializationError("At least one of base_url or endpoint must be provided.") - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - async_client {AsyncAzureOpenAI} -- An existing client to use. - """ - - @overload - def __init__( - self, - deployment_name: str, - endpoint: Union[HttpsUrl, str], - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. - - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal, the endpoint should end in openai.azure.com. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - log: The logger instance to use. (Optional) - """ - - def __init__( - self, - deployment_name: str, - endpoint: Optional[Union[HttpsUrl, str]] = None, - base_url: Optional[Union[HttpsUrl, str]] = None, - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - async_client: Optional[AsyncAzureOpenAI] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. - - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - base_url: The url of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal, the base_url consists of the endpoint, - followed by /openai/deployments/{deployment_name}/, - use endpoint if you only want to supply the endpoint. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal, the endpoint should end in openai.azure.com. - If both base_url and endpoint are supplied, base_url will be used. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) - """ if base_url and isinstance(base_url, str): base_url = HttpsUrl(base_url) if endpoint and deployment_name: @@ -228,19 +126,21 @@ class AzureChatCompletion(AzureOpenAIConfigBase, OpenAIChatCompletionBase, OpenA Arguments: settings: A dictionary of settings for the service. - should contains keys: deployment_name, endpoint, api_key - and optionally: api_version, ad_auth, default_headers + should contains keys: service_id, and optionally: + ad_auth, ad_token_provider, default_headers """ + return AzureChatCompletion( - deployment_name=settings.get("deployment_name"), - endpoint=settings.get("endpoint"), - base_url=settings.get("base_url"), - api_version=settings.get("api_version", DEFAULT_AZURE_API_VERSION), service_id=settings.get("service_id"), - api_key=settings.get("api_key"), + api_key=settings.get("api_key", None), + deployment_name=settings.get("deployment_name", None), + endpoint=settings.get("endpoint", None), + base_url=settings.get("base_url", None), + api_version=settings.get("api_version", None), ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), + env_file_path=settings.get("env_file_path", None), ) def get_prompt_execution_settings_class(self) -> "PromptExecutionSettings": diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py index bb74445b90..bdceb5f710 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_completion.py @@ -1,10 +1,11 @@ # Copyright (c) Microsoft. All rights reserved. import logging -from typing import Any, Dict, Mapping, Optional, overload +from typing import Mapping from openai import AsyncAzureOpenAI from openai.lib.azure import AsyncAzureADTokenProvider +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import ( @@ -16,6 +17,9 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( OpenAITextCompletionBase, ) +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError +from semantic_kernel.kernel_pydantic import HttpsUrl logger: logging.Logger = logging.getLogger(__name__) @@ -23,134 +27,79 @@ logger: logging.Logger = logging.getLogger(__name__) class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): """Azure Text Completion class.""" - @overload def __init__( self, - base_url: str, - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, + service_id: str | None = None, + api_key: str | None = None, + deployment_name: str | None = None, + endpoint: str | None = None, + base_url: str | None = None, + api_version: str | None = None, + ad_token: str | None = None, + ad_token_provider: AsyncAzureADTokenProvider | None = None, + default_headers: Mapping[str, str] | None = None, + async_client: AsyncAzureOpenAI | None = None, + env_file_path: str | None = None, ) -> None: """ Initialize an AzureTextCompletion service. Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. + service_id: The service ID for the Azure deployment. (Optional) + api_key {str | None}: The optional api key. If provided, will override the value in the + env vars or .env file. + deployment_name {str | None}: The optional deployment. If provided, will override the value + (text_deployment_name) in the env vars or .env file. + endpoint {str | None}: The optional deployment endpoint. If provided will override the value + in the env vars or .env file. + base_url {str | None}: The optional deployment base_url. If provided will override the value + in the env vars or .env file. + api_version {str | None}: The optional deployment api version. If provided will override the value + in the env vars or .env file. + ad_token: The Azure Active Directory token. (Optional) + ad_token_provider: The Azure Active Directory token provider. (Optional) default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) + async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback to + environment variables. (Optional) """ + azure_openai_settings = None + try: + azure_openai_settings = AzureOpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AzureOpenAI pydantic settings: {e}") - @overload - def __init__( - self, - deployment_name: str, - endpoint: str, - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - log: Optional[Any] = None, - ) -> None: - """ - Initialize an AzureTextCompletion service. + base_url = base_url or ( + str(azure_openai_settings.base_url) if azure_openai_settings and azure_openai_settings.base_url else None + ) + endpoint = endpoint or ( + str(azure_openai_settings.endpoint) if azure_openai_settings and azure_openai_settings.endpoint else None + ) + deployment_name = deployment_name or ( + azure_openai_settings.text_deployment_name if azure_openai_settings else None + ) + api_version = api_version or (azure_openai_settings.api_version if azure_openai_settings else None) + api_key = api_key or ( + azure_openai_settings.api_key.get_secret_value() + if azure_openai_settings and azure_openai_settings.api_key + else None + ) - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-05-15". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ + if api_version is None: + api_version = DEFAULT_AZURE_API_VERSION - @overload - def __init__( - self, - deployment_name: str, - async_client: AsyncAzureOpenAI, - service_id: Optional[str] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. + if not base_url and not endpoint: + raise ServiceInitializationError("At least one of base_url or endpoint must be provided.") - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - async_client {AsyncAzureOpenAI} -- An existing client to use. - """ + if base_url and isinstance(base_url, str): + base_url = HttpsUrl(base_url) + if endpoint and deployment_name: + base_url = HttpsUrl(f"{str(endpoint).rstrip('/')}/openai/deployments/{deployment_name}") - def __init__( - self, - deployment_name: Optional[str] = None, - endpoint: Optional[str] = None, - base_url: Optional[str] = None, - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - async_client: Optional[AsyncAzureOpenAI] = None, - ) -> None: - """ - Initialize an AzureTextCompletion service. - - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal. - api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - api_version: The API version to use. (Optional) - The default value is "2023-03-15-preview". - ad_auth: Whether to use Azure Active Directory authentication. (Optional) - The default value is False. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. - """ super().__init__( deployment_name=deployment_name, - endpoint=endpoint, + endpoint=endpoint if not isinstance(endpoint, str) else HttpsUrl(endpoint), base_url=base_url, api_version=api_version, service_id=service_id, @@ -163,7 +112,7 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): ) @classmethod - def from_dict(cls, settings: Dict[str, str]) -> "AzureTextCompletion": + def from_dict(cls, settings: dict[str, str]) -> "AzureTextCompletion": """ Initialize an Azure OpenAI service from a dictionary of settings. @@ -174,13 +123,14 @@ class AzureTextCompletion(AzureOpenAIConfigBase, OpenAITextCompletionBase): """ return AzureTextCompletion( - deployment_name=settings.get("deployment_name"), - endpoint=settings.get("endpoint"), - base_url=settings.get("base_url"), - api_version=settings.get("api_version", DEFAULT_AZURE_API_VERSION), service_id=settings.get("service_id"), - api_key=settings["api_key"], + api_key=settings.get("api_key", None), + deployment_name=settings.get("deployment_name", None), + endpoint=settings.get("endpoint", None), + base_url=settings.get("base_url", None), + api_version=settings.get("api_version", None), ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), + env_file_path=settings.get("env_file_path", None), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py index 4e2b2e39cb..1faf8ba28e 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/azure_text_embedding.py @@ -2,10 +2,11 @@ import logging -from typing import Dict, Mapping, Optional, overload +from typing import Mapping from openai import AsyncAzureOpenAI from openai.lib.azure import AsyncAzureADTokenProvider +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION from semantic_kernel.connectors.ai.open_ai.services.azure_config_base import ( @@ -17,6 +18,9 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import ( OpenAITextEmbeddingBase, ) +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError +from semantic_kernel.kernel_pydantic import HttpsUrl logger: logging.Logger = logging.getLogger(__name__) @@ -24,67 +28,80 @@ logger: logging.Logger = logging.getLogger(__name__) class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): """Azure Text Embedding class.""" - @overload def __init__( self, - deployment_name: str, - async_client: AsyncAzureOpenAI, - service_id: Optional[str] = None, - ) -> None: - """ - Initialize an AzureChatCompletion service. - - Arguments: - deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - async_client {AsyncAzureOpenAI} -- An existing client to use. - """ - - def __init__( - self, - deployment_name: str, - endpoint: Optional[str] = None, - api_version: str = DEFAULT_AZURE_API_VERSION, - service_id: Optional[str] = None, - api_key: Optional[str] = None, - ad_token: Optional[str] = None, - ad_token_provider: Optional[AsyncAzureADTokenProvider] = None, - default_headers: Optional[Mapping[str, str]] = None, - async_client: Optional[AsyncAzureOpenAI] = None, + service_id: str | None = None, + api_key: str | None = None, + deployment_name: str | None = None, + endpoint: str | None = None, + base_url: str | None = None, + api_version: str | None = None, + ad_token: str | None = None, + ad_token_provider: AsyncAzureADTokenProvider | None = None, + default_headers: Mapping[str, str] | None = None, + async_client: AsyncAzureOpenAI | None = None, + env_file_path: str | None = None, ) -> None: """ Initialize an AzureTextEmbedding service. - You must provide: - - A deployment_name, endpoint, and api_key (plus, optionally: ad_auth) - - :param deployment_name: The name of the Azure deployment. This value - will correspond to the custom name you chose for your deployment - when you deployed a model. This value can be found under - Resource Management > Deployments in the Azure portal or, alternatively, - under Management > Deployments in Azure OpenAI Studio. - :param endpoint: The endpoint of the Azure deployment. This value - can be found in the Keys & Endpoint section when examining - your resource from the Azure portal. - :param api_version: The API version to use. (Optional) - The default value is "2023-05-15". - :param api_key: The API key for the Azure deployment. This value can be - found in the Keys & Endpoint section when examining your resource in - the Azure portal. You can use either KEY1 or KEY2. - :param ad_token : The Azure AD token for authentication. (Optional) - :param ad_auth: Whether to use Azure Active Directory authentication. + service_id: The service ID. (Optional) + api_key {str | None}: The optional api key. If provided, will override the value in the + env vars or .env file. + deployment_name {str | None}: The optional deployment. If provided, will override the value + (text_deployment_name) in the env vars or .env file. + endpoint {str | None}: The optional deployment endpoint. If provided will override the value + in the env vars or .env file. + base_url {str | None}: The optional deployment base_url. If provided will override the value + in the env vars or .env file. + api_version {str | None}: The optional deployment api version. If provided will override the value + in the env vars or .env file. + ad_token {str | None}: The Azure AD token for authentication. (Optional) + ad_auth {AsyncAzureADTokenProvider | None}: Whether to use Azure Active Directory authentication. (Optional) The default value is False. - :param default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - :param async_client: An existing client to use. (Optional) - + default_headers: The default headers mapping of string keys to + string values for HTTP requests. (Optional) + async_client {Optional[AsyncAzureOpenAI]} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback to + environment variables. (Optional) """ + azure_openai_settings = None + try: + azure_openai_settings = AzureOpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AzureOpenAI pydantic settings: {e}") + + base_url = base_url or ( + str(azure_openai_settings.base_url) if azure_openai_settings and azure_openai_settings.base_url else None + ) + endpoint = endpoint or ( + str(azure_openai_settings.endpoint) if azure_openai_settings and azure_openai_settings.endpoint else None + ) + deployment_name = deployment_name or ( + azure_openai_settings.embedding_deployment_name if azure_openai_settings else None + ) + api_version = api_version or (azure_openai_settings.api_version if azure_openai_settings else None) + api_key = api_key or ( + azure_openai_settings.api_key.get_secret_value() + if azure_openai_settings and azure_openai_settings.api_key + else None + ) + + if api_version is None: + api_version = DEFAULT_AZURE_API_VERSION + + if not base_url and not endpoint: + raise ServiceInitializationError("At least one of base_url or endpoint must be provided.") + + if base_url and isinstance(base_url, str): + base_url = HttpsUrl(base_url) + if endpoint and deployment_name: + base_url = HttpsUrl(f"{str(endpoint).rstrip('/')}/openai/deployments/{deployment_name}") + super().__init__( deployment_name=deployment_name, - endpoint=endpoint, + endpoint=endpoint if not isinstance(endpoint, str) else HttpsUrl(endpoint), + base_url=base_url, api_version=api_version, service_id=service_id, api_key=api_key, @@ -96,7 +113,7 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): ) @classmethod - def from_dict(cls, settings: Dict[str, str]) -> "AzureTextEmbedding": + def from_dict(cls, settings: dict[str, str]) -> "AzureTextEmbedding": """ Initialize an Azure OpenAI service from a dictionary of settings. @@ -106,12 +123,14 @@ class AzureTextEmbedding(AzureOpenAIConfigBase, OpenAITextEmbeddingBase): and optionally: api_version, ad_auth """ return AzureTextEmbedding( - deployment_name=settings["deployment_name"], - endpoint=settings["endpoint"], - api_key=settings["api_key"], - api_version=settings.get("api_version", DEFAULT_AZURE_API_VERSION), service_id=settings.get("service_id"), + api_key=settings.get("api_key", None), + deployment_name=settings.get("deployment_name", None), + endpoint=settings.get("endpoint", None), + base_url=settings.get("base_url", None), + api_version=settings.get("api_version", None), ad_token=settings.get("ad_token"), ad_token_provider=settings.get("ad_token_provider"), default_headers=settings.get("default_headers"), + env_file_path=settings.get("env_file_path", None), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py index dcc674c6d2..cdf88fbe36 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_chat_completion.py @@ -4,11 +4,10 @@ import logging from typing import ( Dict, Mapping, - Optional, - overload, ) from openai import AsyncOpenAI +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion_base import OpenAIChatCompletionBase from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import OpenAIConfigBase @@ -18,6 +17,7 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( OpenAITextCompletionBase, ) +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings logger: logging.Logger = logging.getLogger(__name__) @@ -25,12 +25,15 @@ logger: logging.Logger = logging.getLogger(__name__) class OpenAIChatCompletion(OpenAIConfigBase, OpenAIChatCompletionBase, OpenAITextCompletionBase): """OpenAI Chat completion class.""" - @overload def __init__( self, - ai_model_id: str, - async_client: AsyncOpenAI, - service_id: Optional[str] = None, + ai_model_id: str | None = None, + service_id: str | None = None, + api_key: str | None = None, + org_id: str | None = None, + default_headers: Mapping[str, str] | None = None, + async_client: AsyncOpenAI | None = None, + env_file_path: str | None = None, ) -> None: """ Initialize an OpenAIChatCompletion service. @@ -38,77 +41,31 @@ class OpenAIChatCompletion(OpenAIConfigBase, OpenAIChatCompletionBase, OpenAITex Arguments: ai_model_id {str} -- OpenAI model name, see https://platform.openai.com/docs/models - async_client {AsyncOpenAI} -- An existing client to use. - """ - - @overload - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - org_id: Optional[str] = None, - service_id: Optional[str] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an OpenAIChatCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys - org_id {Optional[str]} -- OpenAI organization ID. - This is usually optional unless your - account belongs to multiple organizations. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ - - @overload - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - service_id: Optional[str] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an OpenAIChatCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ - - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - org_id: Optional[str] = None, - service_id: Optional[str] = None, - default_headers: Optional[Mapping[str, str]] = None, - async_client: Optional[AsyncOpenAI] = None, - ) -> None: - """ - Initialize an OpenAIChatCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys - org_id {Optional[str]} -- OpenAI organization ID. - This is usually optional unless your - account belongs to multiple organizations. + service_id {str | None} -- Service ID tied to the execution settings. + api_key {str | None} -- The optional API key to use. If provided will override, + the env vars or .env file value. + org_id {str | None} -- The optional org ID to use. If provided will override, + the env vars or .env file value. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback + to environment variables. (Optional) """ + openai_settings = None + try: + openai_settings = OpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load OpenAI pydantic settings: {e}") + + api_key = api_key or ( + openai_settings.api_key.get_secret_value() if openai_settings and openai_settings.api_key else None + ) + org_id = org_id or (openai_settings.org_id if openai_settings and openai_settings.org_id else None) + ai_model_id = ai_model_id or ( + openai_settings.chat_model_id if openai_settings and openai_settings.chat_model_id else None + ) + super().__init__( ai_model_id=ai_model_id, api_key=api_key, @@ -130,8 +87,6 @@ class OpenAIChatCompletion(OpenAIConfigBase, OpenAIChatCompletionBase, OpenAITex return OpenAIChatCompletion( ai_model_id=settings["ai_model_id"], - api_key=settings["api_key"], - org_id=settings.get("org_id"), service_id=settings.get("service_id"), default_headers=settings.get("default_headers"), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py index 0fd9e85cda..824b83e684 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_completion.py @@ -2,9 +2,10 @@ import json import logging -from typing import Dict, Mapping, Optional, overload +from typing import Dict, Mapping, Optional from openai import AsyncOpenAI +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import ( OpenAIConfigBase, @@ -15,6 +16,7 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion_base import ( OpenAITextCompletionBase, ) +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings logger: logging.Logger = logging.getLogger(__name__) @@ -22,90 +24,46 @@ logger: logging.Logger = logging.getLogger(__name__) class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): """OpenAI Text Completion class.""" - @overload def __init__( self, - ai_model_id: str, - async_client: AsyncOpenAI, - service_id: Optional[str] = None, - ) -> None: - """ - Initialize an OpenAITextCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - async_client {AsyncOpenAI} -- An existing client to use. - """ - - @overload - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - org_id: Optional[str] = None, - service_id: Optional[str] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an OpenAITextCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys (Optional) - org_id {Optional[str]} -- OpenAI organization ID. - This is usually optional unless your - account belongs to multiple organizations. - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ - - @overload - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - service_id: Optional[str] = None, - default_headers: Optional[Mapping[str, str]] = None, - ) -> None: - """ - Initialize an OpenAITextCompletion service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys (Optional) - default_headers: The default headers mapping of string keys to - string values for HTTP requests. (Optional) - """ - - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - org_id: Optional[str] = None, + ai_model_id: str | None = None, + api_key: str | None = None, + org_id: str | None = None, service_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, async_client: Optional[AsyncOpenAI] = None, + env_file_path: str | None = None, ) -> None: """ Initialize an OpenAITextCompletion service. Arguments: - ai_model_id {str} -- OpenAI model name, see + ai_model_id {str | None} -- OpenAI model name, see https://platform.openai.com/docs/models - api_key {Optional[str]} -- OpenAI API key, see - https://platform.openai.com/account/api-keys (Optional) - org_id {Optional[str]} -- OpenAI organization ID. - This is usually optional unless your - account belongs to multiple organizations. + service_id {str | None} -- Service ID tied to the execution settings. + api_key {str | None} -- The optional API key to use. If provided will override, + the env vars or .env file value. + org_id {str | None} -- The optional org ID to use. If provided will override, + the env vars or .env file value. default_headers: The default headers mapping of string keys to string values for HTTP requests. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as a fallback to + environment variables. (Optional) """ + try: + openai_settings = OpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load OpenAI pydantic settings: {e}") + + api_key = api_key or ( + openai_settings.api_key.get_secret_value() if openai_settings and openai_settings.api_key else None + ) + org_id = org_id or (openai_settings.org_id if openai_settings and openai_settings.org_id else None) + ai_model_id = ai_model_id or ( + openai_settings.text_model_id if openai_settings and openai_settings.text_model_id else None + ) + super().__init__( ai_model_id=ai_model_id, api_key=api_key, @@ -127,9 +85,10 @@ class OpenAITextCompletion(OpenAITextCompletionBase, OpenAIConfigBase): if "default_headers" in settings and isinstance(settings["default_headers"], str): settings["default_headers"] = json.loads(settings["default_headers"]) return OpenAITextCompletion( - ai_model_id=settings["ai_model_id"], - api_key=settings["api_key"], - org_id=settings.get("org_id"), + ai_model_id=settings.get("ai_model_id", None), + api_key=settings.get("api_key", None), + org_id=settings.get("org_id", None), service_id=settings.get("service_id"), default_headers=settings.get("default_headers"), + env_file_path=settings.get("env_file_path", None), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py index 7b1c2476fa..e8ad1025b5 100644 --- a/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py +++ b/python/semantic_kernel/connectors/ai/open_ai/services/open_ai_text_embedding.py @@ -1,9 +1,10 @@ # Copyright (c) Microsoft. All rights reserved. import logging -from typing import Dict, Mapping, Optional, overload +from typing import Dict, Mapping, Optional from openai import AsyncOpenAI +from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.services.open_ai_config_base import ( OpenAIConfigBase, @@ -14,6 +15,7 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import ( from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding_base import ( OpenAITextEmbeddingBase, ) +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings logger: logging.Logger = logging.getLogger(__name__) @@ -21,30 +23,15 @@ logger: logging.Logger = logging.getLogger(__name__) class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): """OpenAI Text Embedding class.""" - @overload def __init__( self, ai_model_id: str, - async_client: AsyncOpenAI, - service_id: Optional[str] = None, - ) -> None: - """ - Initialize an OpenAITextEmbedding service. - - Arguments: - ai_model_id {str} -- OpenAI model name, see - https://platform.openai.com/docs/models - async_client {AsyncOpenAI} -- An existing client to use. - """ - - def __init__( - self, - ai_model_id: str, - api_key: Optional[str] = None, - org_id: Optional[str] = None, + api_key: str | None = None, + org_id: str | None = None, service_id: Optional[str] = None, default_headers: Optional[Mapping[str, str]] = None, async_client: Optional[AsyncOpenAI] = None, + env_file_path: str | None = None, ) -> None: """ Initializes a new instance of the OpenAITextCompletion class. @@ -52,15 +39,30 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): Arguments: ai_model_id {str} -- OpenAI model name, see https://platform.openai.com/docs/models - api_key {str} -- OpenAI API key, see - https://platform.openai.com/account/api-keys - org_id {Optional[str]} -- OpenAI organization ID. - This is usually optional unless your - account belongs to multiple organizations. + service_id {str | None} -- Service ID tied to the execution settings. + api_key {str | None} -- The optional API key to use. If provided will override, + the env vars or .env file value. + org_id {str | None} -- The optional org ID to use. If provided will override, + the env vars or .env file value. default_headers {Optional[Mapping[str,str]]}: The default headers mapping of string keys to string values for HTTP requests. (Optional) async_client {Optional[AsyncOpenAI]} -- An existing client to use. (Optional) + env_file_path {str | None} -- Use the environment settings file as + a fallback to environment variables. (Optional) """ + try: + openai_settings = OpenAISettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load OpenAI pydantic settings: {e}") + + api_key = api_key or ( + openai_settings.api_key.get_secret_value() if openai_settings and openai_settings.api_key else None + ) + org_id = org_id or (openai_settings.org_id if openai_settings and openai_settings.org_id else None) + ai_model_id = ai_model_id or ( + openai_settings.embedding_model_id if openai_settings and openai_settings.embedding_model_id else None + ) + super().__init__( ai_model_id=ai_model_id, api_key=api_key, @@ -82,8 +84,9 @@ class OpenAITextEmbedding(OpenAIConfigBase, OpenAITextEmbeddingBase): return OpenAITextEmbedding( ai_model_id=settings["ai_model_id"], - api_key=settings["api_key"], - org_id=settings.get("org_id"), + api_key=settings.get("api_key", None), + org_id=settings.get("org_id", None), service_id=settings.get("service_id"), default_headers=settings.get("default_headers"), + env_file_path=settings.get("env_file_path", None), ) diff --git a/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py b/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py new file mode 100644 index 0000000000..27ecc718d1 --- /dev/null +++ b/python/semantic_kernel/connectors/ai/open_ai/settings/azure_open_ai_settings.py @@ -0,0 +1,79 @@ +# Copyright (c) Microsoft. All rights reserved. + + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + +from semantic_kernel.kernel_pydantic import HttpsUrl + + +class AzureOpenAISettings(BaseSettings): + """AzureOpenAI model settings + + The settings are first loaded from environment variables with the prefix 'AZURE_OPENAI_'. + If the environment variables are not found, the settings can be loaded from a .env file + with the encoding 'utf-8'. If the settings are not found in the .env file, the settings + are ignored; however, validation will fail alerting that the settings are missing. + + Optional settings for prefix 'AZURE_OPENAI_' are: + - chat_deployment_name: str - The name of the Azure Chat deployment. This value + will correspond to the custom name you chose for your deployment + when you deployed a model. This value can be found under + Resource Management > Deployments in the Azure portal or, alternatively, + under Management > Deployments in Azure OpenAI Studio. + (Env var AZURE_OPENAI_CHAT_DEPLOYMENT_NAME) + - text_deployment_name: str - The name of the Azure Text deployment. This value + will correspond to the custom name you chose for your deployment + when you deployed a model. This value can be found under + Resource Management > Deployments in the Azure portal or, alternatively, + under Management > Deployments in Azure OpenAI Studio. + (Env var AZURE_OPENAI_TEXT_DEPLOYMENT_NAME) + - embedding_deployment_name: str - The name of the Azure Embedding deployment. This value + will correspond to the custom name you chose for your deployment + when you deployed a model. This value can be found under + Resource Management > Deployments in the Azure portal or, alternatively, + under Management > Deployments in Azure OpenAI Studio. + (Env var AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME) + - api_key: SecretStr - The API key for the Azure deployment. This value can be + found in the Keys & Endpoint section when examining your resource in + the Azure portal. You can use either KEY1 or KEY2. + (Env var AZURE_OPENAI_API_KEY) + - base_url: HttpsUrl | None - base_url: The url of the Azure deployment. This value + can be found in the Keys & Endpoint section when examining + your resource from the Azure portal, the base_url consists of the endpoint, + followed by /openai/deployments/{deployment_name}/, + use endpoint if you only want to supply the endpoint. + (Env var AZURE_OPENAI_BASE_URL) + - endpoint: HttpsUrl - The endpoint of the Azure deployment. This value + can be found in the Keys & Endpoint section when examining + your resource from the Azure portal, the endpoint should end in openai.azure.com. + If both base_url and endpoint are supplied, base_url will be used. + (Env var AZURE_OPENAI_ENDPOINT) + - api_version: str | None - The API version to use. The default value is "2024-02-01". + (Env var AZURE_OPENAI_API_VERSION) + - env_file_path: str | None - if provided, the .env settings are read from this file path location + """ + + env_file_path: str | None = None + chat_deployment_name: str | None = None + text_deployment_name: str | None = None + embedding_deployment_name: str | None = None + endpoint: HttpsUrl | None = None + base_url: HttpsUrl | None = None + api_key: SecretStr | None = None + api_version: str | None = None + + class Config: + env_prefix = "AZURE_OPENAI_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py new file mode 100644 index 0000000000..7898296553 --- /dev/null +++ b/python/semantic_kernel/connectors/ai/open_ai/settings/open_ai_settings.py @@ -0,0 +1,49 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class OpenAISettings(BaseSettings): + """OpenAI model settings + + The settings are first loaded from environment variables with the prefix 'OPENAI_'. If the + environment variables are not found, the settings can be loaded from a .env file with the + encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored; + however, validation will fail alerting that the settings are missing. + + Optional settings for prefix 'OPENAI_' are: + - api_key: SecretStr - OpenAI API key, see https://platform.openai.com/account/api-keys + (Env var OPENAI_API_KEY) + - org_id: str | None - This is usually optional unless your account belongs to multiple organizations. + (Env var OPENAI_ORG_ID) + - chat_model_id: str | None - The OpenAI chat model ID to use, for example, gpt-3.5-turbo or gpt-4. + (Env var OPENAI_CHAT_MODEL_ID) + - text_model_id: str | None - The OpenAI text model ID to use, for example, gpt-3.5-turbo-instruct. + (Env var OPENAI_TEXT_MODEL_ID) + - embedding_model_id: str | None - The OpenAI embedding model ID to use, for example, text-embedding-ada-002. + (Env var OPENAI_EMBEDDING_MODEL_ID) + - env_file_path: str | None - if provided, the .env settings are read from this file path location + """ + + env_file_path: str | None = None + org_id: str | None = None + api_key: SecretStr | None = None + chat_model_id: str | None = None + text_model_id: str | None = None + embedding_model_id: str | None = None + + class Config: + env_prefix = "OPENAI_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/connectors/memory/astradb/__init__.py b/python/semantic_kernel/connectors/memory/astradb/__init__.py index b8907d8388..fd1e8448b1 100644 --- a/python/semantic_kernel/connectors/memory/astradb/__init__.py +++ b/python/semantic_kernel/connectors/memory/astradb/__init__.py @@ -3,5 +3,6 @@ from semantic_kernel.connectors.memory.astradb.astradb_memory_store import ( AstraDBMemoryStore, ) +from semantic_kernel.connectors.memory.astradb.astradb_settings import AstraDBSettings -__all__ = ["AstraDBMemoryStore"] +__all__ = ["AstraDBMemoryStore", "AstraDBSettings"] diff --git a/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py b/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py index ed1254a16d..ce38e562da 100644 --- a/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py +++ b/python/semantic_kernel/connectors/memory/astradb/astradb_memory_store.py @@ -6,13 +6,15 @@ from typing import List, Optional, Tuple import aiohttp from numpy import ndarray +from pydantic import ValidationError from semantic_kernel.connectors.memory.astradb.astra_client import AstraClient +from semantic_kernel.connectors.memory.astradb.astradb_settings import AstraDBSettings from semantic_kernel.connectors.memory.astradb.utils import ( build_payload, parse_payload, ) -from semantic_kernel.exceptions import ServiceInitializationError +from semantic_kernel.exceptions import MemoryConnectorInitializationError from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase @@ -37,7 +39,8 @@ class AstraDBMemoryStore(MemoryStoreBase): keyspace_name: str, embedding_dim: int, similarity: str, - session: Optional[aiohttp.ClientSession] = None, + session: aiohttp.ClientSession | None = None, + env_file_path: str | None = None, ) -> None: """Initializes a new instance of the AstraDBMemoryStore class. @@ -49,13 +52,37 @@ class AstraDBMemoryStore(MemoryStoreBase): embedding_dim {int} -- The dimensionality to use for new collections. similarity {str} -- TODO session -- Optional session parameter + env_file_path {str | None} -- Use the environment settings file as a + fallback to environment variables. (Optional) """ + astradb_settings = None + try: + astradb_settings = AstraDBSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AstraDB pydantic settings: {e}") + + # Load the settings and validate + astra_application_token = astra_application_token or ( + astradb_settings.app_token.get_secret_value() if astradb_settings and astradb_settings.app_token else None + ) + assert astra_application_token is not None, "The astra_application_token cannot be None." + astra_id = astra_id or (astradb_settings.db_id if astradb_settings and astradb_settings.db_id else None) + assert astra_id is not None, "The astra_id cannot be None." + astra_region = astra_region or ( + astradb_settings.region if astradb_settings and astradb_settings.region else None + ) + assert astra_region is not None, "The astra_region cannot be None." + keyspace_name = keyspace_name or ( + astradb_settings.keyspace if astradb_settings and astradb_settings.keyspace else None + ) + assert keyspace_name is not None, "The keyspace_name cannot be None." + self._embedding_dim = embedding_dim self._similarity = similarity self._session = session if self._embedding_dim > MAX_DIMENSIONALITY: - raise ServiceInitializationError( + raise MemoryConnectorInitializationError( f"Dimensionality of {self._embedding_dim} exceeds " + f"the maximum allowed value of {MAX_DIMENSIONALITY}." ) diff --git a/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py b/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py new file mode 100644 index 0000000000..d010e4e128 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/astradb/astradb_settings.py @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class AstraDBSettings(BaseModelSettings): + """AstraDB model settings + + Optional: + - app_token: SecretStr | None - AstraDB token + (Env var ASTRADB_APP_TOKEN) + - db_id: str | None - AstraDB database ID + (Env var ASTRADB_DB_ID) + - region: str | None - AstraDB region + (Env var ASTRADB_REGION) + - keyspace: str | None - AstraDB keyspace + (Env var ASTRADB_KEYSPACE) + """ + + app_token: SecretStr + db_id: str + region: str + keyspace: str + + class Config(BaseModelSettings.Config): + env_prefix = "ASTRADB_" diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/__init__.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/__init__.py index 8592bc7b7c..3c04124667 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/__init__.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/__init__.py @@ -1,7 +1,8 @@ # Copyright (c) Microsoft. All rights reserved. +from semantic_kernel.connectors.memory.azure_cognitive_search.azure_ai_search_settings import AzureAISearchSettings from semantic_kernel.connectors.memory.azure_cognitive_search.azure_cognitive_search_memory_store import ( AzureCognitiveSearchMemoryStore, ) -__all__ = ["AzureCognitiveSearchMemoryStore"] +__all__ = ["AzureCognitiveSearchMemoryStore", "AzureAISearchSettings"] diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py new file mode 100644 index 0000000000..42e416dd49 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_ai_search_settings.py @@ -0,0 +1,33 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings +from semantic_kernel.kernel_pydantic import HttpsUrl + + +class AzureAISearchSettings(BaseModelSettings): + """Azure AI Search model settings currently used by the AzureCognitiveSearchMemoryStore connector + + Optional: + - api_key: SecretStr - Azure AI Search API key (Env var AZURE_AI_SEARCH_API_KEY) + - endpoint: HttpsUrl - Azure AI Search endpoint (Env var AZURE_AI_SEARCH_ENDPOINT) + - index_name: str - Azure AI Search index name (Env var AZURE_AI_SEARCH_INDEX_NAME) + """ + + api_key: SecretStr | None = None + endpoint: HttpsUrl | None = None + index_name: str | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "AZURE_AI_SEARCH_" + + def model_dump(self): + """ + Custom method to dump model data in the required format. + """ + return { + "api_key": self.api_key.get_secret_value() if self.api_key else None, + "endpoint": str(self.endpoint), + "index_name": self.index_name, + } diff --git a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py index 22e5593356..415d20415d 100644 --- a/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cognitive_search/azure_cognitive_search_memory_store.py @@ -18,6 +18,7 @@ from azure.search.documents.indexes.models import ( ) from azure.search.documents.models import VectorizedQuery from numpy import ndarray +from pydantic import ValidationError from semantic_kernel.connectors.memory.azure_cognitive_search.utils import ( SEARCH_FIELD_EMBEDDING, @@ -29,7 +30,7 @@ from semantic_kernel.connectors.memory.azure_cognitive_search.utils import ( get_search_index_async_client, memory_record_to_search_record, ) -from semantic_kernel.exceptions import ServiceInitializationError, ServiceResourceNotFoundError +from semantic_kernel.exceptions import MemoryConnectorInitializationError, MemoryConnectorResourceNotFound from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase @@ -43,30 +44,51 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): def __init__( self, vector_size: int, - search_endpoint: Optional[str] = None, - admin_key: Optional[str] = None, - azure_credentials: Optional[AzureKeyCredential] = None, - token_credentials: Optional[TokenCredential] = None, - **kwargs, + search_endpoint: str | None = None, + admin_key: str | None = None, + azure_credentials: AzureKeyCredential | None = None, + token_credentials: TokenCredential | None = None, + env_file_path: str | None = None, ) -> None: """Initializes a new instance of the AzureCognitiveSearchMemoryStore class. Arguments: vector_size {int} -- Embedding vector size. - search_endpoint {Optional[str]} -- The endpoint of the Azure Cognitive Search service + search_endpoint {str | None} -- The endpoint of the Azure Cognitive Search service (default: {None}). - admin_key {Optional[str]} -- Azure Cognitive Search API key (default: {None}). - azure_credentials {Optional[AzureKeyCredential]} -- Azure Cognitive Search credentials (default: {None}). - token_credentials {Optional[TokenCredential]} -- Azure Cognitive Search token credentials + admin_key {str | None} -- Azure Cognitive Search API key (default: {None}). + azure_credentials {AzureKeyCredential | None} -- Azure Cognitive Search credentials (default: {None}). + token_credentials {TokenCredential | None} -- Azure Cognitive Search token credentials (default: {None}). + env_file_path {str | None} -- Use the environment settings file as a fallback + to environment variables Instantiate using Async Context Manager: async with AzureCognitiveSearchMemoryStore(<...>) as memory: await memory.<...> """ + from semantic_kernel.connectors.memory.azure_cognitive_search import AzureAISearchSettings + + acs_memory_settings = None + try: + acs_memory_settings = AzureAISearchSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AzureAISearch pydantic settings: {e}") + + admin_key = admin_key or ( + acs_memory_settings.api_key.get_secret_value() + if acs_memory_settings and acs_memory_settings.api_key + else None + ) + assert admin_key, "The ACS admin_key is required to connect to Azure Cognitive Search." + search_endpoint = search_endpoint or ( + acs_memory_settings.endpoint if acs_memory_settings and acs_memory_settings.endpoint else None + ) + assert search_endpoint, "The ACS endpoint is required to connect to Azure Cognitive Search." + self._vector_size = vector_size self._search_index_client = get_search_index_async_client( - search_endpoint, admin_key, azure_credentials, token_credentials + str(search_endpoint), admin_key, azure_credentials, token_credentials ) async def close(self): @@ -122,7 +144,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): ) if not self._search_index_client: - raise ServiceInitializationError("Error: self._search_index_client not set 1.") + raise MemoryConnectorInitializationError("Error: self._search_index_client not set 1.") # Check to see if collection exists collection_index = None @@ -264,7 +286,7 @@ class AzureCognitiveSearchMemoryStore(MemoryStoreBase): ) except ResourceNotFoundError as exc: await search_client.close() - raise ServiceResourceNotFoundError("Memory record not found") from exc + raise MemoryConnectorResourceNotFound("Memory record not found") from exc await search_client.close() diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/__init__.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/__init__.py index ca310d9b09..2c29757473 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/__init__.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/__init__.py @@ -3,5 +3,6 @@ from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmos_db_memory_store import ( AzureCosmosDBMemoryStore, ) +from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmosdb_settings import AzureCosmosDBSettings -__all__ = ["AzureCosmosDBMemoryStore"] +__all__ = ["AzureCosmosDBMemoryStore", "AzureCosmosDBSettings"] diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py index fc008b8d22..dd0f6c4b41 100644 --- a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmos_db_memory_store.py @@ -1,19 +1,25 @@ # Copyright (c) Microsoft. All rights reserved. + +import logging from typing import List, Tuple from numpy import ndarray +from pydantic import ValidationError from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmos_db_store_api import AzureCosmosDBStoreApi +from semantic_kernel.connectors.memory.azure_cosmosdb.azure_cosmosdb_settings import AzureCosmosDBSettings from semantic_kernel.connectors.memory.azure_cosmosdb.cosmosdb_utils import ( CosmosDBSimilarityType, CosmosDBVectorSearchType, get_mongodb_search_client, ) from semantic_kernel.connectors.memory.azure_cosmosdb.mongo_vcore_store_api import MongoStoreApi -from semantic_kernel.exceptions import ServiceInitializationError +from semantic_kernel.exceptions import MemoryConnectorInitializationError from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase +logger: logging.Logger = logging.getLogger(__name__) + class AzureCosmosDBMemoryStore(MemoryStoreBase): """A memory store that uses AzureCosmosDB for MongoDB vCore, to perform vector similarity search on a fully @@ -48,13 +54,13 @@ class AzureCosmosDBMemoryStore(MemoryStoreBase): ef_search: int = 40, ): if vector_dimensions <= 0: - raise ServiceInitializationError("Vector dimensions must be a positive number.") + raise MemoryConnectorInitializationError("Vector dimensions must be a positive number.") # if connection_string is None: # raise ValueError("Connection String cannot be empty.") if database_name is None: - raise ServiceInitializationError("Database Name cannot be empty.") + raise MemoryConnectorInitializationError("Database Name cannot be empty.") if index_name is None: - raise ServiceInitializationError("Index Name cannot be empty.") + raise MemoryConnectorInitializationError("Index Name cannot be empty.") self.cosmosStore = cosmosStore self.index_name = index_name @@ -80,11 +86,25 @@ class AzureCosmosDBMemoryStore(MemoryStoreBase): m, ef_construction, ef_search, + env_file_path: str | None = None, ) -> MemoryStoreBase: """Creates the underlying data store based on the API definition""" # Right now this only supports Mongo, but set up to support more later. apiStore: AzureCosmosDBStoreApi = None if cosmos_api == "mongo-vcore": + + cosmosdb_settings = None + try: + cosmosdb_settings = AzureCosmosDBSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load AzureCosmosDB pydantic settings: {e}") + + cosmos_connstr = cosmos_connstr or ( + cosmosdb_settings.connection_string.get_secret_value() + if cosmosdb_settings and cosmosdb_settings.connection_string + else None + ) + mongodb_client = get_mongodb_search_client(cosmos_connstr, application_name) database = mongodb_client[database_name] apiStore = MongoStoreApi( @@ -100,7 +120,7 @@ class AzureCosmosDBMemoryStore(MemoryStoreBase): ef_search=ef_search, ) else: - raise NotImplementedError(f"API type {cosmos_api} is not supported.") + raise MemoryConnectorInitializationError(f"API type {cosmos_api} is not supported.") store = AzureCosmosDBMemoryStore( apiStore, diff --git a/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py new file mode 100644 index 0000000000..6dadde931e --- /dev/null +++ b/python/semantic_kernel/connectors/memory/azure_cosmosdb/azure_cosmosdb_settings.py @@ -0,0 +1,20 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class AzureCosmosDBSettings(BaseModelSettings): + """Azure CosmosDB model settings + + Optional: + - connection_string: str - Azure CosmosDB connection string + (Env var COSMOSDB_CONNECTION_STRING) + """ + + api: str | None = None + connection_string: SecretStr | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "COSMOSDB_" diff --git a/python/semantic_kernel/connectors/memory/memory_settings_base.py b/python/semantic_kernel/connectors/memory/memory_settings_base.py new file mode 100644 index 0000000000..ec65ddd611 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/memory_settings_base.py @@ -0,0 +1,21 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic_settings import BaseSettings + + +class BaseModelSettings(BaseSettings): + env_file_path: str | None = None + + class Config: + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/__init__.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/__init__.py index 4ee1e46966..3e3c3775c9 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/__init__.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/__init__.py @@ -1,5 +1,8 @@ +# Copyright (c) Microsoft. All rights reserved. + from semantic_kernel.connectors.memory.mongodb_atlas.mongodb_atlas_memory_store import ( MongoDBAtlasMemoryStore, ) +from semantic_kernel.connectors.memory.mongodb_atlas.mongodb_atlas_settings import MongoDBAtlasSettings -__all__ = ["MongoDBAtlasMemoryStore"] +__all__ = ["MongoDBAtlasMemoryStore", "MongoDBAtlasSettings"] diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py index 16a7204a09..31e75e6f63 100644 --- a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_memory_store.py @@ -3,10 +3,11 @@ from __future__ import annotations import logging from importlib import metadata -from typing import Any, List, Mapping, Optional, Tuple +from typing import Any, List, Mapping, Tuple from motor import core, motor_asyncio from numpy import ndarray +from pydantic import ValidationError from pymongo import DeleteOne, ReadPreference, UpdateOne, results from pymongo.driver_info import DriverInfo @@ -22,7 +23,6 @@ from semantic_kernel.connectors.memory.mongodb_atlas.utils import ( from semantic_kernel.exceptions import ServiceResourceNotFoundError from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase -from semantic_kernel.utils.settings import mongodb_atlas_settings_from_dot_env logger: logging.Logger = logging.getLogger(__name__) @@ -38,16 +38,28 @@ class MongoDBAtlasMemoryStore(MemoryStoreBase): def __init__( self, - index_name: Optional[str] = None, - connection_string: Optional[str] = None, - database_name: Optional[str] = None, - read_preference: Optional[ReadPreference] = ReadPreference.PRIMARY, - **kwargs, + index_name: str | None = None, + connection_string: str | None = None, + database_name: str | None = None, + read_preference: ReadPreference | None = ReadPreference.PRIMARY, + env_file_path: str | None = None, ): - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") + from semantic_kernel.connectors.memory.mongodb_atlas import MongoDBAtlasSettings + + mongodb_settings = None + try: + mongodb_settings = MongoDBAtlasSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load the MongoDBAtlas pydantic settings: {e}") + + connection_string = connection_string or ( + mongodb_settings.connection_string.get_secret_value() + if mongodb_settings and mongodb_settings.connection_string + else None + ) + self._mongo_client = motor_asyncio.AsyncIOMotorClient( - connection_string or mongodb_atlas_settings_from_dot_env(), + connection_string, read_preference=read_preference, driver=DriverInfo("Microsoft Semantic Kernel", metadata.version("semantic-kernel")), ) diff --git a/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py new file mode 100644 index 0000000000..a9223fd9c4 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/mongodb_atlas/mongodb_atlas_settings.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class MongoDBAtlasSettings(BaseModelSettings): + """MongoDB Atlas model settings + + Optional: + - connection_string: str - MongoDB Atlas connection string + (Env var MONGODB_ATLAS_CONNECTION_STRING) + """ + + connection_string: SecretStr | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "MONGODB_ATLAS_" diff --git a/python/semantic_kernel/connectors/memory/pinecone/__init__.py b/python/semantic_kernel/connectors/memory/pinecone/__init__.py index 92a5f112ed..61f63d4333 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/__init__.py +++ b/python/semantic_kernel/connectors/memory/pinecone/__init__.py @@ -3,5 +3,8 @@ from semantic_kernel.connectors.memory.pinecone.pinecone_memory_store import ( PineconeMemoryStore, ) +from semantic_kernel.connectors.memory.pinecone.pinecone_settings import ( + PineconeSettings, +) -__all__ = ["PineconeMemoryStore"] +__all__ = ["PineconeMemoryStore", "PineconeSettings"] diff --git a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py index 89b86e0bc5..c0f9a78db8 100644 --- a/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py +++ b/python/semantic_kernel/connectors/memory/pinecone/pinecone_memory_store.py @@ -5,7 +5,9 @@ from typing import List, NamedTuple, Optional, Tuple from numpy import ndarray from pinecone import FetchResponse, IndexDescription, IndexList, Pinecone, ServerlessSpec +from pydantic import ValidationError +from semantic_kernel.connectors.memory.pinecone.pinecone_settings import PineconeSettings from semantic_kernel.connectors.memory.pinecone.utils import ( build_payload, parse_payload, @@ -45,21 +47,33 @@ class PineconeMemoryStore(MemoryStoreBase): self, api_key: str, default_dimensionality: int, - **kwargs, + env_file_path: str | None = None, ) -> None: """Initializes a new instance of the PineconeMemoryStore class. Arguments: pinecone_api_key {str} -- The Pinecone API key. default_dimensionality {int} -- The default dimensionality to use for new collections. + env_file_path {str | None} -- Use the environment settings file as a fallback + to environment variables. (Optional) """ - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") if default_dimensionality > MAX_DIMENSIONALITY: raise ServiceInitializationError( f"Dimensionality of {default_dimensionality} exceeds " + f"the maximum allowed value of {MAX_DIMENSIONALITY}." ) + + pinecone_settings = None + try: + pinecone_settings = PineconeSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load the Pinecone pydantic settings: {e}") + + api_key = api_key or ( + pinecone_settings.api_key.get_secret_value() if pinecone_settings and pinecone_settings.api_key else None + ) + assert api_key, "The Pinecone api_key cannot be None." + self._pinecone_api_key = api_key self._default_dimensionality = default_dimensionality diff --git a/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py b/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py new file mode 100644 index 0000000000..190521a0e7 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/pinecone/pinecone_settings.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class PineconeSettings(BaseModelSettings): + """Pinecone model settings + + Required: + - api_key: SecretStr - Pinecone API key + (Env var PINECONE_API_KEY) + """ + + api_key: SecretStr | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "PINECONE_" diff --git a/python/semantic_kernel/connectors/memory/postgres/__init__.py b/python/semantic_kernel/connectors/memory/postgres/__init__.py index 029e7fed4c..7a0e7301d8 100644 --- a/python/semantic_kernel/connectors/memory/postgres/__init__.py +++ b/python/semantic_kernel/connectors/memory/postgres/__init__.py @@ -3,5 +3,6 @@ from semantic_kernel.connectors.memory.postgres.postgres_memory_store import ( PostgresMemoryStore, ) +from semantic_kernel.connectors.memory.postgres.postgres_settings import PostgresSettings -__all__ = ["PostgresMemoryStore"] +__all__ = ["PostgresMemoryStore", "PostgresSettings"] diff --git a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py index 7c8dcb352b..22306606bd 100644 --- a/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py +++ b/python/semantic_kernel/connectors/memory/postgres/postgres_memory_store.py @@ -10,7 +10,9 @@ from numpy import ndarray from psycopg import Cursor from psycopg.sql import SQL, Identifier from psycopg_pool import ConnectionPool +from pydantic import ValidationError +from semantic_kernel.connectors.memory.postgres.postgres_settings import PostgresSettings from semantic_kernel.exceptions import ( ServiceInitializationError, ServiceResourceNotFoundError, @@ -41,7 +43,7 @@ class PostgresMemoryStore(MemoryStoreBase): min_pool: int, max_pool: int, schema: str = DEFAULT_SCHEMA, - **kwargs, + env_file_path: str | None = None, ) -> None: """Initializes a new instance of the PostgresMemoryStore class. @@ -52,10 +54,22 @@ class PostgresMemoryStore(MemoryStoreBase): max_pool {int} -- The maximum number of connections in the connection pool.\n schema {str} -- The schema to use. (default: {"public"})\n timezone_offset {Optional[str]} -- The timezone offset to use. (default: {None}) - Expected format '-7:00'. Uses the local timezone offset when not provided.\n + Expected format '-7:00'. Uses the local timezone offset when not provided.\n + env_file_path {str | None} -- Use the environment settings file as a fallback + to environment variables. (Optional) """ - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") + postgres_settings = None + try: + postgres_settings = PostgresSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load Postgres pydantic settings: {e}") + + connection_string = connection_string or ( + postgres_settings.connection_string.get_secret_value() + if postgres_settings and postgres_settings.connection_string + else None + ) + self._check_dimensionality(default_dimensionality) self._connection_string = connection_string diff --git a/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py b/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py new file mode 100644 index 0000000000..e4df824f08 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/postgres/postgres_settings.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class PostgresSettings(BaseModelSettings): + """Postgres model settings + + Required: + - connection_string: str - Postgres connection string + (Env var POSTGRES_CONNECTION_STRING) + """ + + connection_string: SecretStr | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "POSTGRES_" diff --git a/python/semantic_kernel/connectors/memory/redis/__init__.py b/python/semantic_kernel/connectors/memory/redis/__init__.py index 85a1b31919..16e086af74 100644 --- a/python/semantic_kernel/connectors/memory/redis/__init__.py +++ b/python/semantic_kernel/connectors/memory/redis/__init__.py @@ -3,5 +3,6 @@ from semantic_kernel.connectors.memory.redis.redis_memory_store import ( RedisMemoryStore, ) +from semantic_kernel.connectors.memory.redis.redis_settings import RedisSettings -__all__ = ["RedisMemoryStore"] +__all__ = ["RedisMemoryStore", "RedisSettings"] diff --git a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py index 95e0511ee6..841e99757b 100644 --- a/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py +++ b/python/semantic_kernel/connectors/memory/redis/redis_memory_store.py @@ -6,11 +6,13 @@ from typing import List, Tuple import numpy as np import redis from numpy import ndarray +from pydantic import ValidationError from redis.commands.search.field import TextField, VectorField from redis.commands.search.indexDefinition import IndexDefinition, IndexType from redis.commands.search.query import Query from redis.exceptions import ResponseError +from semantic_kernel.connectors.memory.redis.redis_settings import RedisSettings from semantic_kernel.connectors.memory.redis.utils import ( deserialize_document_to_record, deserialize_redis_to_record, @@ -50,7 +52,7 @@ class RedisMemoryStore(MemoryStoreBase): vector_type: str = "FLOAT32", vector_index_algorithm: str = "HNSW", query_dialect: int = 2, - **kwargs, + env_file_path: str | None = None, ) -> None: """ RedisMemoryStore is an abstracted interface to interact with a Redis node connection. @@ -64,10 +66,21 @@ class RedisMemoryStore(MemoryStoreBase): vector_type {str} -- Vector type, defaults to FLOAT32 vector_index_algorithm {str} -- Indexing algorithm for vectors, defaults to HNSW query_dialect {int} -- Query dialect, must be 2 or greater for vector similarity searching, defaults to 2 - + env_file_path {str | None} -- Use the environment settings file as a fallback to + environment variables, defaults to False """ - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") + redis_settings = None + try: + redis_settings = RedisSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load Redis pydantic settings: {e}") + + connection_string = connection_string or ( + redis_settings.connection_string.get_secret_value() + if redis_settings and redis_settings.connection_string + else None + ) + if vector_size <= 0: raise ServiceInitializationError("Vector dimension must be a positive integer") diff --git a/python/semantic_kernel/connectors/memory/redis/redis_settings.py b/python/semantic_kernel/connectors/memory/redis/redis_settings.py new file mode 100644 index 0000000000..93fd02831c --- /dev/null +++ b/python/semantic_kernel/connectors/memory/redis/redis_settings.py @@ -0,0 +1,19 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings + + +class RedisSettings(BaseModelSettings): + """Redis model settings + + Optional: + - connection_string: str | None - Redis connection string + (Env var REDIS_CONNECTION_STRING) + """ + + connection_string: SecretStr | None = None + + class Config(BaseModelSettings.Config): + env_prefix = "REDIS_" diff --git a/python/semantic_kernel/connectors/memory/weaviate/__init__.py b/python/semantic_kernel/connectors/memory/weaviate/__init__.py index dacbcb42bb..3f53c056d1 100644 --- a/python/semantic_kernel/connectors/memory/weaviate/__init__.py +++ b/python/semantic_kernel/connectors/memory/weaviate/__init__.py @@ -2,5 +2,6 @@ from semantic_kernel.connectors.memory.weaviate.weaviate_memory_store import ( WeaviateMemoryStore, ) +from semantic_kernel.connectors.memory.weaviate.weaviate_settings import WeaviateSettings -__all__ = ["WeaviateMemoryStore"] +__all__ = ["WeaviateMemoryStore", "WeaviateSettings"] diff --git a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py index 4cca2a814a..116998ad93 100644 --- a/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py +++ b/python/semantic_kernel/connectors/memory/weaviate/weaviate_memory_store.py @@ -7,9 +7,9 @@ from typing import List, Tuple import numpy as np import weaviate -from weaviate.embedded import EmbeddedOptions +from pydantic import ValidationError -from semantic_kernel.exceptions import ServiceInitializationError +from semantic_kernel.connectors.memory.weaviate.weaviate_settings import WeaviateSettings from semantic_kernel.memory.memory_record import MemoryRecord from semantic_kernel.memory.memory_store_base import MemoryStoreBase @@ -115,25 +115,59 @@ class WeaviateMemoryStore(MemoryStoreBase): """ return {key.lstrip("_"): value for key, value in sk_dict.items()} - def __init__(self, config: WeaviateConfig, **kwargs): - if kwargs.get("logger"): - logger.warning("The `logger` parameter is deprecated. Please use the `logging` module instead.") - self.config = config + def __init__(self, config: WeaviateConfig | None = None, env_file_path: str | None = None): + """Initializes a new instance of the WeaviateMemoryStore + + Optional parameters: + - env_file_path {str | None} -- Whether to use the environment settings (.env) file. Defaults to False. + """ + + # Initialize settings from environment variables or defaults defined in WeaviateSettings + weaviate_settings = None + try: + weaviate_settings = WeaviateSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load WeaviateSettings pydantic settings: {e}") + + # Override settings with provided config if available + if config: + self.settings = self.merge_settings(weaviate_settings, config) + else: + self.settings = weaviate_settings + + self.settings.validate_settings() self.client = self._initialize_client() - def _initialize_client(self): - if self.config.use_embed: - return weaviate.Client(embedded_options=EmbeddedOptions()) - elif self.config.url: - if self.config.api_key: - return weaviate.Client( - url=self.config.url, - auth_client_secret=weaviate.auth.AuthApiKey(api_key=self.config.api_key), - ) - else: - return weaviate.Client(url=self.config.url) - else: - raise ServiceInitializationError("Weaviate config must have either url or use_embed set") + def merge_settings(self, default_settings: WeaviateSettings, config: WeaviateConfig) -> WeaviateSettings: + """ + Merges default settings with configuration provided through WeaviateConfig. + + This function allows for manual overriding of settings from the config parameter. + """ + return WeaviateSettings( + url=config.url or (str(default_settings.url) if default_settings and default_settings.url else None), + api_key=config.api_key + or (default_settings.api_key.get_secret_value() if default_settings and default_settings.api_key else None), + use_embed=( + config.use_embed + if config.use_embed is not None + else (default_settings.use_embed if default_settings and default_settings.use_embed else False) + ), + ) + + def _initialize_client(self) -> weaviate.Client: + """ + Initializes the Weaviate client based on the combined settings. + """ + if self.settings.use_embed: + return weaviate.Client(embedded_options=weaviate.EmbeddedOptions()) + + if self.settings.api_key: + return weaviate.Client( + url=self.settings.url, auth_client_secret=weaviate.auth.AuthApiKey(api_key=self.settings.api_key) + ) + + return weaviate.Client(url=self.settings.url) async def create_collection(self, collection_name: str) -> None: schema = SCHEMA.copy() diff --git a/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py b/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py new file mode 100644 index 0000000000..866f82e996 --- /dev/null +++ b/python/semantic_kernel/connectors/memory/weaviate/weaviate_settings.py @@ -0,0 +1,28 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr + +from semantic_kernel.connectors.memory.memory_settings_base import BaseModelSettings +from semantic_kernel.kernel_pydantic import HttpsUrl + + +class WeaviateSettings(BaseModelSettings): + """Weaviate model settings + + Optional: + - url: HttpsUrl | None - Weaviate URL (Env var WEAVIATE_URL) + - api_key: SecretStr | None - Weaviate token (Env var WEAVIATE_API_KEY) + - use_embed: bool - Whether to use the client embedding options + (Env var WEAVIATE_USE_EMBED) + """ + + url: HttpsUrl | None = None + api_key: SecretStr | None = None + use_embed: bool = False + + class Config(BaseModelSettings.Config): + env_prefix = "WEAVIATE_" + + def validate_settings(self): + if not self.use_embed and not self.url: + raise ValueError("Weaviate config must have either url or use_embed set") diff --git a/python/semantic_kernel/connectors/search_engine/bing_connector.py b/python/semantic_kernel/connectors/search_engine/bing_connector.py index 6c019fb88c..0d0cb27152 100644 --- a/python/semantic_kernel/connectors/search_engine/bing_connector.py +++ b/python/semantic_kernel/connectors/search_engine/bing_connector.py @@ -5,9 +5,11 @@ import urllib from typing import List import aiohttp +from pydantic import ValidationError +from semantic_kernel.connectors.search_engine.bing_connector_settings import BingSettings from semantic_kernel.connectors.search_engine.connector import ConnectorBase -from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvalidRequestError +from semantic_kernel.exceptions import ServiceInvalidRequestError logger: logging.Logger = logging.getLogger(__name__) @@ -19,13 +21,25 @@ class BingConnector(ConnectorBase): _api_key: str - def __init__(self, api_key: str) -> None: - self._api_key = api_key + def __init__(self, api_key: str | None = None, env_file_path: str | None = None) -> None: + """Initializes a new instance of the BingConnector class. - if not self._api_key: - raise ServiceInitializationError( - "Bing API key cannot be null. Please set environment variable BING_API_KEY." - ) + Arguments: + api_key {str | None}: The Bing Search API key. If provided, will override + the value in the env vars or .env file. + env_file_path {str | None}: The optional path to the .env file. If provided, + the settings are read from this file path location. + """ + bing_settings = None + try: + bing_settings = BingSettings(env_file_path=env_file_path) + except ValidationError as e: + logger.warning(f"Failed to load the Bing pydantic settings: {e}.") + + self._api_key = api_key or ( + bing_settings.api_key.get_secret_value() if bing_settings and bing_settings.api_key else None + ) + assert self._api_key, "API key cannot be 'None' or empty." async def search(self, query: str, num_results: int = 1, offset: int = 0) -> List[str]: """ diff --git a/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py b/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py new file mode 100644 index 0000000000..38a4966d50 --- /dev/null +++ b/python/semantic_kernel/connectors/search_engine/bing_connector_settings.py @@ -0,0 +1,36 @@ +# Copyright (c) Microsoft. All rights reserved. + +from pydantic import SecretStr +from pydantic_settings import BaseSettings + + +class BingSettings(BaseSettings): + """Bing Connector settings + + The settings are first loaded from environment variables with the prefix 'BING_'. If the + environment variables are not found, the settings can be loaded from a .env file with the + encoding 'utf-8'. If the settings are not found in the .env file, the settings are ignored; + however, validation will fail alerting that the settings are missing. + + Optional settings for prefix 'BING_' are: + - api_key: SecretStr - The Bing API key (Env var BING_API_KEY) + + """ + + env_file_path: str | None = None + api_key: SecretStr | None = None + + class Config: + env_prefix = "BING_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/core_plugins/sessions_python_tool/README.md b/python/semantic_kernel/core_plugins/sessions_python_tool/README.md index 9ac97aafa8..eb700ae07f 100644 --- a/python/semantic_kernel/core_plugins/sessions_python_tool/README.md +++ b/python/semantic_kernel/core_plugins/sessions_python_tool/README.md @@ -88,7 +88,7 @@ chat_service = AzureChatCompletion( kernel.add_service(chat_service) python_code_interpreter = SessionsPythonTool( - **azure_container_apps_settings_from_dot_env_as_dict(), auth_callback=auth_callback + auth_callback=auth_callback ) sessions_tool = kernel.add_plugin(python_code_interpreter, "PythonCodeInterpreter") diff --git a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py index 38c62178ac..96a3a87c35 100644 --- a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py +++ b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_plugin.py @@ -9,11 +9,12 @@ from io import BufferedReader, BytesIO from typing import Annotated, Any, Awaitable, Callable import httpx -from pydantic import field_validator +from pydantic import ValidationError, field_validator from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT, version_info from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_settings import ( + ACASessionsSettings, SessionsPythonSettings, ) from semantic_kernel.core_plugins.sessions_python_tool.sessions_remote_file_metadata import SessionsRemoteFileMetadata @@ -37,10 +38,11 @@ class SessionsPythonTool(KernelBaseModel): def __init__( self, - pool_management_endpoint: str, auth_callback: Callable[..., Awaitable[Any]], + pool_management_endpoint: str | None = None, settings: SessionsPythonSettings | None = None, http_client: httpx.AsyncClient | None = None, + env_file_path: str | None = None, **kwargs, ): """Initializes a new instance of the SessionsPythonTool class.""" @@ -50,8 +52,16 @@ class SessionsPythonTool(KernelBaseModel): if not http_client: http_client = httpx.AsyncClient() + try: + aca_settings = ACASessionsSettings.create(env_file_path=env_file_path) + except ValidationError as e: + logger.error(f"Failed to load the ACASessionsSettings with message: {str(e)}") + raise FunctionExecutionException(f"Failed to load the ACASessionsSettings with message: {str(e)}") from e + + endpoint = pool_management_endpoint or aca_settings.pool_management_endpoint + super().__init__( - pool_management_endpoint=pool_management_endpoint, + pool_management_endpoint=endpoint, auth_callback=auth_callback, settings=settings, http_client=http_client, @@ -61,6 +71,8 @@ class SessionsPythonTool(KernelBaseModel): @field_validator("pool_management_endpoint", mode="before") @classmethod def _validate_endpoint(cls, endpoint: str): + endpoint = str(endpoint) + """Validates the pool management endpoint.""" if "/python/execute" in endpoint: # Remove '/python/execute/' and ensure the endpoint ends with a '/' diff --git a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py index 4ea3457ed5..7b008b59df 100644 --- a/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py +++ b/python/semantic_kernel/core_plugins/sessions_python_tool/sessions_python_settings.py @@ -6,8 +6,9 @@ import uuid from enum import Enum from pydantic import Field +from pydantic_settings import BaseSettings -from semantic_kernel.kernel_pydantic import KernelBaseModel +from semantic_kernel.kernel_pydantic import HttpsUrl, KernelBaseModel class CodeInputType(str, Enum): @@ -32,3 +33,30 @@ class SessionsPythonSettings(KernelBaseModel): python_code: str | None = Field(alias="pythonCode", default=None) timeout_in_sec: int | None = Field(default=100, alias="timeoutInSeconds") sanitize_input: bool | None = Field(default=True, alias="sanitizeInput") + + +class ACASessionsSettings(BaseSettings): + """Azure Container Apps sessions settings. + + Required: + - pool_management_endpoint: HttpsUrl - The URL of the Azure Container Apps pool management endpoint. + (Env var ACA_POOL_MANAGEMENT_ENDPOINT) + """ + + env_file_path: str | None = None + pool_management_endpoint: HttpsUrl + + class Config: + env_prefix = "ACA_" + env_file = None + env_file_encoding = "utf-8" + extra = "ignore" + case_sensitive = False + + @classmethod + def create(cls, **kwargs): + if "env_file_path" in kwargs and kwargs["env_file_path"]: + cls.Config.env_file = kwargs["env_file_path"] + else: + cls.Config.env_file = None + return cls(**kwargs) diff --git a/python/semantic_kernel/exceptions/__init__.py b/python/semantic_kernel/exceptions/__init__.py index c4d62eb82e..5b9b21b91d 100644 --- a/python/semantic_kernel/exceptions/__init__.py +++ b/python/semantic_kernel/exceptions/__init__.py @@ -3,6 +3,7 @@ from semantic_kernel.exceptions.content_exceptions import * # noqa: F401, F403 from semantic_kernel.exceptions.function_exceptions import * # noqa: F401, F403 from semantic_kernel.exceptions.kernel_exceptions import * # noqa: F401, F403 +from semantic_kernel.exceptions.memory_connector_exceptions import * # noqa: F401, F403 from semantic_kernel.exceptions.planner_exceptions import * # noqa: F401, F403 from semantic_kernel.exceptions.service_exceptions import * # noqa: F401, F403 from semantic_kernel.exceptions.template_engine_exceptions import * # noqa: F401, F403 diff --git a/python/semantic_kernel/exceptions/memory_connector_exceptions.py b/python/semantic_kernel/exceptions/memory_connector_exceptions.py new file mode 100644 index 0000000000..b72a266762 --- /dev/null +++ b/python/semantic_kernel/exceptions/memory_connector_exceptions.py @@ -0,0 +1,23 @@ +# Copyright (c) Microsoft. All rights reserved. + + +from semantic_kernel.exceptions.kernel_exceptions import KernelException + + +class MemoryConnectorException(KernelException): + pass + + +class MemoryConnectorInitializationError(MemoryConnectorException): + pass + + +class MemoryConnectorResourceNotFound(MemoryConnectorException): + pass + + +__all__ = [ + "MemoryConnectorException", + "MemoryConnectorInitializationError", + "MemoryConnectorResourceNotFound", +] diff --git a/python/semantic_kernel/utils/settings.py b/python/semantic_kernel/utils/settings.py deleted file mode 100644 index 63f3c0d933..0000000000 --- a/python/semantic_kernel/utils/settings.py +++ /dev/null @@ -1,377 +0,0 @@ -# Copyright (c) Microsoft. All rights reserved. - -from __future__ import annotations - -from typing import Optional, Tuple, Union - -from dotenv import dotenv_values - - -def openai_settings_from_dot_env() -> Tuple[str, Optional[str]]: - """ - Reads the OpenAI API key and organization ID from the .env file. - - Returns: - Tuple[str, str]: The OpenAI API key, the OpenAI organization ID - """ - - config = dotenv_values(".env") - api_key = config.get("OPENAI_API_KEY", None) - org_id = config.get("OPENAI_ORG_ID", None) - - assert api_key, "OpenAI API key not found in .env file" - - # It's okay if the org ID is not found (not required) - return api_key, org_id - - -def azure_openai_settings_from_dot_env( - include_deployment: bool = True, include_api_version: bool = False -) -> Union[Tuple[str, str, str], Tuple[str, str, str, str]]: - """ - Reads the Azure OpenAI API key and endpoint from the .env file. - - Arguments: - include_deployment {bool} -- Whether to include the deployment name in the return value - include_api_version {bool} -- Whether to include the API version in the return value, - when set to True, this will also make the output a Tuple[str, str, str, str]. - - Returns: - Union[Tuple[str, str, str], Tuple[str, str, str, str]]: The deployment name (or empty), Azure OpenAI API key, - the endpoint and optionally the api version - """ - - deployment, api_key, endpoint, api_version = None, None, None, None - config = dotenv_values(".env") - deployment = config.get("AZURE_OPENAI_DEPLOYMENT_NAME", None) - api_key = config.get("AZURE_OPENAI_API_KEY", None) - endpoint = config.get("AZURE_OPENAI_ENDPOINT", None) - api_version = config.get("AZURE_OPENAI_API_VERSION", None) - - # Azure requires the deployment name, the API key and the endpoint URL. - if include_deployment: - assert deployment is not None, "Azure OpenAI deployment name not found in .env file" - if include_api_version: - assert api_version is not None, "Azure OpenAI API version not found in .env file" - - assert api_key, "Azure OpenAI API key not found in .env file" - assert endpoint, "Azure OpenAI endpoint not found in .env file" - - if include_api_version: - return deployment or "", api_key, endpoint, api_version or "" - return deployment or "", api_key, endpoint - - -def azure_openai_settings_from_dot_env_as_dict( - include_deployment: bool = True, include_api_version: bool = False -) -> dict[str, str]: - """ - Reads the Azure OpenAI API key and endpoint from the .env file. - - Returns: - dict[str, str]: The deployment name (or empty), Azure OpenAI API key, - endpoint and api version (or empty) - """ - ( - deployment_name, - api_key, - endpoint, - api_version, - ) = azure_openai_settings_from_dot_env(include_deployment, include_api_version) - ret = { - "api_key": api_key, - "endpoint": endpoint, - } - if include_deployment: - ret["deployment_name"] = deployment_name - if include_api_version: - ret["api_version"] = api_version - return ret - - -def postgres_settings_from_dot_env() -> str: - """Reads the Postgres connection string from the .env file. - - Returns: - str: The Postgres connection string - """ - connection_string = None - config = dotenv_values(".env") - connection_string = config.get("POSTGRES_CONNECTION_STRING", None) - - assert connection_string, "Postgres connection string not found in .env file" - - return connection_string - - -def pinecone_settings_from_dot_env() -> str: - """ - Reads the Pinecone API key from the .env file. - Returns: - str: The Pinecone API key - """ - - config = dotenv_values(".env") - api_key = config.get("PINECONE_API_KEY", None) - - assert api_key, "Pinecone API key not found in .env file" - - return api_key - - -def astradb_settings_from_dot_env() -> Tuple[str, Optional[str]]: - """ - Reads the Astradb API key and Environment from the .env file. - Returns: - Tuple[str, str]: The Astradb API key, the Astradb Environment - """ - - app_token, db_id, region, keyspace = None, None, None, None - with open(".env", "r") as f: - lines = f.readlines() - - for line in lines: - if line.startswith("ASTRADB_APP_TOKEN"): - parts = line.split("=")[1:] - app_token = "=".join(parts).strip().strip('"') - continue - - if line.startswith("ASTRADB_ID"): - parts = line.split("=")[1:] - db_id = "=".join(parts).strip().strip('"') - continue - - if line.startswith("ASTRADB_REGION"): - parts = line.split("=")[1:] - region = "=".join(parts).strip().strip('"') - continue - - if line.startswith("ASTRADB_KEYSPACE"): - parts = line.split("=")[1:] - keyspace = "=".join(parts).strip().strip('"') - continue - - assert app_token, "Astradb Application token not found in .env file" - assert db_id, "Astradb ID not found in .env file" - assert region, "Astradb Region not found in .env file" - assert keyspace, "Astradb Keyspace name not found in .env file" - - return app_token, db_id, region, keyspace - - -def weaviate_settings_from_dot_env() -> Tuple[Optional[str], str]: - """ - Reads the Weaviate API key and URL from the .env file. - - Returns: - Tuple[str, str]: The Weaviate API key, the Weaviate URL - """ - - config = dotenv_values(".env") - api_key = config.get("WEAVIATE_API_KEY", None) - url = config.get("WEAVIATE_URL", None) - - # API key not needed for local Weaviate deployment, URL still needed - assert url is not None, "Weaviate instance URL not found in .env file" - - return api_key, url - - -def bing_search_settings_from_dot_env() -> str: - """Reads the Bing Search API key from the .env file. - - Returns: - str: The Bing Search API key - """ - - api_key = None - config = dotenv_values(".env") - api_key = config.get("BING_API_KEY", None) - - assert api_key is not None, "Bing Search API key not found in .env file" - - return api_key - - -def mongodb_atlas_settings_from_dot_env() -> str: - """Returns the Atlas MongoDB Connection String from the .env file. - - Returns: - str: MongoDB Connection String URI - """ - - config = dotenv_values(".env") - uri = config.get("MONGODB_ATLAS_CONNECTION_STRING") - assert uri is not None, "MongoDB Connection String not found in .env file" - - return uri - - -def google_palm_settings_from_dot_env() -> str: - """ - Reads the Google PaLM API key from the .env file. - - Returns: - str: The Google PaLM API key - """ - - config = dotenv_values(".env") - api_key = config.get("GOOGLE_PALM_API_KEY", None) - - assert api_key is not None, "Google PaLM API key not found in .env file" - - return api_key - - -def azure_cosmos_db_settings_from_dot_env() -> Tuple[str, str]: - """ - Reads the Azure CosmosDB environment variables for the .env file. - Returns: - dict: The Azure CosmosDB environment variables - """ - config = dotenv_values(".env") - cosmos_api = config.get("AZCOSMOS_API") - cosmos_connstr = config.get("AZCOSMOS_CONNSTR") - - assert cosmos_connstr is not None, "Azure Cosmos Connection String not found in .env file" - - return cosmos_api, cosmos_connstr - - -def redis_settings_from_dot_env() -> str: - """Reads the Redis connection string from the .env file. - - Returns: - str: The Redis connection string - """ - config = dotenv_values(".env") - connection_string = config.get("REDIS_CONNECTION_STRING", None) - - assert connection_string is not None, "Redis connection string not found in .env file" - - return connection_string - - -def azure_aisearch_settings_from_dot_env( - include_index_name=False, -) -> Union[Tuple[str, str], Tuple[str, str, str]]: - """ - Reads the Azure AI Search environment variables for the .env file. - - Returns: - Tuple[str, str]: Azure AI Search API key, the Azure AI Search URL - """ - config = dotenv_values(".env") - api_key = config.get("AZURE_AISEARCH_API_KEY", None) - url = config.get("AZURE_AISEARCH_URL", None) - - assert url is not None, "Azure AI Search URL not found in .env file" - assert api_key is not None, "Azure AI Search API key not found in .env file" - - if not include_index_name: - return api_key, url - else: - index_name = config.get("AZURE_AISEARCH_INDEX_NAME", None) - assert index_name is not None, "Azure AI Search index name not found in .env file" - return api_key, url, index_name - - -def azure_aisearch_settings_from_dot_env_as_dict() -> dict[str, str]: - """ - Reads the Azure AI Search environment variables including index name from the .env file. - - Returns: - dict[str, str]: the Azure AI search environment variables - """ - api_key, url, index_name = azure_aisearch_settings_from_dot_env(include_index_name=True) - return {"authentication": {"type": "api_key", "key": api_key}, "endpoint": url, "index_name": index_name} - - -def azure_key_vault_settings_from_dot_env( - include_client_id: bool = True, include_client_secret: bool = True -) -> Tuple[str, Optional[str], Optional[str]]: - """ - Reads the Azure Key Vault environment variables for the .env file. - - Returns: - Tuple[str, str, str]: Azure Key Vault endpoint, the Azure Key Vault client ID, the Azure Key Vault client secret - """ - config = dotenv_values(".env") - endpoint = config.get("AZURE_KEY_VAULT_ENDPOINT", None) - client_id = config.get("AZURE_KEY_VAULT_CLIENT_ID", None) - client_secret = config.get("AZURE_KEY_VAULT_CLIENT_SECRET", None) - - assert endpoint is not None, "Azure Key Vault endpoint not found in .env file" - if include_client_id: - assert client_id is not None, "Azure Key Vault client ID not found in .env file" - if include_client_secret: - assert client_secret is not None, "Azure Key Vault client secret not found in .env file" - - if include_client_id and include_client_secret: - return endpoint, client_id, client_secret - return endpoint, client_id - - -def azure_key_vault_settings_from_dot_env_as_dict() -> dict[str, str]: - """ - Reads the Azure Key Vault environment variables for the .env file. - - Returns: - dict[str, str]: Azure Key Vault environment variables - """ - endpoint, client_id, client_secret = azure_key_vault_settings_from_dot_env() - return {"endpoint": endpoint, "client_id": client_id, "client_secret": client_secret} - - -def booking_sample_settings_from_dot_env() -> Tuple[str, str, str]: - """ - Reads the Booking Sample environment variables for the .env file. - - Returns: - Tuple[str, str]: Booking Sample environment variables - """ - config = dotenv_values(".env") - client_id = config.get("BOOKING_SAMPLE_CLIENT_ID", None) - tenant_id = config.get("BOOKING_SAMPLE_TENANT_ID", None) - client_secret = config.get("BOOKING_SAMPLE_CLIENT_SECRET", None) - - assert client_id, "Booking Sample Client ID not found in .env file" - assert tenant_id, "Booking Sample Tenant ID not found in .env file" - assert client_secret, "Booking Sample Client Secret not found in .env file" - - return client_id, tenant_id, client_secret - - -def booking_sample_settings_from_dot_env_as_dict() -> dict[str, str]: - """ - Reads the Booking Sample environment variables for the .env file. - - Returns: - dict[str, str]: Booking Sample environment variables - """ - client_id, tenant_id, client_secret = booking_sample_settings_from_dot_env() - return {"client_id": client_id, "tenant_id": tenant_id, "client_secret": client_secret} - - -def azure_container_apps_settings_from_dot_env() -> str: - """ - Reads the Azure Container Apps environment variables from the .env file. - Returns: - str: Azure Container Apps pool management connection string - """ - config = dotenv_values(".env") - connection_string = config.get("ACA_POOL_MANAGEMENT_ENDPOINT", None) - - assert connection_string is not None, "Azure Container Apps connection string not found in .env file" - - return connection_string - - -def azure_container_apps_settings_from_dot_env_as_dict() -> dict[str, str]: - """ - Reads the Azure Container Apps environment variables from the .env file. - Returns: - Dict[str, str]: Azure Container Apps environment variables - """ - pool_management_endpoint = azure_container_apps_settings_from_dot_env() - return {"pool_management_endpoint": pool_management_endpoint} diff --git a/python/tests/conftest.py b/python/tests/conftest.py index 34d1b4557c..10a3e66dab 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -2,7 +2,6 @@ from __future__ import annotations -import os import warnings from typing import TYPE_CHECKING, Callable, List from unittest.mock import Mock @@ -174,44 +173,148 @@ def enable_debug_mode(): builtins.pr = snoop.pp -@pytest.fixture(scope="session") -def get_aoai_config(): - from semantic_kernel.utils.settings import azure_openai_settings_from_dot_env - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIEmbeddings__DeploymentName"] - api_key = os.environ["AzureOpenAI_EastUS__ApiKey"] - endpoint = os.environ["AzureOpenAI_EastUS__Endpoint"] - else: - # Load credentials from .env file - deployment_name, api_key, endpoint = azure_openai_settings_from_dot_env() - deployment_name = "text-embedding-ada-002" - - return deployment_name, api_key, endpoint +@pytest.fixture +def exclude_list(request): + """Fixture that returns a list of environment variables to exclude.""" + return request.param if hasattr(request, "param") else [] -@pytest.fixture(scope="session") -def get_oai_config(): - from semantic_kernel.utils.settings import openai_settings_from_dot_env - - if "Python_Integration_Tests" in os.environ: - api_key = os.environ["OpenAI__ApiKey"] - org_id = None - else: - # Load credentials from .env file - api_key, org_id = openai_settings_from_dot_env() - - return api_key, org_id +@pytest.fixture +def override_env_param_dict(request): + """Fixture that returns a dict of environment variables to override.""" + return request.param if hasattr(request, "param") else {} -@pytest.fixture(scope="session") -def get_gp_config(): - from semantic_kernel.utils.settings import google_palm_settings_from_dot_env +@pytest.fixture() +def azure_openai_unit_test_env(monkeypatch, exclude_list, override_env_param_dict): + """Fixture to set environment variables for AzureOpenAISettings.""" + if exclude_list is None: + exclude_list = [] - if "Python_Integration_Tests" in os.environ: - api_key = os.environ["GOOGLE_PALM_API_KEY"] - else: - # Load credentials from .env file - api_key = google_palm_settings_from_dot_env() + if override_env_param_dict is None: + override_env_param_dict = {} - return api_key + env_vars = { + "AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "test_chat_deployment", + "AZURE_OPENAI_TEXT_DEPLOYMENT_NAME": "test_text_deployment", + "AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME": "test_embedding_deployment", + "AZURE_OPENAI_API_KEY": "test_api_key", + "AZURE_OPENAI_ENDPOINT": "https://test-endpoint.com", + "AZURE_OPENAI_API_VERSION": "2023-03-15-preview", + "AZURE_OPENAI_BASE_URL": "https://test_text_deployment.test-base-url.com", + } + + env_vars.update(override_env_param_dict) + + for key, value in env_vars.items(): + if key not in exclude_list: + monkeypatch.setenv(key, value) + else: + monkeypatch.delenv(key, raising=False) + + return env_vars + + +@pytest.fixture() +def openai_unit_test_env(monkeypatch, exclude_list, override_env_param_dict): + """Fixture to set environment variables for OpenAISettings.""" + if exclude_list is None: + exclude_list = [] + + if override_env_param_dict is None: + override_env_param_dict = {} + + env_vars = { + "OPENAI_API_KEY": "test_api_key", + "OPENAI_ORG_ID": "test_org_id", + "OPENAI_CHAT_MODEL_ID": "test_chat_model_id", + "OPENAI_TEXT_MODEL_ID": "test_text_model_id", + "OPENAI_EMBEDDING_MODEL_ID": "test_embedding_model_id", + } + + env_vars.update(override_env_param_dict) + + for key, value in env_vars.items(): + if key not in exclude_list: + monkeypatch.setenv(key, value) + else: + monkeypatch.delenv(key, raising=False) + + return env_vars + + +@pytest.fixture() +def google_palm_unit_test_env(monkeypatch, exclude_list, override_env_param_dict): + """Fixture to set environment variables for Google Palm.""" + if exclude_list is None: + exclude_list = [] + + if override_env_param_dict is None: + override_env_param_dict = {} + + env_vars = { + "GOOGLE_PALM_API_KEY": "test_api_key", + "OPENAI_CHAT_MODEL_ID": "test_chat_model_id", + "OPENAI_TEXT_MODEL_ID": "test_text_model_id", + "OPENAI_EMBEDDING_MODEL_ID": "test_embedding_model_id", + } + + env_vars.update(override_env_param_dict) + + for key, value in env_vars.items(): + if key not in exclude_list: + monkeypatch.setenv(key, value) + else: + monkeypatch.delenv(key, raising=False) + + return env_vars + + +@pytest.fixture() +def aca_python_sessions_unit_test_env(monkeypatch, exclude_list, override_env_param_dict): + """Fixture to set environment variables for ACA Python Unit Tests.""" + if exclude_list is None: + exclude_list = [] + + if override_env_param_dict is None: + override_env_param_dict = {} + + env_vars = { + "ACA_POOL_MANAGEMENT_ENDPOINT": "https://test.endpoint/python/excute/", + } + + env_vars.update(override_env_param_dict) + + for key, value in env_vars.items(): + if key not in exclude_list: + monkeypatch.setenv(key, value) + else: + monkeypatch.delenv(key, raising=False) + + return env_vars + + +@pytest.fixture() +def azure_ai_search_unit_test_env(monkeypatch, exclude_list, override_env_param_dict): + """Fixture to set environment variables for ACA Python Unit Tests.""" + if exclude_list is None: + exclude_list = [] + + if override_env_param_dict is None: + override_env_param_dict = {} + + env_vars = { + "AZURE_AI_SEARCH_API_KEY": "test-api-key", + "AZURE_AI_SEARCH_ENDPOINT": "https://test-endpoint.com", + "AZURE_AI_SEARCH_INDEX_NAME": "test-index-name", + } + + env_vars.update(override_env_param_dict) + + for key, value in env_vars.items(): + if key not in exclude_list: + monkeypatch.setenv(key, value) + else: + monkeypatch.delenv(key, raising=False) + + return env_vars diff --git a/python/tests/integration/completions/conftest.py b/python/tests/integration/completions/conftest.py index 129aeffbcd..9d775ac11a 100644 --- a/python/tests/integration/completions/conftest.py +++ b/python/tests/integration/completions/conftest.py @@ -84,10 +84,9 @@ def setup_summarize_conversation_using_plugin(kernel: Kernel): @pytest.fixture(scope="function") -def setup_gp_text_completion_function(kernel: Kernel, get_gp_config): - api_key = get_gp_config +def setup_gp_text_completion_function(kernel: Kernel): # Configure LLM service - palm_text_completion = sk_gp.GooglePalmTextCompletion(ai_model_id="models/text-bison-001", api_key=api_key) + palm_text_completion = sk_gp.GooglePalmTextCompletion(ai_model_id="models/text-bison-001") kernel.add_service(palm_text_completion) # Define semantic function using SK prompt template language diff --git a/python/tests/integration/completions/test_azure_oai_chat_service.py b/python/tests/integration/completions/test_azure_oai_chat_service.py index afe660b1d4..e98af4853d 100644 --- a/python/tests/integration/completions/test_azure_oai_chat_service.py +++ b/python/tests/integration/completions/test_azure_oai_chat_service.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -import os import pytest from openai import AsyncAzureOpenAI @@ -11,6 +10,7 @@ from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBeh from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import ( AzureChatPromptExecutionSettings, ) +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.contents.streaming_chat_message_content import StreamingChatMessageContent from semantic_kernel.core_plugins.math_plugin import MathPlugin @@ -20,24 +20,13 @@ from semantic_kernel.prompt_template.prompt_template_config import PromptTemplat @pytest.mark.asyncio -async def test_azure_e2e_chat_completion_with_plugin(setup_tldr_function_for_oai_models, get_aoai_config): +async def test_azure_e2e_chat_completion_with_plugin(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") - # Configure LLM service kernel.add_service( sk_oai.AzureChatCompletion( - service_id="chat", deployment_name=deployment_name, endpoint=endpoint, api_key=api_key + service_id="chat", ), ) @@ -62,27 +51,20 @@ async def test_azure_e2e_chat_completion_with_plugin(setup_tldr_function_for_oai @pytest.mark.asyncio -async def test_azure_e2e_chat_completion_with_plugin_and_provided_client( - setup_tldr_function_for_oai_models, get_aoai_config -): +async def test_azure_e2e_chat_completion_with_plugin_and_provided_client(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") + azure_openai_settings = AzureOpenAISettings.create() + endpoint = azure_openai_settings.endpoint + deployment_name = azure_openai_settings.chat_deployment_name + api_key = azure_openai_settings.api_key.get_secret_value() + api_version = azure_openai_settings.api_version client = AsyncAzureOpenAI( azure_endpoint=endpoint, azure_deployment=deployment_name, api_key=api_key, - api_version="2023-05-15", + api_version=api_version, default_headers={"Test-User-X-ID": "test"}, ) @@ -90,7 +72,6 @@ async def test_azure_e2e_chat_completion_with_plugin_and_provided_client( kernel.add_service( sk_oai.AzureChatCompletion( service_id="chat_completion", - deployment_name=deployment_name, async_client=client, ), ) @@ -116,23 +97,18 @@ async def test_azure_e2e_chat_completion_with_plugin_and_provided_client( @pytest.mark.asyncio -async def test_azure_oai_chat_service_with_tool_call(kernel: Kernel, get_aoai_config): - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo-0613" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") +async def test_azure_oai_chat_service_with_tool_call(kernel: Kernel): + azure_openai_settings = AzureOpenAISettings.create() + endpoint = azure_openai_settings.endpoint + deployment_name = azure_openai_settings.chat_deployment_name + api_key = azure_openai_settings.api_key.get_secret_value() + api_version = azure_openai_settings.api_version client = AsyncAzureOpenAI( azure_endpoint=endpoint, azure_deployment=deployment_name, api_key=api_key, - api_version="2023-05-15", + api_version=api_version, default_headers={"Test-User-X-ID": "test"}, ) @@ -140,7 +116,6 @@ async def test_azure_oai_chat_service_with_tool_call(kernel: Kernel, get_aoai_co kernel.add_service( sk_oai.AzureChatCompletion( service_id="chat_completion", - deployment_name=deployment_name, async_client=client, ), ) @@ -176,23 +151,18 @@ async def test_azure_oai_chat_service_with_tool_call(kernel: Kernel, get_aoai_co @pytest.mark.asyncio -async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, get_aoai_config): - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo-0613" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") +async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel): + azure_openai_settings = AzureOpenAISettings.create() + endpoint = azure_openai_settings.endpoint + deployment_name = azure_openai_settings.chat_deployment_name + api_key = azure_openai_settings.api_key.get_secret_value() + api_version = azure_openai_settings.api_version client = AsyncAzureOpenAI( azure_endpoint=endpoint, azure_deployment=deployment_name, api_key=api_key, - api_version="2024-02-01", + api_version=api_version, default_headers={"Test-User-X-ID": "test"}, ) @@ -200,7 +170,6 @@ async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, g kernel.add_service( sk_oai.AzureChatCompletion( service_id="chat_completion", - deployment_name=deployment_name, async_client=client, ), ) @@ -208,7 +177,7 @@ async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, g kernel.add_plugin(MathPlugin(), plugin_name="Math") # Create the prompt function - kernel.add_function(prompt="{{$input}}", function_name="chat", plugin_name="chat") + kernel.add_function(prompt="Keep the answer short. {{$input}}", function_name="chat", plugin_name="chat") execution_settings = sk_oai.AzureChatPromptExecutionSettings( service_id="chat_completion", max_tokens=2000, @@ -227,4 +196,4 @@ async def test_azure_oai_chat_service_with_tool_call_streaming(kernel: Kernel, g print(f"Math output: '{output}'") assert "2" in output - assert 0 < len(output) < 100 + assert 0 < len(output) < 500 diff --git a/python/tests/integration/completions/test_azure_oai_chat_service_extensions.py b/python/tests/integration/completions/test_azure_oai_chat_service_extensions.py index c240985a95..e6087f585c 100644 --- a/python/tests/integration/completions/test_azure_oai_chat_service_extensions.py +++ b/python/tests/integration/completions/test_azure_oai_chat_service_extensions.py @@ -77,20 +77,9 @@ of climate change.", @pytest.fixture(scope="function") @pytest.mark.asyncio -async def create_with_data_chat_function(get_aoai_config, kernel: Kernel, create_memory_store): +async def create_with_data_chat_function(kernel: Kernel, create_memory_store): collection, memory_store = await create_memory_store try: - deployment_name, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") - # Load Azure OpenAI with data settings search_endpoint = os.getenv("AZURE_COGNITIVE_SEARCH_ENDPOINT") search_api_key = os.getenv("AZURE_COGNITIVE_SEARCH_ADMIN_KEY") @@ -112,13 +101,8 @@ async def create_with_data_chat_function(get_aoai_config, kernel: Kernel, create ) ] ) - print(f"deployment: {deployment_name}, endpoint: {endpoint}") chat_service = sk_oai.AzureChatCompletion( service_id="chat-gpt-extensions", - deployment_name=deployment_name, - api_key=api_key, - endpoint=endpoint, - api_version="2024-02-01", ) kernel.add_service(chat_service) diff --git a/python/tests/integration/completions/test_azure_oai_text_service.py b/python/tests/integration/completions/test_azure_oai_text_service.py index 30c8b501aa..dbc9e40dea 100644 --- a/python/tests/integration/completions/test_azure_oai_text_service.py +++ b/python/tests/integration/completions/test_azure_oai_text_service.py @@ -1,44 +1,32 @@ # Copyright (c) Microsoft. All rights reserved. -import os import pytest from openai import AsyncAzureOpenAI from test_utils import retry import semantic_kernel.connectors.ai.open_ai as sk_oai +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig @pytest.mark.asyncio -async def test_azure_e2e_text_completion_with_plugin(setup_tldr_function_for_oai_models, get_aoai_config): +async def test_azure_e2e_text_completion_with_plugin(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAI__Text__DeploymentName"] - else: - deployment_name = "gpt-35-turbo-instruct" - - print("* Service: Azure OpenAI Text Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") + service_id = "text_completion" # Configure LLM service kernel.add_service( sk_oai.AzureTextCompletion( - service_id="text_completion", - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, + service_id=service_id, ), ) exec_settings = PromptExecutionSettings( - service_id="text_completion", extension_data={"max_tokens": 200, "temperature": 0, "top_p": 0.5} + service_id=service_id, extension_data={"max_tokens": 200, "temperature": 0, "top_p": 0.5} ) prompt_template_config = PromptTemplateConfig( @@ -59,42 +47,36 @@ async def test_azure_e2e_text_completion_with_plugin(setup_tldr_function_for_oai @pytest.mark.asyncio -async def test_azure_e2e_text_completion_with_plugin_with_provided_client( - setup_tldr_function_for_oai_models, get_aoai_config -): +async def test_azure_e2e_text_completion_with_plugin_with_provided_client(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAI__Text__DeploymentName"] - else: - deployment_name = "gpt-35-turbo-instruct" - - print("* Service: Azure OpenAI Text Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") + azure_openai_settings = AzureOpenAISettings.create() + endpoint = azure_openai_settings.endpoint + deployment_name = azure_openai_settings.chat_deployment_name + api_key = azure_openai_settings.api_key.get_secret_value() + api_version = azure_openai_settings.api_version client = AsyncAzureOpenAI( azure_endpoint=endpoint, azure_deployment=deployment_name, api_key=api_key, - api_version="2023-05-15", + api_version=api_version, default_headers={"Test-User-X-ID": "test"}, ) + service_id = "text_completion" + # Configure LLM service kernel.add_service( sk_oai.AzureTextCompletion( - service_id="text_completion", - deployment_name=deployment_name, + service_id=service_id, async_client=client, ), overwrite=True, # Overwrite the service for the test if it already exists ) exec_settings = PromptExecutionSettings( - service_id="text_completion", extension_data={"max_tokens": 200, "temperature": 0, "top_p": 0.5} + service_id=service_id, extension_data={"max_tokens": 200, "temperature": 0, "top_p": 0.5} ) prompt_template_config = PromptTemplateConfig( @@ -111,4 +93,4 @@ async def test_azure_e2e_text_completion_with_plugin_with_provided_client( summary = await retry(lambda: kernel.invoke(tldr_function, arguments)) output = str(summary).strip() print(f"TLDR using input string: '{output}'") - assert len(output) < 100 + assert len(output) > 0 diff --git a/python/tests/integration/completions/test_conversation_summary_plugin.py b/python/tests/integration/completions/test_conversation_summary_plugin.py index f4b58cc409..c6fbd0448f 100644 --- a/python/tests/integration/completions/test_conversation_summary_plugin.py +++ b/python/tests/integration/completions/test_conversation_summary_plugin.py @@ -1,6 +1,5 @@ # Copyright (c) Microsoft. All rights reserved. -import os import pytest from test_utils import retry @@ -12,22 +11,12 @@ from semantic_kernel.core_plugins.conversation_summary_plugin import ( ) from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig -from semantic_kernel.utils.settings import openai_settings_from_dot_env @pytest.mark.asyncio -async def test_azure_summarize_conversation_using_plugin(setup_summarize_conversation_using_plugin, get_aoai_config): +async def test_azure_summarize_conversation_using_plugin(setup_summarize_conversation_using_plugin): kernel, chatTranscript = setup_summarize_conversation_using_plugin - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAI__DeploymentName"] - api_key = os.environ["AzureOpenAI__ApiKey"] - endpoint = os.environ["AzureOpenAI__Endpoint"] - else: - # Load credentials from .env file - deployment_name, api_key, endpoint = get_aoai_config - deployment_name = "gpt-35-turbo-instruct" - service_id = "text_completion" execution_settings = PromptExecutionSettings( @@ -41,7 +30,7 @@ async def test_azure_summarize_conversation_using_plugin(setup_summarize_convers kernel.add_service( sk_oai.AzureTextCompletion( - service_id=service_id, deployment_name=deployment_name, endpoint=endpoint, api_key=api_key + service_id=service_id, ), ) @@ -65,13 +54,6 @@ async def test_oai_summarize_conversation_using_plugin( ): kernel, chatTranscript = setup_summarize_conversation_using_plugin - if "Python_Integration_Tests" in os.environ: - api_key = os.environ["OpenAI__ApiKey"] - org_id = None - else: - # Load credentials from .env file - api_key, org_id = openai_settings_from_dot_env() - execution_settings = PromptExecutionSettings( service_id="conversation_summary", max_tokens=ConversationSummaryPlugin._max_tokens, temperature=0.1, top_p=0.5 ) @@ -83,7 +65,8 @@ async def test_oai_summarize_conversation_using_plugin( kernel.add_service( sk_oai.OpenAITextCompletion( - service_id="conversation_summary", ai_model_id="gpt-3.5-turbo-instruct", api_key=api_key, org_id=org_id + service_id="conversation_summary", + ai_model_id="gpt-3.5-turbo-instruct", ), ) diff --git a/python/tests/integration/completions/test_gp_chat_service.py b/python/tests/integration/completions/test_gp_chat_service.py index 061897f274..a337d675b6 100644 --- a/python/tests/integration/completions/test_gp_chat_service.py +++ b/python/tests/integration/completions/test_gp_chat_service.py @@ -23,14 +23,13 @@ pytestmark = [ @pytest.mark.asyncio -async def test_gp_chat_service_with_plugins(setup_tldr_function_for_oai_models, get_gp_config): +async def test_gp_chat_service_with_plugins(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - api_key = get_gp_config print("* Service: Google PaLM Chat Completion") print("* Model: chat-bison-001") model_id = "models/chat-bison-001" - palm_chat_completion = sk_gp.GooglePalmChatCompletion(ai_model_id=model_id, api_key=api_key) + palm_chat_completion = sk_gp.GooglePalmChatCompletion(ai_model_id=model_id) kernel.add_service(palm_chat_completion) exec_settings = PromptExecutionSettings( @@ -49,5 +48,4 @@ async def test_gp_chat_service_with_plugins(setup_tldr_function_for_oai_models, summary = await retry(lambda: kernel.invoke(tldr_function, arguments)) output = str(summary).strip() print(f"TLDR using input string: '{output}'") - # assert "First Law" not in output and ("human" in output or "Human" in output or "preserve" in output) assert len(output) > 0 diff --git a/python/tests/integration/completions/test_oai_chat_service.py b/python/tests/integration/completions/test_oai_chat_service.py index e7e758acff..edd2d7ba32 100644 --- a/python/tests/integration/completions/test_oai_chat_service.py +++ b/python/tests/integration/completions/test_oai_chat_service.py @@ -7,6 +7,7 @@ from test_utils import retry import semantic_kernel.connectors.ai.open_ai as sk_oai from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.contents.chat_history import ChatHistory from semantic_kernel.core_plugins.math_plugin import MathPlugin @@ -14,17 +15,11 @@ from semantic_kernel.prompt_template.prompt_template_config import PromptTemplat @pytest.mark.asyncio -async def test_oai_chat_service_with_plugins(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_plugins(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo") - kernel.add_service( - sk_oai.OpenAIChatCompletion(service_id="chat-gpt", ai_model_id="gpt-3.5-turbo", api_key=api_key, org_id=org_id), + sk_oai.OpenAIChatCompletion(service_id="chat-gpt", ai_model_id="gpt-3.5-turbo"), ) exec_settings = PromptExecutionSettings( @@ -48,18 +43,13 @@ async def test_oai_chat_service_with_plugins(setup_tldr_function_for_oai_models, @pytest.mark.asyncio -async def test_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_models): kernel, _, _ = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo-1106") - kernel.add_service( sk_oai.OpenAIChatCompletion( - service_id="chat-gpt", ai_model_id="gpt-3.5-turbo-1106", api_key=api_key, org_id=org_id + service_id="chat-gpt", + ai_model_id="gpt-3.5-turbo-1106", ), ) @@ -92,18 +82,13 @@ async def test_oai_chat_service_with_tool_call(setup_tldr_function_for_oai_model @pytest.mark.asyncio -async def test_oai_chat_service_with_tool_call_streaming(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_tool_call_streaming(setup_tldr_function_for_oai_models): kernel, _, _ = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo-1106") - kernel.add_service( sk_oai.OpenAIChatCompletion( - service_id="chat-gpt", ai_model_id="gpt-3.5-turbo-1106", api_key=api_key, org_id=org_id + service_id="chat-gpt", + ai_model_id="gpt-3.5-turbo-1106", ), ) @@ -139,14 +124,12 @@ async def test_oai_chat_service_with_tool_call_streaming(setup_tldr_function_for @pytest.mark.asyncio -async def test_oai_chat_service_with_plugins_with_provided_client(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_plugins_with_provided_client(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo") + openai_settings = OpenAISettings.create() + api_key = openai_settings.api_key.get_secret_value() + org_id = openai_settings.org_id client = AsyncOpenAI( api_key=api_key, @@ -185,24 +168,13 @@ async def test_oai_chat_service_with_plugins_with_provided_client(setup_tldr_fun @pytest.mark.asyncio -async def test_oai_chat_stream_service_with_plugins(setup_tldr_function_for_oai_models, get_aoai_config): +async def test_azure_oai_chat_stream_service_with_plugins(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIChat__DeploymentName"] - else: - deployment_name = "gpt-35-turbo" - - print("* Service: Azure OpenAI Chat Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") - # Configure LLM service kernel.add_service( sk_oai.AzureChatCompletion( - service_id="chat_completion", deployment_name=deployment_name, endpoint=endpoint, api_key=api_key + service_id="chat_completion", ), overwrite=True, ) @@ -233,14 +205,12 @@ async def test_oai_chat_stream_service_with_plugins(setup_tldr_function_for_oai_ @pytest.mark.asyncio -async def test_oai_chat_service_with_yaml_jinja2(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_yaml_jinja2(setup_tldr_function_for_oai_models): kernel, _, _ = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo") + openai_settings = OpenAISettings.create() + api_key = openai_settings.api_key.get_secret_value() + org_id = openai_settings.org_id client = AsyncOpenAI( api_key=api_key, @@ -272,14 +242,12 @@ async def test_oai_chat_service_with_yaml_jinja2(setup_tldr_function_for_oai_mod @pytest.mark.asyncio -async def test_oai_chat_service_with_yaml_handlebars(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_chat_service_with_yaml_handlebars(setup_tldr_function_for_oai_models): kernel, _, _ = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Chat Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo") + openai_settings = OpenAISettings.create() + api_key = openai_settings.api_key.get_secret_value() + org_id = openai_settings.org_id client = AsyncOpenAI( api_key=api_key, diff --git a/python/tests/integration/completions/test_oai_text_service.py b/python/tests/integration/completions/test_oai_text_service.py index 8de1fad490..0c2df6baad 100644 --- a/python/tests/integration/completions/test_oai_text_service.py +++ b/python/tests/integration/completions/test_oai_text_service.py @@ -1,30 +1,22 @@ # Copyright (c) Microsoft. All rights reserved. -import os import pytest from openai import AsyncOpenAI from test_utils import retry import semantic_kernel.connectors.ai.open_ai as sk_oai +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings from semantic_kernel.functions.kernel_arguments import KernelArguments from semantic_kernel.prompt_template.prompt_template_config import PromptTemplateConfig @pytest.mark.asyncio -async def test_oai_text_completion_with_plugins(setup_tldr_function_for_oai_models, get_oai_config): +async def test_oai_text_completion_with_plugins(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Text Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo-instruct") - kernel.add_service( - sk_oai.OpenAITextCompletion( - service_id="text-completion", ai_model_id="gpt-3.5-turbo-instruct", api_key=api_key, org_id=org_id - ), + sk_oai.OpenAITextCompletion(service_id="text-completion", ai_model_id="gpt-3.5-turbo-instruct"), ) exec_settings = PromptExecutionSettings( @@ -50,16 +42,12 @@ async def test_oai_text_completion_with_plugins(setup_tldr_function_for_oai_mode @pytest.mark.asyncio -async def test_oai_text_completion_with_plugins_with_provided_client( - setup_tldr_function_for_oai_models, get_oai_config -): +async def test_oai_text_completion_with_plugins_with_provided_client(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - api_key, org_id = get_oai_config - - print("* Service: OpenAI Text Completion") - print("* Endpoint: OpenAI") - print("* Model: gpt-3.5-turbo-instruct") + openai_settings = OpenAISettings.create() + api_key = openai_settings.api_key.get_secret_value() + org_id = openai_settings.org_id client = AsyncOpenAI( api_key=api_key, @@ -100,27 +88,13 @@ async def test_oai_text_completion_with_plugins_with_provided_client( @pytest.mark.asyncio -async def test_oai_text_stream_completion_with_plugins(setup_tldr_function_for_oai_models, get_aoai_config): +async def test_azure_oai_text_stream_completion_with_plugins(setup_tldr_function_for_oai_models): kernel, prompt, text_to_summarize = setup_tldr_function_for_oai_models - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAI__DeploymentName"] - else: - deployment_name = "gpt-35-turbo-instruct" - - print("* Service: Azure OpenAI Text Completion") - print(f"* Endpoint: {endpoint}") - print(f"* Deployment: {deployment_name}") - # Configure LLM service kernel.add_service( sk_oai.AzureTextCompletion( service_id="text_completion", - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, ), ) diff --git a/python/tests/integration/connectors/memory/test_astradb.py b/python/tests/integration/connectors/memory/test_astradb.py index b01b90bc26..01b742fa82 100644 --- a/python/tests/integration/connectors/memory/test_astradb.py +++ b/python/tests/integration/connectors/memory/test_astradb.py @@ -4,9 +4,10 @@ import os import time import pytest +from pydantic import ValidationError from semantic_kernel.connectors.memory.astradb import AstraDBMemoryStore -from semantic_kernel.utils.settings import astradb_settings_from_dot_env +from semantic_kernel.connectors.memory.astradb.astradb_settings import AstraDBSettings astradb_installed: bool try: @@ -36,16 +37,15 @@ def slow_down_tests(): @pytest.fixture(scope="session") def get_astradb_config(): - if "Python_Integration_Tests" in os.environ: - app_token = os.environ["ASTRADB_APP_TOKEN"] - db_id = os.environ["ASTRADB_ID"] - region = os.environ["ASTRADB_REGION"] - keyspace = os.environ["ASTRADB_KEYSPACE"] - else: - # Load credentials from .env file - app_token, db_id, region, keyspace = astradb_settings_from_dot_env() - - return app_token, db_id, region, keyspace + try: + astradb_settings = AstraDBSettings() + app_token = astradb_settings.app_token.get_secret_value() + db_id = astradb_settings.db_id + region = astradb_settings.region + keyspace = astradb_settings.keyspace + return app_token, db_id, region, keyspace + except ValidationError: + pytest.skip("AsbtraDBSettings not found in env vars.") @pytest.mark.asyncio diff --git a/python/tests/integration/connectors/memory/test_azure_cognitive_search.py b/python/tests/integration/connectors/memory/test_azure_cognitive_search.py index 703159019a..ac3da61389 100644 --- a/python/tests/integration/connectors/memory/test_azure_cognitive_search.py +++ b/python/tests/integration/connectors/memory/test_azure_cognitive_search.py @@ -10,7 +10,7 @@ import pytest from semantic_kernel.connectors.memory.azure_cognitive_search.azure_cognitive_search_memory_store import ( AzureCognitiveSearchMemoryStore, ) -from semantic_kernel.exceptions import ServiceResourceNotFoundError +from semantic_kernel.exceptions import MemoryConnectorResourceNotFound from semantic_kernel.memory.memory_record import MemoryRecord try: @@ -117,7 +117,7 @@ async def test_record_not_found(): # Clean up and fail await memory_store.delete_collection(collection) assert False - except ServiceResourceNotFoundError: + except MemoryConnectorResourceNotFound: pass await memory_store.delete_collection(collection) diff --git a/python/tests/integration/connectors/memory/test_mongodb_atlas.py b/python/tests/integration/connectors/memory/test_mongodb_atlas.py index e4def4f719..8d45666de3 100644 --- a/python/tests/integration/connectors/memory/test_mongodb_atlas.py +++ b/python/tests/integration/connectors/memory/test_mongodb_atlas.py @@ -1,16 +1,19 @@ # Copyright (c) Microsoft. All rights reserved. -import os import random import time import numpy as np import pytest import pytest_asyncio +from pydantic import ValidationError from pymongo import errors from semantic_kernel.connectors.memory.mongodb_atlas.mongodb_atlas_memory_store import ( MongoDBAtlasMemoryStore, ) +from semantic_kernel.connectors.memory.mongodb_atlas.mongodb_atlas_settings import ( + MongoDBAtlasSettings, +) from semantic_kernel.memory.memory_record import MemoryRecord mongodb_atlas_installed: bool @@ -64,11 +67,18 @@ def test_collection(): return f"AVSTest-{random.randint(0,9999)}" +@pytest.fixture(scope="session") +def connection_string(): + try: + mongodb_atlas_settings = MongoDBAtlasSettings.create() + return mongodb_atlas_settings.api_key.get_secret_value() + except ValidationError: + pytest.skip("MongoDB Atlas connection string not found in env vars.") + + @pytest_asyncio.fixture async def vector_search_store(): - if "Python_Integration_Tests" in os.environ: - connection_string = os.environ["MONGODB_ATLAS_CONNECTION_STRING"] - async with MongoDBAtlasMemoryStore(connection_string=connection_string, database_name="pyMSKTest") as memory: + async with MongoDBAtlasMemoryStore(connection_string, database_name="pyMSKTest") as memory: # Delete all collections before and after for cname in await memory.get_collections(): await memory.delete_collection(cname) @@ -105,9 +115,7 @@ async def vector_search_store(): @pytest_asyncio.fixture async def nearest_match_store(): """Fixture for read only vector store; the URI for test needs atlas configured""" - if "Python_Integration_Tests" in os.environ: - connection_string = os.environ["MONGODB_ATLAS_CONNECTION_STRING"] - async with MongoDBAtlasMemoryStore(connection_string=connection_string, database_name="pyMSKTest") as memory: + async with MongoDBAtlasMemoryStore(connection_string, database_name="pyMSKTest") as memory: if not await memory.does_collection_exist("nearestSearch"): pytest.skip( reason="db: readOnly collection: nearestSearch not found, " diff --git a/python/tests/integration/connectors/memory/test_pinecone.py b/python/tests/integration/connectors/memory/test_pinecone.py index aaca0d9b70..d9b3603213 100644 --- a/python/tests/integration/connectors/memory/test_pinecone.py +++ b/python/tests/integration/connectors/memory/test_pinecone.py @@ -1,16 +1,16 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio -import os import time import numpy as np import pytest +from pydantic import ValidationError from semantic_kernel.connectors.memory.pinecone import PineconeMemoryStore +from semantic_kernel.connectors.memory.pinecone.pinecone_settings import PineconeSettings from semantic_kernel.exceptions.service_exceptions import ServiceResourceNotFoundError from semantic_kernel.memory.memory_record import MemoryRecord -from semantic_kernel.utils.settings import pinecone_settings_from_dot_env try: import pinecone # noqa: F401 @@ -43,13 +43,11 @@ def slow_down_tests(): @pytest.fixture(scope="session") def api_key(): - if "Python_Integration_Tests" in os.environ: - api_key = os.environ["Pinecone__ApiKey"] - else: - # Load credentials from .env file - api_key = pinecone_settings_from_dot_env() - - return api_key + try: + pinecone_settings = PineconeSettings.create() + return pinecone_settings.api_key.get_secret_value() + except ValidationError: + pytest.skip("Pinecone API key not found in env vars.") @pytest.fixture diff --git a/python/tests/integration/connectors/memory/test_postgres.py b/python/tests/integration/connectors/memory/test_postgres.py index 201ddb91cb..738d2a87c5 100644 --- a/python/tests/integration/connectors/memory/test_postgres.py +++ b/python/tests/integration/connectors/memory/test_postgres.py @@ -1,12 +1,12 @@ # Copyright (c) Microsoft. All rights reserved. -import os import time import pytest +from pydantic import ValidationError -import semantic_kernel as sk from semantic_kernel.connectors.memory.postgres import PostgresMemoryStore +from semantic_kernel.connectors.memory.postgres.postgres_settings import PostgresSettings from semantic_kernel.exceptions import ServiceResourceNotFoundError try: @@ -37,13 +37,11 @@ def wait_between_tests(): @pytest.fixture(scope="session") def connection_string(): - if "Python_Integration_Tests" in os.environ: - connection_string = os.environ["Postgres__Connectionstr"] - else: - # Load credentials from .env file - connection_string = sk.postgres_settings_from_dot_env() - - return connection_string + try: + postgres_settings = PostgresSettings.create() + return postgres_settings.connection_string.get_secret_value() + except ValidationError: + pytest.skip("Postgres Connection string not found in env vars.") def test_constructor(connection_string): diff --git a/python/tests/integration/connectors/memory/test_redis.py b/python/tests/integration/connectors/memory/test_redis.py index e17b4b6b21..83f6684d5e 100644 --- a/python/tests/integration/connectors/memory/test_redis.py +++ b/python/tests/integration/connectors/memory/test_redis.py @@ -1,13 +1,12 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio -import os import platform import pytest -import semantic_kernel as sk from semantic_kernel.connectors.memory.redis import RedisMemoryStore +from semantic_kernel.connectors.memory.redis.redis_settings import RedisSettings try: import redis # noqa: F401 @@ -21,7 +20,7 @@ except ImportError: pytestmark = pytest.mark.skipif(not redis_installed, reason="Redis is not installed") pytestmark = pytest.mark.skipif( - platform.system() != "Linux" and "Python_Integration_Tests" in os.environ, + platform.system() != "Linux", reason="local redis docker container is not available on all non-Linux platforms", ) @@ -29,11 +28,13 @@ pytestmark = pytest.mark.skipif( @pytest.fixture(scope="session") def connection_string(): try: - connection_string = sk.redis_settings_from_dot_env() + redis_settings = RedisSettings.create() + if redis_settings.connection_string: + return redis_settings.connection_string.get_secret_value() + else: + return "redis://localhost:6379" except Exception: - connection_string = "redis://localhost:6379" - - return connection_string + pytest.skip("Redis connection string not found in env vars.") @pytest.fixture diff --git a/python/tests/integration/connectors/memory/test_weaviate_memory_store.py b/python/tests/integration/connectors/memory/test_weaviate_memory_store.py index 84b884dc0e..e51b70ab66 100644 --- a/python/tests/integration/connectors/memory/test_weaviate_memory_store.py +++ b/python/tests/integration/connectors/memory/test_weaviate_memory_store.py @@ -7,7 +7,7 @@ import numpy as np import numpy.testing as npt import pytest -from semantic_kernel.connectors.memory.weaviate import weaviate_memory_store +from semantic_kernel.connectors.memory.weaviate.weaviate_memory_store import WeaviateConfig, WeaviateMemoryStore from semantic_kernel.memory.memory_record import MemoryRecord if not sys.platform.startswith("linux"): @@ -74,19 +74,19 @@ def documents(): @pytest.fixture def memory_store(): max_attempts = 5 # the number of retry attempts - delay = 30 # delay in seconds between each attempt + delay = 3 # delay in seconds between each attempt - config = weaviate_memory_store.WeaviateConfig(use_embed=True) + config = WeaviateConfig(use_embed=True) for attempt in range(max_attempts): try: - store = weaviate_memory_store.WeaviateMemoryStore(config) + store = WeaviateMemoryStore(config=config) store.client.schema.delete_all() except Exception: if attempt < max_attempts - 1: # it's not the final attempt time.sleep(delay) # wait before retrying continue # go to the next attempt else: # it's the final attempt - raise # re-raise the last exception + pytest.skip("Unable to start Weaviate memory store.") else: break # successful attempt, get out of the loop @@ -116,8 +116,8 @@ def memory_store_with_collection(memory_store, event_loop, documents): def test_embedded_weaviate(): - config = weaviate_memory_store.WeaviateConfig(use_embed=True) - memory_store = weaviate_memory_store.WeaviateMemoryStore(config=config) + config = WeaviateConfig(use_embed=True) + memory_store = WeaviateMemoryStore(config=config) assert memory_store.client._connection.embedded_db diff --git a/python/tests/integration/embeddings/test_azure_oai_embedding_service.py b/python/tests/integration/embeddings/test_azure_oai_embedding_service.py index 49de10ae55..957fd455c3 100644 --- a/python/tests/integration/embeddings/test_azure_oai_embedding_service.py +++ b/python/tests/integration/embeddings/test_azure_oai_embedding_service.py @@ -1,36 +1,28 @@ # Copyright (c) Microsoft. All rights reserved. -import os import pytest from openai import AsyncAzureOpenAI import semantic_kernel as sk import semantic_kernel.connectors.ai.open_ai as sk_oai +from semantic_kernel.connectors.ai.open_ai.settings.azure_open_ai_settings import AzureOpenAISettings +from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin from semantic_kernel.kernel import Kernel from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory +from semantic_kernel.memory.volatile_memory_store import VolatileMemoryStore @pytest.mark.asyncio -async def test_azure_text_embedding_service(kernel: Kernel, get_aoai_config): - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIEmbeddings_EastUS__DeploymentName"] - else: - deployment_name = "text-embedding-ada-002" - +async def test_azure_text_embedding_service(kernel: Kernel): embeddings_gen = sk_oai.AzureTextEmbedding( service_id="aoai-ada", - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, ) kernel.add_service(embeddings_gen) - memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embeddings_gen) - kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin") + memory = SemanticTextMemory(storage=VolatileMemoryStore(), embeddings_generator=embeddings_gen) + kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000") await memory.save_reference( @@ -42,31 +34,30 @@ async def test_azure_text_embedding_service(kernel: Kernel, get_aoai_config): @pytest.mark.asyncio -async def test_azure_text_embedding_service_with_provided_client(kernel: Kernel, get_aoai_config): - _, api_key, endpoint = get_aoai_config +async def test_azure_text_embedding_service_with_provided_client(kernel: Kernel): - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIEmbeddings_EastUS__DeploymentName"] - else: - deployment_name = "text-embedding-ada-002" + azure_openai_settings = AzureOpenAISettings.create() + endpoint = azure_openai_settings.endpoint + deployment_name = azure_openai_settings.embedding_deployment_name + api_key = azure_openai_settings.api_key.get_secret_value() + api_version = azure_openai_settings.api_version client = AsyncAzureOpenAI( azure_endpoint=endpoint, azure_deployment=deployment_name, api_key=api_key, - api_version="2023-05-15", + api_version=api_version, default_headers={"Test-User-X-ID": "test"}, ) embeddings_gen = sk_oai.AzureTextEmbedding( service_id="aoai-ada-2", - deployment_name=deployment_name, async_client=client, ) kernel.add_service(embeddings_gen) memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embeddings_gen) - kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin") + kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000") await memory.save_reference( @@ -78,21 +69,9 @@ async def test_azure_text_embedding_service_with_provided_client(kernel: Kernel, @pytest.mark.asyncio -async def test_batch_azure_embeddings(get_aoai_config): +async def test_batch_azure_embeddings(): # Configure LLM service - _, api_key, endpoint = get_aoai_config - - if "Python_Integration_Tests" in os.environ: - deployment_name = os.environ["AzureOpenAIEmbeddings_EastUS__DeploymentName"] - - else: - deployment_name = "text-embedding-ada-002" - - embeddings_service = sk_oai.AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - ) + embeddings_service = sk_oai.AzureTextEmbedding(service_id="aoai-ada") texts = ["hello world"] results = await embeddings_service.generate_embeddings(texts) batch_results = await embeddings_service.generate_embeddings(texts, batch_size=1) diff --git a/python/tests/integration/embeddings/test_gp_embedding_service.py b/python/tests/integration/embeddings/test_gp_embedding_service.py index fcc944b239..59b7bd0ae1 100644 --- a/python/tests/integration/embeddings/test_gp_embedding_service.py +++ b/python/tests/integration/embeddings/test_gp_embedding_service.py @@ -6,6 +6,7 @@ import sys import pytest import semantic_kernel as sk +from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin from semantic_kernel.kernel import Kernel from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory @@ -22,14 +23,12 @@ pytestmark = [ @pytest.mark.asyncio -async def test_gp_embedding_service(kernel: Kernel, get_gp_config): - api_key = get_gp_config - - palm_text_embed = sk_gp.GooglePalmTextEmbedding("models/embedding-gecko-001", api_key) +async def test_gp_embedding_service(kernel: Kernel): + palm_text_embed = sk_gp.GooglePalmTextEmbedding("models/embedding-gecko-001") kernel.add_service(palm_text_embed) memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=palm_text_embed) - kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin") + kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") await memory.save_information(collection="generic", id="info1", text="My budget for 2024 is $100,000") await memory.save_reference( diff --git a/python/tests/integration/embeddings/test_oai_embedding_service.py b/python/tests/integration/embeddings/test_oai_embedding_service.py index 58542e3333..9ca74c28e6 100644 --- a/python/tests/integration/embeddings/test_oai_embedding_service.py +++ b/python/tests/integration/embeddings/test_oai_embedding_service.py @@ -5,22 +5,23 @@ from openai import AsyncOpenAI import semantic_kernel as sk import semantic_kernel.connectors.ai.open_ai as sk_oai +from semantic_kernel.connectors.ai.open_ai.settings.open_ai_settings import OpenAISettings +from semantic_kernel.core_plugins.text_memory_plugin import TextMemoryPlugin from semantic_kernel.kernel import Kernel from semantic_kernel.memory.semantic_text_memory import SemanticTextMemory @pytest.mark.asyncio -async def test_oai_embedding_service(kernel: Kernel, get_oai_config): - api_key, org_id = get_oai_config - +async def test_oai_embedding_service(kernel: Kernel): embedding_gen = sk_oai.OpenAITextEmbedding( - service_id="oai-ada", ai_model_id="text-embedding-ada-002", api_key=api_key, org_id=org_id + service_id="oai-ada", + ai_model_id="text-embedding-ada-002", ) kernel.add_service(embedding_gen) memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_gen) - kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin") + kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") await memory.save_reference( "test", @@ -31,8 +32,10 @@ async def test_oai_embedding_service(kernel: Kernel, get_oai_config): @pytest.mark.asyncio -async def test_oai_embedding_service_with_provided_client(kernel: Kernel, get_oai_config): - api_key, org_id = get_oai_config +async def test_oai_embedding_service_with_provided_client(kernel: Kernel): + openai_settings = OpenAISettings.create() + api_key = openai_settings.api_key.get_secret_value() + org_id = openai_settings.org_id client = AsyncOpenAI( api_key=api_key, @@ -45,7 +48,7 @@ async def test_oai_embedding_service_with_provided_client(kernel: Kernel, get_oa kernel.add_service(embedding_gen) memory = SemanticTextMemory(storage=sk.memory.VolatileMemoryStore(), embeddings_generator=embedding_gen) - kernel.add_plugin(sk.core_plugins.TextMemoryPlugin(memory), "TextMemoryPlugin") + kernel.add_plugin(TextMemoryPlugin(memory), "TextMemoryPlugin") await memory.save_reference( "test", diff --git a/python/tests/integration/planning/function_calling_stepwise_planner/test_int_function_calling_stepwise_planner.py b/python/tests/integration/planning/function_calling_stepwise_planner/test_int_function_calling_stepwise_planner.py index 8cee73be73..37d616a558 100644 --- a/python/tests/integration/planning/function_calling_stepwise_planner/test_int_function_calling_stepwise_planner.py +++ b/python/tests/integration/planning/function_calling_stepwise_planner/test_int_function_calling_stepwise_planner.py @@ -19,15 +19,13 @@ from semantic_kernel.planners.function_calling_stepwise_planner.function_calling @pytest.mark.asyncio -async def test_can_execute_function_calling_stepwise_plan(kernel: Kernel, get_oai_config): - api_key, _ = get_oai_config +async def test_can_execute_function_calling_stepwise_plan(kernel: Kernel): service_id = "planner" kernel.add_service( OpenAIChatCompletion( service_id=service_id, ai_model_id="gpt-3.5-turbo-1106", - api_key=api_key, ), ) diff --git a/python/tests/integration/planning/sequential_planner/test_sequential_plan_parser.py b/python/tests/integration/planning/sequential_planner/test_sequential_plan_parser.py index 960630971f..fc4f2f6629 100644 --- a/python/tests/integration/planning/sequential_planner/test_sequential_plan_parser.py +++ b/python/tests/integration/planning/sequential_planner/test_sequential_plan_parser.py @@ -11,17 +11,13 @@ from tests.integration.fakes.writer_plugin_fake import WriterPluginFake @pytest.mark.asyncio -async def test_can_call_to_plan_from_xml(get_aoai_config): - deployment_name, api_key, endpoint = get_aoai_config +async def test_can_call_to_plan_from_xml(): kernel = Kernel() # Configure LLM service kernel.add_service( sk_oai.AzureChatCompletion( service_id="text_completion", - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, ), ) kernel.add_plugin(EmailPluginFake(), "email") diff --git a/python/tests/integration/planning/sequential_planner/test_sequential_planner.py b/python/tests/integration/planning/sequential_planner/test_sequential_planner.py index b2f422365a..c94f6b0473 100644 --- a/python/tests/integration/planning/sequential_planner/test_sequential_planner.py +++ b/python/tests/integration/planning/sequential_planner/test_sequential_planner.py @@ -27,26 +27,19 @@ async def retry(func, retries=3): time.sleep(max(min(i, max_delay), min_delay)) -def initialize_kernel(get_aoai_config, use_embeddings=False, use_chat_model=False): - _, api_key, endpoint = get_aoai_config +def initialize_kernel(use_embeddings=False, use_chat_model=False): kernel = Kernel() if use_chat_model: kernel.add_service( sk_oai.AzureChatCompletion( service_id="chat_completion", - deployment_name="gpt-35-turbo-0613", - endpoint=endpoint, - api_key=api_key, ), ) else: kernel.add_service( sk_oai.AzureTextCompletion( service_id="text_completion", - deployment_name="gpt-35-turbo-instruct", - endpoint=endpoint, - api_key=api_key, ), ) @@ -54,9 +47,6 @@ def initialize_kernel(get_aoai_config, use_embeddings=False, use_chat_model=Fals kernel.add_service( sk_oai.AzureTextEmbedding( service_id="text_embedding", - deployment_name="text-embedding-ada-002", - endpoint=endpoint, - api_key=api_key, ), ) return kernel @@ -84,11 +74,11 @@ def initialize_kernel(get_aoai_config, use_embeddings=False, use_chat_model=Fals raises=PlannerException, reason="Test is known to occasionally produce unexpected results.", ) -async def test_create_plan_function_flow(get_aoai_config, use_chat_model, prompt, expected_function, expected_plugin): +async def test_create_plan_function_flow(use_chat_model, prompt, expected_function, expected_plugin): # Arrange service_id = "chat_completion" if use_chat_model else "text_completion" - kernel = initialize_kernel(get_aoai_config, False, use_chat_model) + kernel = initialize_kernel(False, use_chat_model) kernel.add_plugin(EmailPluginFake(), "email_plugin_fake") kernel.add_plugin(FunPluginFake(), "fun_plugin_fake") @@ -117,9 +107,9 @@ async def test_create_plan_function_flow(get_aoai_config, use_chat_model, prompt raises=PlannerException, reason="Test is known to occasionally produce unexpected results.", ) -async def test_create_plan_with_defaults(get_aoai_config, prompt, expected_function, expected_plugin, expected_default): +async def test_create_plan_with_defaults(prompt, expected_function, expected_plugin, expected_default): # Arrange - kernel = initialize_kernel(get_aoai_config) + kernel = initialize_kernel() kernel.add_plugin(EmailPluginFake(), "email_plugin_fake") kernel.add_plugin(WriterPluginFake(), "WriterPlugin") @@ -152,9 +142,9 @@ async def test_create_plan_with_defaults(get_aoai_config, prompt, expected_funct raises=PlannerException, reason="Test is known to occasionally produce unexpected results.", ) -async def test_create_plan_goal_relevant(get_aoai_config, prompt, expected_function, expected_plugin): +async def test_create_plan_goal_relevant(prompt, expected_function, expected_plugin): # Arrange - kernel = initialize_kernel(get_aoai_config, use_embeddings=True) + kernel = initialize_kernel(use_embeddings=True) kernel.add_plugin(EmailPluginFake(), "email_plugin_fake") kernel.add_plugin(FunPluginFake(), "fun_plugin_fake") kernel.add_plugin(WriterPluginFake(), "writer_plugin_fake") diff --git a/python/tests/unit/connectors/google_palm/services/test_palm_chat_completion.py b/python/tests/unit/connectors/google_palm/services/test_palm_chat_completion.py index 074c89b8af..8606b4db66 100644 --- a/python/tests/unit/connectors/google_palm/services/test_palm_chat_completion.py +++ b/python/tests/unit/connectors/google_palm/services/test_palm_chat_completion.py @@ -1,50 +1,41 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio -import sys from unittest.mock import MagicMock, patch import pytest +from google.generativeai.types import ChatResponse, MessageDict from pydantic import ValidationError -if sys.version_info >= (3, 9): - from google.generativeai.types import ChatResponse, MessageDict - - from semantic_kernel.connectors.ai.google_palm import GooglePalmChatPromptExecutionSettings - from semantic_kernel.connectors.ai.google_palm.services.gp_chat_completion import GooglePalmChatCompletion - from semantic_kernel.contents.chat_history import ChatHistory +from semantic_kernel.connectors.ai.google_palm import GooglePalmChatPromptExecutionSettings +from semantic_kernel.connectors.ai.google_palm.services.gp_chat_completion import GooglePalmChatCompletion +from semantic_kernel.contents.chat_history import ChatHistory -pytestmark = pytest.mark.skipif(sys.version_info < (3, 9), reason="Google Palm requires Python 3.9 or greater") - - -def test_google_palm_chat_completion_init() -> None: +def test_google_palm_chat_completion_init(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - api_key = "test_api_key" gp_chat_completion = GooglePalmChatCompletion( ai_model_id=ai_model_id, - api_key=api_key, ) assert gp_chat_completion.ai_model_id == ai_model_id - assert gp_chat_completion.api_key == api_key + assert gp_chat_completion.api_key == google_palm_unit_test_env["GOOGLE_PALM_API_KEY"] assert isinstance(gp_chat_completion, GooglePalmChatCompletion) -def test_google_palm_chat_completion_init_with_empty_api_key() -> None: +@pytest.mark.parametrize("exclude_list", [["GOOGLE_PALM_API_KEY"]], indirect=True) +def test_google_palm_chat_completion_init_with_empty_api_key(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - # api_key = "test_api_key" - with pytest.raises(ValidationError, match="api_key"): + with pytest.raises(ValidationError): GooglePalmChatCompletion( ai_model_id=ai_model_id, - api_key="", ) @pytest.mark.asyncio -async def test_google_palm_text_completion_complete_chat_call_with_parameters() -> None: +async def test_google_palm_text_completion_complete_chat_call_with_parameters(google_palm_unit_test_env) -> None: class MockChatResponse(ChatResponse): def last(self): return "" @@ -65,12 +56,10 @@ async def test_google_palm_text_completion_complete_chat_call_with_parameters() new=mock_gp, ): ai_model_id = "test_model_id" - api_key = "test_api_key" chats = ChatHistory() chats.add_user_message("Hello word") gp_chat_completion = GooglePalmChatCompletion( ai_model_id=ai_model_id, - api_key=api_key, ) settings = GooglePalmChatPromptExecutionSettings() response = await gp_chat_completion.complete_chat(chats, settings) diff --git a/python/tests/unit/connectors/google_palm/services/test_palm_text_completion.py b/python/tests/unit/connectors/google_palm/services/test_palm_text_completion.py index 431da12947..3d6098411a 100644 --- a/python/tests/unit/connectors/google_palm/services/test_palm_text_completion.py +++ b/python/tests/unit/connectors/google_palm/services/test_palm_text_completion.py @@ -1,53 +1,44 @@ # Copyright (c) Microsoft. All rights reserved. -import sys from unittest.mock import MagicMock, patch import pytest +from google.generativeai.types import Completion +from google.generativeai.types.text_types import TextCompletion from pydantic import ValidationError -if sys.version_info >= (3, 9): - from google.generativeai.types import Completion - from google.generativeai.types.text_types import TextCompletion - - from semantic_kernel.connectors.ai.google_palm import ( - GooglePalmTextPromptExecutionSettings, - ) - from semantic_kernel.connectors.ai.google_palm.services.gp_text_completion import ( - GooglePalmTextCompletion, - ) +from semantic_kernel.connectors.ai.google_palm import ( + GooglePalmTextPromptExecutionSettings, +) +from semantic_kernel.connectors.ai.google_palm.services.gp_text_completion import ( + GooglePalmTextCompletion, +) -pytestmark = pytest.mark.skipif(sys.version_info < (3, 9), reason="Google Palm requires Python 3.9 or greater") - - -def test_google_palm_text_completion_init() -> None: +def test_google_palm_text_completion_init(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - api_key = "test_api_key" # Test successful initialization gp_text_completion = GooglePalmTextCompletion( ai_model_id=ai_model_id, - api_key=api_key, ) assert gp_text_completion.ai_model_id == ai_model_id - assert gp_text_completion.api_key == api_key + assert gp_text_completion.api_key == google_palm_unit_test_env["GOOGLE_PALM_API_KEY"] assert isinstance(gp_text_completion, GooglePalmTextCompletion) -def test_google_palm_text_completion_init_with_empty_api_key() -> None: +@pytest.mark.parametrize("exclude_list", [["GOOGLE_PALM_API_KEY"]], indirect=True) +def test_google_palm_text_completion_init_with_empty_api_key(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - # api_key = "test_api_key" - with pytest.raises(ValidationError, match="api_key"): + with pytest.raises(ValidationError): GooglePalmTextCompletion( ai_model_id=ai_model_id, - api_key="", ) @pytest.mark.asyncio -async def test_google_palm_text_completion_complete_call_with_parameters() -> None: +async def test_google_palm_text_completion_complete_call_with_parameters(google_palm_unit_test_env) -> None: gp_completion = Completion() gp_completion.candidates = [TextCompletion(output="Example response")] gp_completion.filters = None @@ -59,11 +50,9 @@ async def test_google_palm_text_completion_complete_call_with_parameters() -> No new=mock_gp, ): ai_model_id = "test_model_id" - api_key = "test_api_key" prompt = "hello world" gp_text_completion = GooglePalmTextCompletion( ai_model_id=ai_model_id, - api_key=api_key, ) settings = GooglePalmTextPromptExecutionSettings() response = await gp_text_completion.complete(prompt, settings) diff --git a/python/tests/unit/connectors/google_palm/services/test_palm_text_embedding.py b/python/tests/unit/connectors/google_palm/services/test_palm_text_embedding.py index 6e9f99df47..42a022d229 100644 --- a/python/tests/unit/connectors/google_palm/services/test_palm_text_embedding.py +++ b/python/tests/unit/connectors/google_palm/services/test_palm_text_embedding.py @@ -1,48 +1,40 @@ # Copyright (c) Microsoft. All rights reserved. -import sys from unittest.mock import MagicMock, patch import pytest from pydantic import ValidationError -if sys.version_info >= (3, 9): - from semantic_kernel.connectors.ai.google_palm.services.gp_text_embedding import ( - GooglePalmTextEmbedding, - ) +from semantic_kernel.connectors.ai.google_palm.services.gp_text_embedding import ( + GooglePalmTextEmbedding, +) -pytestmark = pytest.mark.skipif(sys.version_info < (3, 9), reason="Google Palm requires Python 3.9 or greater") - - -def test_google_palm_text_embedding_init() -> None: +def test_google_palm_text_embedding_init(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - api_key = "test_api_key" # Test successful initialization gp_text_embed = GooglePalmTextEmbedding( ai_model_id=ai_model_id, - api_key=api_key, ) assert gp_text_embed.ai_model_id == ai_model_id - assert gp_text_embed.api_key == api_key + assert gp_text_embed.api_key == google_palm_unit_test_env["GOOGLE_PALM_API_KEY"] assert isinstance(gp_text_embed, GooglePalmTextEmbedding) -def test_google_palm_text_embedding_init_with_empty_api_key() -> None: +@pytest.mark.parametrize("exclude_list", [["GOOGLE_PALM_API_KEY"]], indirect=True) +def test_google_palm_text_embedding_init_with_empty_api_key(google_palm_unit_test_env) -> None: ai_model_id = "test_model_id" - # api_key = "test_api_key" - with pytest.raises(ValidationError, match="api_key"): + with pytest.raises(ValidationError): GooglePalmTextEmbedding( ai_model_id=ai_model_id, - api_key="", ) @pytest.mark.asyncio -async def test_google_palm_text_embedding_calls_with_parameters() -> None: +async def test_google_palm_text_embedding_calls_with_parameters(google_palm_unit_test_env) -> None: mock_gp = MagicMock() mock_gp.generate_embeddings.return_value = {"embedding": [0.1, 0.2, 0.3]} with patch( @@ -50,13 +42,11 @@ async def test_google_palm_text_embedding_calls_with_parameters() -> None: new=mock_gp, ): ai_model_id = "test_model_id" - api_key = "test_api_key" texts = ["hello world"] text = "hello world" gp_text_embedding = GooglePalmTextEmbedding( ai_model_id=ai_model_id, - api_key=api_key, ) await gp_text_embedding.generate_embeddings(texts) diff --git a/python/tests/unit/connectors/open_ai/services/test_azure_chat_completion.py b/python/tests/unit/connectors/open_ai/services/test_azure_chat_completion.py index 7dab06baff..1ee41b24c8 100644 --- a/python/tests/unit/connectors/open_ai/services/test_azure_chat_completion.py +++ b/python/tests/unit/connectors/open_ai/services/test_azure_chat_completion.py @@ -1,5 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. +import os from unittest.mock import AsyncMock, patch import openai @@ -28,150 +29,71 @@ from semantic_kernel.exceptions.service_exceptions import ServiceResponseExcepti from semantic_kernel.kernel import Kernel -def test_azure_chat_completion_init() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +def test_azure_chat_completion_init(azure_openai_unit_test_env) -> None: # Test successful initialization - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() assert azure_chat_completion.client is not None assert isinstance(azure_chat_completion.client, AsyncAzureOpenAI) - assert azure_chat_completion.ai_model_id == deployment_name + assert azure_chat_completion.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"] assert isinstance(azure_chat_completion, ChatCompletionClientBase) -def test_azure_chat_completion_init_base_url() -> None: - deployment_name = "test_deployment" - base_url = "https://test-endpoint.com/openai/deployment/test_deployment" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +def test_azure_chat_completion_init_base_url(azure_openai_unit_test_env) -> None: # Custom header for testing default_headers = {"X-Unit-Test": "test-guid"} azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - base_url=base_url, - api_key=api_key, - api_version=api_version, default_headers=default_headers, ) assert azure_chat_completion.client is not None assert isinstance(azure_chat_completion.client, AsyncAzureOpenAI) - assert azure_chat_completion.ai_model_id == deployment_name + assert azure_chat_completion.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"] assert isinstance(azure_chat_completion, ChatCompletionClientBase) for key, value in default_headers.items(): assert key in azure_chat_completion.client.default_headers assert azure_chat_completion.client.default_headers[key] == value -def test_azure_chat_completion_init_with_empty_deployment_name() -> None: - # deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="ai_model_id"): - AzureChatCompletion( - deployment_name="", - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]], indirect=True) +def test_azure_chat_completion_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None: + with pytest.raises(ValidationError): + AzureChatCompletion() -def test_azure_chat_completion_init_with_empty_api_key() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - # api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ServiceInitializationError, match="api_key"): - AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key="", - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True) +def test_azure_chat_completion_init_with_empty_api_key(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureChatCompletion() -def test_azure_chat_completion_init_with_empty_endpoint() -> None: - deployment_name = "test_deployment" - # endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="url"): - AzureChatCompletion( - deployment_name=deployment_name, - endpoint="", - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True) +def test_azure_chat_completion_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureChatCompletion() -def test_azure_chat_completion_init_with_invalid_endpoint() -> None: - deployment_name = "test_deployment" - endpoint = "http://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="url"): - AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) - - -def test_azure_chat_completion_init_with_base_url() -> None: - deployment_name = "test_deployment" - base_url = "http://test-endpoint.com/openai/deployment/test_deployment" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="url"): - AzureChatCompletion( - deployment_name=deployment_name, - base_url=base_url, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True) +def test_azure_chat_completion_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureChatCompletion() @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_call_with_parameters( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" chat_history.add_user_message("hello world") complete_prompt_execution_settings = AzureChatPromptExecutionSettings(service_id="test_service_id") - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_version=api_version, - api_key=api_key, - ) + azure_chat_completion = AzureChatCompletion() await azure_chat_completion.complete_chat( chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel ) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], frequency_penalty=complete_prompt_execution_settings.frequency_penalty, logit_bias={}, max_tokens=complete_prompt_execution_settings.max_tokens, @@ -187,13 +109,8 @@ async def test_azure_chat_completion_call_with_parameters( @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - prompt = "hello world" chat_history.add_user_message(prompt) complete_prompt_execution_settings = AzureChatPromptExecutionSettings() @@ -201,19 +118,14 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined token_bias = {"1": -100} complete_prompt_execution_settings.logit_bias = token_bias - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() await azure_chat_completion.complete_chat( chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel ) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], messages=azure_chat_completion._prepare_chat_history_for_request(chat_history), temperature=complete_prompt_execution_settings.temperature, top_p=complete_prompt_execution_settings.top_p, @@ -230,12 +142,8 @@ async def test_azure_chat_completion_call_with_parameters_and_Logit_Bias_Defined @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined( mock_create, + azure_openai_unit_test_env, ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - prompt = "hello world" messages = [{"role": "user", "content": prompt}] complete_prompt_execution_settings = AzureChatPromptExecutionSettings() @@ -243,17 +151,12 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined( stop = ["!"] complete_prompt_execution_settings.stop = stop - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() await azure_chat_completion.complete(prompt=prompt, settings=complete_prompt_execution_settings) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], messages=messages, temperature=complete_prompt_execution_settings.temperature, top_p=complete_prompt_execution_settings.top_p, @@ -267,18 +170,14 @@ async def test_azure_chat_completion_call_with_parameters_and_Stop_Defined( ) -def test_azure_chat_completion_serialize() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +def test_azure_chat_completion_serialize(azure_openai_unit_test_env) -> None: default_headers = {"X-Test": "test"} settings = { - "deployment_name": deployment_name, - "endpoint": endpoint, - "api_key": api_key, - "api_version": api_version, + "deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], + "endpoint": azure_openai_unit_test_env["AZURE_OPENAI_ENDPOINT"], + "api_key": azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"], + "api_version": azure_openai_unit_test_env["AZURE_OPENAI_API_VERSION"], "default_headers": default_headers, } @@ -302,12 +201,8 @@ def test_azure_chat_completion_serialize() -> None: @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_with_data_call_with_parameters( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" prompt = "hello world" messages_in = chat_history messages_in.add_user_message(prompt) @@ -329,19 +224,14 @@ async def test_azure_chat_completion_with_data_call_with_parameters( complete_prompt_execution_settings = AzureChatPromptExecutionSettings(extra_body=expected_data_settings) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_version=api_version, - api_key=api_key, - ) + azure_chat_completion = AzureChatCompletion() await azure_chat_completion.complete_chat( chat_history=messages_in, settings=complete_prompt_execution_settings, kernel=kernel ) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], messages=azure_chat_completion._prepare_chat_history_for_request(messages_out), temperature=complete_prompt_execution_settings.temperature, frequency_penalty=complete_prompt_execution_settings.frequency_penalty, @@ -358,12 +248,8 @@ async def test_azure_chat_completion_with_data_call_with_parameters( @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_call_with_data_parameters_and_function_calling( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" prompt = "hello world" chat_history.add_user_message(prompt) @@ -376,12 +262,7 @@ async def test_azure_chat_completion_call_with_data_parameters_and_function_call ) extra = ExtraBody(data_sources=[ai_source]) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() functions = [{"name": "test-function", "description": "test-description"}] complete_prompt_execution_settings = AzureChatPromptExecutionSettings( @@ -399,7 +280,7 @@ async def test_azure_chat_completion_call_with_data_parameters_and_function_call expected_data_settings = extra.model_dump(exclude_none=True, by_alias=True) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], messages=azure_chat_completion._prepare_chat_history_for_request(chat_history), temperature=complete_prompt_execution_settings.temperature, top_p=complete_prompt_execution_settings.top_p, @@ -418,12 +299,8 @@ async def test_azure_chat_completion_call_with_data_parameters_and_function_call @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) async def test_azure_chat_completion_call_with_data_with_parameters_and_Stop_Defined( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" chat_history.add_user_message("hello world") complete_prompt_execution_settings = AzureChatPromptExecutionSettings() @@ -441,19 +318,14 @@ async def test_azure_chat_completion_call_with_data_with_parameters_and_Stop_Def complete_prompt_execution_settings.extra_body = extra - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() await azure_chat_completion.complete_chat(chat_history, complete_prompt_execution_settings, kernel=kernel) expected_data_settings = extra.model_dump(exclude_none=True, by_alias=True) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"], messages=azure_chat_completion._prepare_chat_history_for_request(chat_history), temperature=complete_prompt_execution_settings.temperature, top_p=complete_prompt_execution_settings.top_p, @@ -484,19 +356,16 @@ CONTENT_FILTERED_ERROR_FULL_MESSAGE = ( @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create") async def test_azure_chat_completion_content_filtering_raises_correct_exception( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" prompt = "some prompt that would trigger the content filtering" chat_history.add_user_message(prompt) complete_prompt_execution_settings = AzureChatPromptExecutionSettings() + test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") mock_create.side_effect = openai.BadRequestError( CONTENT_FILTERED_ERROR_FULL_MESSAGE, - response=Response(400, request=Request("POST", endpoint)), + response=Response(400, request=Request("POST", test_endpoint)), body={ "message": CONTENT_FILTERED_ERROR_MESSAGE, "type": None, @@ -515,12 +384,7 @@ async def test_azure_chat_completion_content_filtering_raises_correct_exception( }, ) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() with pytest.raises(ContentFilterAIException, match="service encountered a content error") as exc_info: await azure_chat_completion.complete_chat(chat_history, complete_prompt_execution_settings, kernel=kernel) @@ -534,19 +398,16 @@ async def test_azure_chat_completion_content_filtering_raises_correct_exception( @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create") async def test_azure_chat_completion_content_filtering_without_response_code_raises_with_default_code( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" prompt = "some prompt that would trigger the content filtering" chat_history.add_user_message(prompt) complete_prompt_execution_settings = AzureChatPromptExecutionSettings() + test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") mock_create.side_effect = openai.BadRequestError( CONTENT_FILTERED_ERROR_FULL_MESSAGE, - response=Response(400, request=Request("POST", endpoint)), + response=Response(400, request=Request("POST", test_endpoint)), body={ "message": CONTENT_FILTERED_ERROR_MESSAGE, "type": None, @@ -564,12 +425,7 @@ async def test_azure_chat_completion_content_filtering_without_response_code_rai }, ) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() with pytest.raises(ContentFilterAIException, match="service encountered a content error"): await azure_chat_completion.complete_chat(chat_history, complete_prompt_execution_settings, kernel=kernel) @@ -578,26 +434,18 @@ async def test_azure_chat_completion_content_filtering_without_response_code_rai @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create") async def test_azure_chat_completion_bad_request_non_content_filter( - mock_create, kernel: Kernel, chat_history: ChatHistory + mock_create, kernel: Kernel, azure_openai_unit_test_env, chat_history: ChatHistory ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" prompt = "some prompt that would trigger the content filtering" chat_history.add_user_message(prompt) complete_prompt_execution_settings = AzureChatPromptExecutionSettings() + test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") mock_create.side_effect = openai.BadRequestError( - "The request was bad.", response=Response(400, request=Request("POST", endpoint)), body={} + "The request was bad.", response=Response(400, request=Request("POST", test_endpoint)), body={} ) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() with pytest.raises(ServiceResponseException, match="service failed to complete the prompt"): await azure_chat_completion.complete_chat(chat_history, complete_prompt_execution_settings, kernel=kernel) @@ -605,27 +453,21 @@ async def test_azure_chat_completion_bad_request_non_content_filter( @pytest.mark.asyncio @patch.object(AsyncChatCompletions, "create") -async def test_azure_chat_completion_no_kernel_provided_throws_error(mock_create, chat_history: ChatHistory) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +async def test_azure_chat_completion_no_kernel_provided_throws_error( + mock_create, azure_openai_unit_test_env, chat_history: ChatHistory +) -> None: prompt = "some prompt that would trigger the content filtering" chat_history.add_user_message(prompt) complete_prompt_execution_settings = AzureChatPromptExecutionSettings( function_call_behavior=FunctionCallBehavior.AutoInvokeKernelFunctions() ) + test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") mock_create.side_effect = openai.BadRequestError( - "The request was bad.", response=Response(400, request=Request("POST", endpoint)), body={} + "The request was bad.", response=Response(400, request=Request("POST", test_endpoint)), body={} ) - azure_chat_completion = AzureChatCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_chat_completion = AzureChatCompletion() with pytest.raises( ServiceInvalidExecutionSettingsError, diff --git a/python/tests/unit/connectors/open_ai/services/test_azure_text_completion.py b/python/tests/unit/connectors/open_ai/services/test_azure_text_completion.py index 9ae02c6bf2..92b86fb2cc 100644 --- a/python/tests/unit/connectors/open_ai/services/test_azure_text_completion.py +++ b/python/tests/unit/connectors/open_ai/services/test_azure_text_completion.py @@ -15,134 +15,69 @@ from semantic_kernel.connectors.ai.text_completion_client_base import TextComple from semantic_kernel.exceptions import ServiceInitializationError -def test_azure_text_completion_init() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +def test_azure_text_completion_init(azure_openai_unit_test_env) -> None: # Test successful initialization - azure_text_completion = AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_completion = AzureTextCompletion() assert azure_text_completion.client is not None assert isinstance(azure_text_completion.client, AsyncAzureOpenAI) - assert azure_text_completion.ai_model_id == deployment_name + assert azure_text_completion.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"] assert isinstance(azure_text_completion, TextCompletionClientBase) -def test_azure_text_completion_init_with_custom_header() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +def test_azure_text_completion_init_with_custom_header(azure_openai_unit_test_env) -> None: # Custom header for testing default_headers = {"X-Unit-Test": "test-guid"} # Test successful initialization azure_text_completion = AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, default_headers=default_headers, ) assert azure_text_completion.client is not None assert isinstance(azure_text_completion.client, AsyncAzureOpenAI) - assert azure_text_completion.ai_model_id == deployment_name + assert azure_text_completion.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"] assert isinstance(azure_text_completion, TextCompletionClientBase) for key, value in default_headers.items(): assert key in azure_text_completion.client.default_headers assert azure_text_completion.client.default_headers[key] == value -def test_azure_text_completion_init_with_empty_deployment_name() -> None: - # deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="ai_model_id"): - AzureTextCompletion( - deployment_name="", - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"]], indirect=True) +def test_azure_text_completion_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None: + with pytest.raises(ValidationError): + AzureTextCompletion() -def test_azure_text_completion_init_with_empty_api_key() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - # api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ServiceInitializationError, match="api_key"): - AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key="", - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True) +def test_azure_text_completion_init_with_empty_api_key(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextCompletion() -def test_azure_text_completion_init_with_empty_endpoint() -> None: - deployment_name = "test_deployment" - # endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="endpoint"): - AzureTextCompletion( - deployment_name=deployment_name, - endpoint="", - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True) +def test_azure_text_completion_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextCompletion() -def test_azure_text_completion_init_with_invalid_endpoint() -> None: - deployment_name = "test_deployment" - endpoint = "http://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="https"): - AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True) +def test_azure_text_completion_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextCompletion() @pytest.mark.asyncio @patch.object(AsyncCompletions, "create", new_callable=AsyncMock) -async def test_azure_text_completion_call_with_parameters(mock_create) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +async def test_azure_text_completion_call_with_parameters(mock_create, azure_openai_unit_test_env) -> None: prompt = "hello world" complete_prompt_execution_settings = OpenAITextPromptExecutionSettings() - azure_text_completion = AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_completion = AzureTextCompletion() await azure_text_completion.complete(prompt, complete_prompt_execution_settings) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"], frequency_penalty=complete_prompt_execution_settings.frequency_penalty, logit_bias={}, max_tokens=complete_prompt_execution_settings.max_tokens, @@ -160,29 +95,20 @@ async def test_azure_text_completion_call_with_parameters(mock_create) -> None: @patch.object(AsyncCompletions, "create", new_callable=AsyncMock) async def test_azure_text_completion_call_with_parameters_logit_bias_not_none( mock_create, + azure_openai_unit_test_env, ) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - prompt = "hello world" complete_prompt_execution_settings = OpenAITextPromptExecutionSettings() token_bias = {"200": 100} complete_prompt_execution_settings.logit_bias = token_bias - azure_text_completion = AzureTextCompletion( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_completion = AzureTextCompletion() await azure_text_completion.complete(prompt, complete_prompt_execution_settings) mock_create.assert_awaited_once_with( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"], frequency_penalty=complete_prompt_execution_settings.frequency_penalty, logit_bias=complete_prompt_execution_settings.logit_bias, max_tokens=complete_prompt_execution_settings.max_tokens, @@ -196,18 +122,15 @@ async def test_azure_text_completion_call_with_parameters_logit_bias_not_none( ) -def test_azure_text_completion_serialize() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +def test_azure_text_completion_serialize(azure_openai_unit_test_env) -> None: default_headers = {"X-Test": "test"} settings = { - "deployment_name": deployment_name, - "endpoint": endpoint, - "api_key": api_key, - "api_version": api_version, + "deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"], + "endpoint": azure_openai_unit_test_env["AZURE_OPENAI_ENDPOINT"], + "base_url": azure_openai_unit_test_env["AZURE_OPENAI_BASE_URL"], + "api_key": azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"], + "api_version": azure_openai_unit_test_env["AZURE_OPENAI_API_VERSION"], "default_headers": default_headers, } diff --git a/python/tests/unit/connectors/open_ai/services/test_azure_text_embedding.py b/python/tests/unit/connectors/open_ai/services/test_azure_text_embedding.py index 393a9d5ec0..0c1853324d 100644 --- a/python/tests/unit/connectors/open_ai/services/test_azure_text_embedding.py +++ b/python/tests/unit/connectors/open_ai/services/test_azure_text_embedding.py @@ -12,98 +12,53 @@ from semantic_kernel.connectors.ai.open_ai.services.azure_text_embedding import from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError -def test_azure_text_embedding_init() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - +def test_azure_text_embedding_init(azure_openai_unit_test_env) -> None: # Test successful initialization - azure_text_embedding = AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_embedding = AzureTextEmbedding() assert azure_text_embedding.client is not None assert isinstance(azure_text_embedding.client, AsyncAzureOpenAI) - assert azure_text_embedding.ai_model_id == deployment_name + assert azure_text_embedding.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"] assert isinstance(azure_text_embedding, EmbeddingGeneratorBase) -def test_azure_text_embedding_init_with_empty_deployment_name() -> None: - # deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="ai_model_id"): - AzureTextEmbedding( - deployment_name="", - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"]], indirect=True) +def test_azure_text_embedding_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None: + with pytest.raises(ValidationError): + AzureTextEmbedding() -def test_azure_text_embedding_init_with_empty_api_key() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - # api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ServiceInitializationError, match="api_key"): - AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key="", - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True) +def test_azure_text_embedding_init_with_empty_api_key(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextEmbedding() -def test_azure_text_embedding_init_with_empty_endpoint() -> None: - deployment_name = "test_deployment" - # endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="endpoint"): - AzureTextEmbedding( - deployment_name=deployment_name, - endpoint="", - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True) +def test_azure_text_embedding_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextEmbedding() -def test_azure_text_embedding_init_with_invalid_endpoint() -> None: - deployment_name = "test_deployment" - endpoint = "http://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" - - with pytest.raises(ValidationError, match="https"): - AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) +@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True) +def test_azure_text_embedding_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + AzureTextEmbedding() -def test_azure_text_embedding_init_with_from_dict() -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +@pytest.mark.parametrize( + "override_env_param_dict", + [{"AZURE_OPENAI_BASE_URL": "https://test_embedding_deployment.test-base-url.com"}], + indirect=True, +) +def test_azure_text_embedding_init_with_from_dict(azure_openai_unit_test_env) -> None: default_headers = {"test_header": "test_value"} settings = { - "deployment_name": deployment_name, - "endpoint": endpoint, - "api_key": api_key, - "api_version": api_version, + "deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"], + "endpoint": azure_openai_unit_test_env["AZURE_OPENAI_ENDPOINT"], + "api_key": azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"], + "api_version": azure_openai_unit_test_env["AZURE_OPENAI_API_VERSION"], "default_headers": default_headers, } @@ -111,10 +66,10 @@ def test_azure_text_embedding_init_with_from_dict() -> None: assert azure_text_embedding.client is not None assert isinstance(azure_text_embedding.client, AsyncAzureOpenAI) - assert azure_text_embedding.ai_model_id == deployment_name + assert azure_text_embedding.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"] assert isinstance(azure_text_embedding, EmbeddingGeneratorBase) - assert endpoint in str(azure_text_embedding.client.base_url) - assert azure_text_embedding.client.api_key == api_key + assert settings["deployment_name"] in str(azure_text_embedding.client.base_url) + assert azure_text_embedding.client.api_key == azure_openai_unit_test_env["AZURE_OPENAI_API_KEY"] # Assert that the default header we added is present in the client's default headers for key, value in default_headers.items(): @@ -124,56 +79,38 @@ def test_azure_text_embedding_init_with_from_dict() -> None: @pytest.mark.asyncio @patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock) -async def test_azure_text_embedding_calls_with_parameters(mock_create) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +async def test_azure_text_embedding_calls_with_parameters(mock_create, azure_openai_unit_test_env) -> None: texts = ["hello world", "goodbye world"] embedding_dimensions = 1536 - azure_text_embedding = AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_embedding = AzureTextEmbedding() await azure_text_embedding.generate_embeddings(texts, dimensions=embedding_dimensions) mock_create.assert_awaited_once_with( input=texts, - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"], dimensions=embedding_dimensions, ) @pytest.mark.asyncio @patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock) -async def test_azure_text_embedding_calls_with_batches(mock_create) -> None: - deployment_name = "test_deployment" - endpoint = "https://test-endpoint.com" - api_key = "test_api_key" - api_version = "2023-03-15-preview" +async def test_azure_text_embedding_calls_with_batches(mock_create, azure_openai_unit_test_env) -> None: texts = [i for i in range(0, 5)] - azure_text_embedding = AzureTextEmbedding( - deployment_name=deployment_name, - endpoint=endpoint, - api_key=api_key, - api_version=api_version, - ) + azure_text_embedding = AzureTextEmbedding() await azure_text_embedding.generate_embeddings(texts, batch_size=3) mock_create.assert_has_awaits( [ call( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"], input=texts[0:3], ), call( - model=deployment_name, + model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"], input=texts[3:5], ), ], diff --git a/python/tests/unit/connectors/open_ai/services/test_openai_chat_completion.py b/python/tests/unit/connectors/open_ai/services/test_openai_chat_completion.py index 1292ffac4a..b535bb8493 100644 --- a/python/tests/unit/connectors/open_ai/services/test_openai_chat_completion.py +++ b/python/tests/unit/connectors/open_ai/services/test_openai_chat_completion.py @@ -2,40 +2,39 @@ import pytest -from pydantic import ValidationError from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError -def test_open_ai_chat_completion_init() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" - +def test_open_ai_chat_completion_init(openai_unit_test_env) -> None: # Test successful initialization - open_ai_chat_completion = OpenAIChatCompletion( - ai_model_id=ai_model_id, - api_key=api_key, - ) + open_ai_chat_completion = OpenAIChatCompletion() + + assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"] + assert isinstance(open_ai_chat_completion, ChatCompletionClientBase) + + +def test_open_ai_chat_completion_init_ai_model_id_constructor(openai_unit_test_env) -> None: + # Test successful initialization + ai_model_id = "test_model_id" + open_ai_chat_completion = OpenAIChatCompletion(ai_model_id=ai_model_id) assert open_ai_chat_completion.ai_model_id == ai_model_id assert isinstance(open_ai_chat_completion, ChatCompletionClientBase) -def test_open_ai_chat_completion_init_with_default_header() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" +def test_open_ai_chat_completion_init_with_default_header(openai_unit_test_env) -> None: default_headers = {"X-Unit-Test": "test-guid"} # Test successful initialization open_ai_chat_completion = OpenAIChatCompletion( - ai_model_id=ai_model_id, - api_key=api_key, default_headers=default_headers, ) - assert open_ai_chat_completion.ai_model_id == ai_model_id + assert open_ai_chat_completion.ai_model_id == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"] assert isinstance(open_ai_chat_completion, ChatCompletionClientBase) # Assert that the default header we added is present in the client's default headers @@ -44,43 +43,35 @@ def test_open_ai_chat_completion_init_with_default_header() -> None: assert open_ai_chat_completion.client.default_headers[key] == value -def test_open_ai_chat_completion_init_with_empty_model_id() -> None: - # ai_model_id = "test_model_id" - api_key = "test_api_key" - - with pytest.raises(ValidationError, match="ai_model_id"): - OpenAIChatCompletion( - ai_model_id="", - api_key=api_key, - ) +@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True) +def test_open_ai_chat_completion_init_with_empty_model_id(openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + OpenAIChatCompletion() -def test_open_ai_chat_completion_init_with_empty_api_key() -> None: +@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True) +def test_open_ai_chat_completion_init_with_empty_api_key(openai_unit_test_env) -> None: ai_model_id = "test_model_id" - # api_key = "test_api_key" - with pytest.raises(ValidationError, match="api_key"): + with pytest.raises(ServiceInitializationError): OpenAIChatCompletion( ai_model_id=ai_model_id, - api_key="", ) -def test_open_ai_chat_completion_serialize() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" +def test_open_ai_chat_completion_serialize(openai_unit_test_env) -> None: default_headers = {"X-Unit-Test": "test-guid"} settings = { - "ai_model_id": ai_model_id, - "api_key": api_key, + "ai_model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"], + "api_key": openai_unit_test_env["OPENAI_API_KEY"], "default_headers": default_headers, } open_ai_chat_completion = OpenAIChatCompletion.from_dict(settings) dumped_settings = open_ai_chat_completion.to_dict() - assert dumped_settings["ai_model_id"] == ai_model_id - assert dumped_settings["api_key"] == api_key + assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"] + assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"] # Assert that the default header we added is present in the dumped_settings default headers for key, value in default_headers.items(): assert key in dumped_settings["default_headers"] @@ -89,21 +80,17 @@ def test_open_ai_chat_completion_serialize() -> None: assert USER_AGENT not in dumped_settings["default_headers"] -def test_open_ai_chat_completion_serialize_with_org_id() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" - org_id = "test_org_id" - +def test_open_ai_chat_completion_serialize_with_org_id(openai_unit_test_env) -> None: settings = { - "ai_model_id": ai_model_id, - "api_key": api_key, - "org_id": org_id, + "ai_model_id": openai_unit_test_env["OPENAI_CHAT_MODEL_ID"], + "api_key": openai_unit_test_env["OPENAI_API_KEY"], + "org_id": openai_unit_test_env["OPENAI_ORG_ID"], } open_ai_chat_completion = OpenAIChatCompletion.from_dict(settings) dumped_settings = open_ai_chat_completion.to_dict() - assert dumped_settings["ai_model_id"] == ai_model_id - assert dumped_settings["api_key"] == api_key - assert dumped_settings["org_id"] == org_id + assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_CHAT_MODEL_ID"] + assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"] + assert dumped_settings["org_id"] == openai_unit_test_env["OPENAI_ORG_ID"] # Assert that the 'User-agent' header is not present in the dumped_settings default headers assert USER_AGENT not in dumped_settings["default_headers"] diff --git a/python/tests/unit/connectors/open_ai/services/test_openai_text_completion.py b/python/tests/unit/connectors/open_ai/services/test_openai_text_completion.py index f1e06161e2..4be7199cf7 100644 --- a/python/tests/unit/connectors/open_ai/services/test_openai_text_completion.py +++ b/python/tests/unit/connectors/open_ai/services/test_openai_text_completion.py @@ -2,101 +2,78 @@ import pytest -from pydantic import ValidationError from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion import OpenAITextCompletion from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase +from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError -def test_open_ai_text_completion_init() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" - +def test_open_ai_text_completion_init(openai_unit_test_env) -> None: # Test successful initialization - open_ai_text_completion = OpenAITextCompletion( - ai_model_id=ai_model_id, - api_key=api_key, - ) + open_ai_text_completion = OpenAITextCompletion() + + assert open_ai_text_completion.ai_model_id == openai_unit_test_env["OPENAI_TEXT_MODEL_ID"] + assert isinstance(open_ai_text_completion, TextCompletionClientBase) + + +def test_open_ai_text_completion_init_with_ai_model_id(openai_unit_test_env) -> None: + # Test successful initialization + ai_model_id = "test_model_id" + open_ai_text_completion = OpenAITextCompletion(ai_model_id=ai_model_id) assert open_ai_text_completion.ai_model_id == ai_model_id assert isinstance(open_ai_text_completion, TextCompletionClientBase) -def test_open_ai_text_completion_init_with_default_header() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" +def test_open_ai_text_completion_init_with_default_header(openai_unit_test_env) -> None: default_headers = {"X-Unit-Test": "test-guid"} # Test successful initialization open_ai_text_completion = OpenAITextCompletion( - ai_model_id=ai_model_id, - api_key=api_key, default_headers=default_headers, ) - assert open_ai_text_completion.ai_model_id == ai_model_id + assert open_ai_text_completion.ai_model_id == openai_unit_test_env["OPENAI_TEXT_MODEL_ID"] assert isinstance(open_ai_text_completion, TextCompletionClientBase) for key, value in default_headers.items(): assert key in open_ai_text_completion.client.default_headers assert open_ai_text_completion.client.default_headers[key] == value -def test_open_ai_text_completion_init_with_empty_model_id() -> None: - # ai_model_id = "test_model_id" - api_key = "test_api_key" - - with pytest.raises(ValidationError, match="ai_model_id"): - OpenAITextCompletion( - ai_model_id="", - api_key=api_key, - ) +@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True) +def test_open_ai_text_completion_init_with_empty_api_key(openai_unit_test_env) -> None: + with pytest.raises(ServiceInitializationError): + OpenAITextCompletion() -def test_open_ai_text_completion_init_with_empty_api_key() -> None: - ai_model_id = "test_model_id" - # api_key = "test_api_key" - - with pytest.raises(ValidationError, match="api_key"): - OpenAITextCompletion( - ai_model_id=ai_model_id, - api_key="", - ) - - -def test_open_ai_text_completion_serialize() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" +def test_open_ai_text_completion_serialize(openai_unit_test_env) -> None: default_headers = {"X-Unit-Test": "test-guid"} settings = { - "ai_model_id": ai_model_id, - "api_key": api_key, + "ai_model_id": openai_unit_test_env["OPENAI_TEXT_MODEL_ID"], + "api_key": openai_unit_test_env["OPENAI_API_KEY"], "default_headers": default_headers, } open_ai_text_completion = OpenAITextCompletion.from_dict(settings) dumped_settings = open_ai_text_completion.to_dict() - assert dumped_settings["ai_model_id"] == ai_model_id - assert dumped_settings["api_key"] == api_key + assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_TEXT_MODEL_ID"] + assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"] # Assert that the default header we added is present in the dumped_settings default headers for key, value in default_headers.items(): assert key in dumped_settings["default_headers"] assert dumped_settings["default_headers"][key] == value -def test_open_ai_text_completion_serialize_with_org_id() -> None: - ai_model_id = "test_model_id" - api_key = "test_api_key" - org_id = "test_org_id" - +def test_open_ai_text_completion_serialize_with_org_id(openai_unit_test_env) -> None: settings = { - "ai_model_id": ai_model_id, - "api_key": api_key, - "org_id": org_id, + "ai_model_id": openai_unit_test_env["OPENAI_TEXT_MODEL_ID"], + "api_key": openai_unit_test_env["OPENAI_API_KEY"], + "org_id": openai_unit_test_env["OPENAI_ORG_ID"], } open_ai_text_completion = OpenAITextCompletion.from_dict(settings) dumped_settings = open_ai_text_completion.to_dict() - assert dumped_settings["ai_model_id"] == ai_model_id - assert dumped_settings["api_key"] == api_key - assert dumped_settings["org_id"] == org_id + assert dumped_settings["ai_model_id"] == openai_unit_test_env["OPENAI_TEXT_MODEL_ID"] + assert dumped_settings["api_key"] == openai_unit_test_env["OPENAI_API_KEY"] + assert dumped_settings["org_id"] == openai_unit_test_env["OPENAI_ORG_ID"] diff --git a/python/tests/unit/connectors/open_ai/services/test_openai_text_embedding.py b/python/tests/unit/connectors/open_ai/services/test_openai_text_embedding.py index 4dac491305..533493c162 100644 --- a/python/tests/unit/connectors/open_ai/services/test_openai_text_embedding.py +++ b/python/tests/unit/connectors/open_ai/services/test_openai_text_embedding.py @@ -10,15 +10,13 @@ from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding impor @pytest.mark.asyncio @patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock) -async def test_openai_text_embedding_calls_with_parameters(mock_create) -> None: +async def test_openai_text_embedding_calls_with_parameters(mock_create, openai_unit_test_env) -> None: ai_model_id = "test_model_id" - api_key = "test_api_key" texts = ["hello world", "goodbye world"] embedding_dimensions = 1536 openai_text_embedding = OpenAITextEmbedding( ai_model_id=ai_model_id, - api_key=api_key, ) await openai_text_embedding.generate_embeddings(texts, dimensions=embedding_dimensions) diff --git a/python/tests/unit/core_plugins/test_sessions_python_plugin.py b/python/tests/unit/core_plugins/test_sessions_python_plugin.py index 2c2daf0c9e..86a867fa8d 100644 --- a/python/tests/unit/core_plugins/test_sessions_python_plugin.py +++ b/python/tests/unit/core_plugins/test_sessions_python_plugin.py @@ -16,21 +16,19 @@ def test_auth_callback(): return "sample_token" -def test_it_can_be_instantiated(): - plugin = SessionsPythonTool(pool_management_endpoint="https://example.com", auth_callback=test_auth_callback) +def test_it_can_be_instantiated(aca_python_sessions_unit_test_env): + plugin = SessionsPythonTool(auth_callback=test_auth_callback) assert plugin is not None -def test_validate_endpoint(): - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/execute/", auth_callback=test_auth_callback - ) +def test_validate_endpoint(aca_python_sessions_unit_test_env): + plugin = SessionsPythonTool(auth_callback=test_auth_callback) assert plugin is not None - assert plugin.pool_management_endpoint == "https://example.com/" + assert plugin.pool_management_endpoint == aca_python_sessions_unit_test_env["ACA_POOL_MANAGEMENT_ENDPOINT"] -def test_it_can_be_imported(kernel: Kernel): - plugin = SessionsPythonTool(pool_management_endpoint="https://example.com", auth_callback=test_auth_callback) +def test_it_can_be_imported(kernel: Kernel, aca_python_sessions_unit_test_env): + plugin = SessionsPythonTool(auth_callback=test_auth_callback) assert kernel.add_plugin(plugin=plugin, plugin_name="PythonCodeInterpreter") assert kernel.plugins["PythonCodeInterpreter"] is not None assert kernel.plugins["PythonCodeInterpreter"].name == "PythonCodeInterpreter" @@ -38,7 +36,7 @@ def test_it_can_be_imported(kernel: Kernel): @pytest.mark.asyncio @patch("httpx.AsyncClient.post") -async def test_call_to_container_succeeds(mock_post): +async def test_call_to_container_succeeds(mock_post, aca_python_sessions_unit_test_env): async def async_return(result): return result @@ -54,9 +52,7 @@ async def test_call_to_container_succeeds(mock_post): mock_post.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/execute/", auth_callback=test_auth_callback - ) + plugin = SessionsPythonTool(auth_callback=test_auth_callback) result = await plugin.execute_code("print('hello world')") assert result is not None @@ -65,7 +61,7 @@ async def test_call_to_container_succeeds(mock_post): @pytest.mark.asyncio @patch("httpx.AsyncClient.post") -async def test_call_to_container_fails_raises_exception(mock_post): +async def test_call_to_container_fails_raises_exception(mock_post, aca_python_sessions_unit_test_env): async def async_return(result): return result @@ -79,9 +75,7 @@ async def test_call_to_container_fails_raises_exception(mock_post): mock_post.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/execute/", auth_callback=test_auth_callback - ) + plugin = SessionsPythonTool(auth_callback=test_auth_callback) with pytest.raises(Exception): _ = await plugin.execute_code("print('hello world')") @@ -89,7 +83,7 @@ async def test_call_to_container_fails_raises_exception(mock_post): @pytest.mark.asyncio @patch("httpx.AsyncClient.post") -async def test_upload_file_with_local_path(mock_post): +async def test_upload_file_with_local_path(mock_post, aca_python_sessions_unit_test_env): """Test upload_file when providing a local file path.""" async def async_return(result): @@ -106,9 +100,7 @@ async def test_upload_file_with_local_path(mock_post): ) mock_post.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=lambda: "sample_token" - ) + plugin = SessionsPythonTool(auth_callback=lambda: "sample_token") result = await plugin.upload_file(local_file_path="test.txt", remote_file_path="uploaded_test.txt") assert result.filename == "test.txt" @@ -118,7 +110,7 @@ async def test_upload_file_with_local_path(mock_post): @pytest.mark.asyncio @patch("httpx.AsyncClient.post") -async def test_upload_file_with_buffer(mock_post): +async def test_upload_file_with_buffer(mock_post, aca_python_sessions_unit_test_env): """Test upload_file when providing file data as a BufferedReader.""" async def async_return(result): @@ -135,9 +127,7 @@ async def test_upload_file_with_buffer(mock_post): ) mock_post.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=lambda: "sample_token" - ) + plugin = SessionsPythonTool(auth_callback=lambda: "sample_token") data_buffer = BufferedReader(BytesIO(b"file data")) @@ -149,7 +139,7 @@ async def test_upload_file_with_buffer(mock_post): @pytest.mark.asyncio @patch("httpx.AsyncClient.get") -async def test_list_files(mock_get): +async def test_list_files(mock_get, aca_python_sessions_unit_test_env): """Test list_files function.""" async def async_return(result): @@ -174,9 +164,7 @@ async def test_list_files(mock_get): ) mock_get.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=lambda: "sample_token" - ) + plugin = SessionsPythonTool(auth_callback=lambda: "sample_token") files = await plugin.list_files() assert len(files) == 2 @@ -189,7 +177,7 @@ async def test_list_files(mock_get): @pytest.mark.asyncio @patch("httpx.AsyncClient.get") -async def test_download_file_to_local(mock_get): +async def test_download_file_to_local(mock_get, aca_python_sessions_unit_test_env): """Test download_file when saving to a local file path.""" async def async_return(result): @@ -209,9 +197,7 @@ async def test_download_file_to_local(mock_get): mock_response = httpx.Response(status_code=200, content=b"file data", request=mock_request) mock_get.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=mock_auth_callback - ) + plugin = SessionsPythonTool(auth_callback=mock_auth_callback) await plugin.download_file(remote_file_path="remote_test.txt", local_file_path="local_test.txt") mock_get.assert_awaited_once() @@ -221,7 +207,7 @@ async def test_download_file_to_local(mock_get): @pytest.mark.asyncio @patch("httpx.AsyncClient.get") -async def test_download_file_to_buffer(mock_get): +async def test_download_file_to_buffer(mock_get, aca_python_sessions_unit_test_env): """Test download_file when returning as a BufferedReader.""" async def async_return(result): @@ -241,9 +227,7 @@ async def test_download_file_to_buffer(mock_get): mock_response = httpx.Response(status_code=200, content=b"file data", request=mock_request) mock_get.return_value = await async_return(mock_response) - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=mock_auth_callback - ) + plugin = SessionsPythonTool(auth_callback=mock_auth_callback) buffer = await plugin.download_file(remote_file_path="remote_test.txt") assert buffer is not None @@ -274,10 +258,8 @@ async def test_download_file_to_buffer(mock_get): (" ", ""), ], ) -def test_sanitize_input(input_code, expected_output): +def test_sanitize_input(input_code, expected_output, aca_python_sessions_unit_test_env): """Test the `_sanitize_input` function with various inputs.""" - plugin = SessionsPythonTool( - pool_management_endpoint="https://example.com/python/", auth_callback=lambda: "sample_token" - ) + plugin = SessionsPythonTool(auth_callback=lambda: "sample_token") sanitized_code = plugin._sanitize_input(input_code) assert sanitized_code == expected_output diff --git a/python/tests/unit/functions/test_kernel_function_from_method.py b/python/tests/unit/functions/test_kernel_function_from_method.py index b521202cbe..0fe816c850 100644 --- a/python/tests/unit/functions/test_kernel_function_from_method.py +++ b/python/tests/unit/functions/test_kernel_function_from_method.py @@ -191,9 +191,9 @@ async def test_invoke_gen_async(): @pytest.mark.asyncio -async def test_service_execution(): +async def test_service_execution(openai_unit_test_env): kernel = Kernel() - service = OpenAIChatCompletion(service_id="test", ai_model_id="test", api_key="test") + service = OpenAIChatCompletion(service_id="test", ai_model_id="test") req_settings = service.get_prompt_execution_settings_class()(service_id="test") req_settings.temperature = 0.5 kernel.add_service(service) diff --git a/python/tests/unit/functions/test_kernel_function_from_prompt.py b/python/tests/unit/functions/test_kernel_function_from_prompt.py index 506f8393d5..3f285da917 100644 --- a/python/tests/unit/functions/test_kernel_function_from_prompt.py +++ b/python/tests/unit/functions/test_kernel_function_from_prompt.py @@ -140,9 +140,9 @@ def test_init_prompt_execution_settings_dict(): @pytest.mark.asyncio -async def test_invoke_chat_stream(): +async def test_invoke_chat_stream(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", @@ -169,9 +169,9 @@ async def test_invoke_chat_stream(): @pytest.mark.asyncio -async def test_invoke_exception(): +async def test_invoke_exception(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", @@ -198,9 +198,9 @@ async def test_invoke_exception(): @pytest.mark.asyncio -async def test_invoke_text(): +async def test_invoke_text(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", @@ -223,9 +223,9 @@ async def test_invoke_text(): @pytest.mark.asyncio -async def test_invoke_exception_text(): +async def test_invoke_exception_text(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAITextCompletion(service_id="test", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", @@ -250,9 +250,9 @@ async def test_invoke_exception_text(): @pytest.mark.asyncio -async def test_invoke_defaults(): +async def test_invoke_defaults(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAIChatCompletion(service_id="test", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", @@ -291,9 +291,9 @@ def test_create_with_multiple_settings(): @pytest.mark.asyncio -async def test_create_with_multiple_settings_one_service_registered(): +async def test_create_with_multiple_settings_one_service_registered(openai_unit_test_env): kernel = Kernel() - kernel.add_service(OpenAIChatCompletion(service_id="test2", ai_model_id="test", api_key="test")) + kernel.add_service(OpenAIChatCompletion(service_id="test2", ai_model_id="test")) function = KernelFunctionFromPrompt( function_name="test", plugin_name="test", diff --git a/python/tests/unit/memory/test_azure_cognitive_search_memory_store.py b/python/tests/unit/memory/test_azure_cognitive_search_memory_store.py index 36561e4697..f667068158 100644 --- a/python/tests/unit/memory/test_azure_cognitive_search_memory_store.py +++ b/python/tests/unit/memory/test_azure_cognitive_search_memory_store.py @@ -11,7 +11,7 @@ from semantic_kernel.connectors.memory.azure_cognitive_search import AzureCognit @pytest.fixture -def azure_cognitive_search_memory_store(): +def azure_cognitive_search_memory_store(azure_ai_search_unit_test_env): """Fixture to instantiate AzureCognitiveSearchMemoryStore with basic configuration.""" store = AzureCognitiveSearchMemoryStore( 1536, "https://test.search.windows.net", azure_credentials=AzureKeyCredential("test_key")