chore: import upstream snapshot with attribution
CodeQL / Analyze (csharp) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:21:23 +08:00
commit b957a53def
5423 changed files with 863745 additions and 0 deletions
@@ -0,0 +1,99 @@
# Copyright (c) Microsoft. All rights reserved.
import os
from unittest.mock import AsyncMock, patch
import pytest
from openai import AsyncAzureOpenAI
from openai.resources.audio.transcriptions import AsyncTranscriptions
from openai.types.audio import Transcription
from semantic_kernel.connectors.ai.open_ai import AzureAudioToText
from semantic_kernel.contents import AudioContent
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError, ServiceInvalidRequestError
def test_azure_audio_to_text_init(azure_openai_unit_test_env) -> None:
azure_audio_to_text = AzureAudioToText()
assert azure_audio_to_text.client is not None
assert isinstance(azure_audio_to_text.client, AsyncAzureOpenAI)
assert azure_audio_to_text.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME"]
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME"]], indirect=True)
def test_azure_audio_to_text_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="The Azure OpenAI audio to text deployment name is required."):
AzureAudioToText(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_audio_to_text_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureAudioToText(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_azure_audio_to_text_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="Please provide an endpoint or a base_url"):
AzureAudioToText(env_file_path="test.env")
@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
def test_azure_audio_to_text_init_with_invalid_http_endpoint(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="Invalid settings: "):
AzureAudioToText()
@pytest.mark.parametrize(
"override_env_param_dict",
[{"AZURE_OPENAI_BASE_URL": "https://test_audio_to_text_deployment.test-base-url.com"}],
indirect=True,
)
def test_azure_audio_to_text_init_with_from_dict(azure_openai_unit_test_env) -> None:
default_headers = {"test_header": "test_value"}
settings = {
"deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_AUDIO_TO_TEXT_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,
}
azure_audio_to_text = AzureAudioToText.from_dict(settings=settings)
assert azure_audio_to_text.client is not None
assert isinstance(azure_audio_to_text.client, AsyncAzureOpenAI)
assert azure_audio_to_text.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME"]
assert settings["deployment_name"] in str(azure_audio_to_text.client.base_url)
assert azure_audio_to_text.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():
assert key in azure_audio_to_text.client.default_headers
assert azure_audio_to_text.client.default_headers[key] == value
async def test_azure_audio_to_text_get_text_contents(azure_openai_unit_test_env) -> None:
audio_content = AudioContent.from_audio_file(
os.path.join(os.path.dirname(__file__), "../../../../../", "assets/sample_audio.mp3")
)
with patch.object(AsyncTranscriptions, "create", new_callable=AsyncMock) as mock_transcription_create:
mock_transcription_create.return_value = Transcription(text="This is a test audio file.")
openai_audio_to_text = AzureAudioToText()
text_contents = await openai_audio_to_text.get_text_contents(audio_content)
assert len(text_contents) == 1
assert text_contents[0].text == "This is a test audio file."
assert text_contents[0].ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME"]
async def test_azure_audio_to_text_get_text_contents_invalid_audio_content(azure_openai_unit_test_env):
audio_content = AudioContent()
openai_audio_to_text = AzureAudioToText()
with pytest.raises(ServiceInvalidRequestError, match="Audio content uri must be a string to a local file."):
await openai_audio_to_text.get_text_contents(audio_content)
@@ -0,0 +1,960 @@
# Copyright (c) Microsoft. All rights reserved.
import json
import os
from copy import deepcopy
from unittest.mock import AsyncMock, MagicMock, patch
import openai
import pytest
from httpx import Request, Response
from openai import AsyncAzureOpenAI, AsyncStream
from openai.resources.chat.completions import AsyncCompletions as AsyncChatCompletions
from openai.types.chat import ChatCompletion, ChatCompletionChunk
from openai.types.chat.chat_completion import Choice
from openai.types.chat.chat_completion_chunk import Choice as ChunkChoice
from openai.types.chat.chat_completion_chunk import ChoiceDelta as ChunkChoiceDelta
from openai.types.chat.chat_completion_message import ChatCompletionMessage
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.function_choice_behavior import FunctionChoiceBehavior
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.open_ai.exceptions.content_filter_ai_exception import (
ContentFilterAIException,
ContentFilterResultSeverity,
)
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureChatPromptExecutionSettings,
)
from semantic_kernel.const import USER_AGENT
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.contents.function_call_content import FunctionCallContent
from semantic_kernel.contents.function_result_content import FunctionResultContent
from semantic_kernel.contents.text_content import TextContent
from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvalidExecutionSettingsError
from semantic_kernel.exceptions.service_exceptions import ServiceResponseException
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.functions.kernel_function_decorator import kernel_function
from semantic_kernel.kernel import Kernel
# region Service Setup
def test_init(azure_openai_unit_test_env) -> None:
# Test successful initialization
azure_chat_completion = AzureChatCompletion(service_id="test_service_id")
assert azure_chat_completion.client is not None
assert isinstance(azure_chat_completion.client, AsyncAzureOpenAI)
assert azure_chat_completion.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
assert isinstance(azure_chat_completion, ChatCompletionClientBase)
assert azure_chat_completion.get_prompt_execution_settings_class() == AzureChatPromptExecutionSettings
def test_init_client(azure_openai_unit_test_env) -> None:
# Test successful initialization with client
client = MagicMock(spec=AsyncAzureOpenAI)
azure_chat_completion = AzureChatCompletion(async_client=client)
assert azure_chat_completion.client is not None
assert isinstance(azure_chat_completion.client, AsyncAzureOpenAI)
def test_init_base_url(azure_openai_unit_test_env) -> None:
# Custom header for testing
default_headers = {"X-Unit-Test": "test-guid"}
azure_chat_completion = AzureChatCompletion(
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 == 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
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_init_endpoint(azure_openai_unit_test_env) -> None:
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 == azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]
assert isinstance(azure_chat_completion, ChatCompletionClientBase)
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]], indirect=True)
def test_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
def test_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion()
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_serialize(azure_openai_unit_test_env) -> None:
default_headers = {"X-Test": "test"}
settings = {
"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,
}
azure_chat_completion = AzureChatCompletion.from_dict(settings)
dumped_settings = azure_chat_completion.to_dict()
assert dumped_settings["ai_model_id"] == settings["deployment_name"]
assert settings["endpoint"] in str(dumped_settings["base_url"])
assert settings["deployment_name"] in str(dumped_settings["base_url"])
assert settings["api_key"] == dumped_settings["api_key"]
assert settings["api_version"] == dumped_settings["api_version"]
# 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
# Assert that the 'User-agent' header is not present in the dumped_settings default headers
assert USER_AGENT not in dumped_settings["default_headers"]
# endregion
# region CMC
@pytest.fixture
def mock_chat_completion_response() -> ChatCompletion:
return ChatCompletion(
id="test_id",
choices=[
Choice(index=0, message=ChatCompletionMessage(content="test", role="assistant"), finish_reason="stop")
],
created=0,
model="test",
object="chat.completion",
)
@pytest.fixture
def mock_streaming_chat_completion_response() -> AsyncStream[ChatCompletionChunk]:
content = ChatCompletionChunk(
id="test_id",
choices=[ChunkChoice(index=0, delta=ChunkChoiceDelta(content="test", role="assistant"), finish_reason="stop")],
created=0,
model="test",
object="chat.completion.chunk",
)
stream = MagicMock(spec=AsyncStream)
stream.__aiter__.return_value = [content]
return stream
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_create.return_value = mock_chat_completion_response
chat_history.add_user_message("hello world")
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(service_id="test_service_id")
azure_chat_completion = AzureChatCompletion()
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=False,
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_with_developer_instruction_role_propagates(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_create.return_value = mock_chat_completion_response
chat_history.add_user_message("hello world")
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(service_id="test_service_id")
azure_chat_completion = AzureChatCompletion(instruction_role="developer")
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=False,
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
)
assert azure_chat_completion.instruction_role == "developer"
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_with_logit_bias(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
chat_history.add_user_message(prompt)
complete_prompt_execution_settings = AzureChatPromptExecutionSettings()
token_bias = {"1": -100}
complete_prompt_execution_settings.logit_bias = token_bias
azure_chat_completion = AzureChatCompletion()
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
stream=False,
logit_bias=token_bias,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_with_stop(
mock_create,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_create.return_value = mock_chat_completion_response
complete_prompt_execution_settings = AzureChatPromptExecutionSettings()
stop = ["!"]
complete_prompt_execution_settings.stop = stop
azure_chat_completion = AzureChatCompletion()
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
stream=False,
stop=stop,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_azure_on_your_data(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content="test",
role="assistant",
context={
"citations": {
"content": "test content",
"title": "test title",
"url": "test url",
"filepath": "test filepath",
"chunk_id": "test chunk_id",
},
"intent": "query used",
},
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
messages_in = chat_history
messages_in.add_user_message(prompt)
messages_out = ChatHistory()
messages_out.add_user_message(prompt)
expected_data_settings = {
"data_sources": [
{
"type": "AzureCognitiveSearch",
"parameters": {
"indexName": "test_index",
"endpoint": "https://test-endpoint-search.com",
"key": "test_key",
},
}
]
}
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(extra_body=expected_data_settings)
azure_chat_completion = AzureChatCompletion()
content = await azure_chat_completion.get_chat_message_contents(
chat_history=messages_in, settings=complete_prompt_execution_settings, kernel=kernel
)
assert isinstance(content[0].items[0], FunctionCallContent)
assert isinstance(content[0].items[1], FunctionResultContent)
assert isinstance(content[0].items[2], TextContent)
assert content[0].items[2].text == "test"
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(messages_out),
stream=False,
extra_body=expected_data_settings,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_azure_on_your_data_string(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content="test",
role="assistant",
context=json.dumps({
"citations": {
"content": "test content",
"title": "test title",
"url": "test url",
"filepath": "test filepath",
"chunk_id": "test chunk_id",
},
"intent": "query used",
}),
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
messages_in = chat_history
messages_in.add_user_message(prompt)
messages_out = ChatHistory()
messages_out.add_user_message(prompt)
expected_data_settings = {
"data_sources": [
{
"type": "AzureCognitiveSearch",
"parameters": {
"indexName": "test_index",
"endpoint": "https://test-endpoint-search.com",
"key": "test_key",
},
}
]
}
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(extra_body=expected_data_settings)
azure_chat_completion = AzureChatCompletion()
content = await azure_chat_completion.get_chat_message_contents(
chat_history=messages_in, settings=complete_prompt_execution_settings, kernel=kernel
)
assert isinstance(content[0].items[0], FunctionCallContent)
assert isinstance(content[0].items[1], FunctionResultContent)
assert isinstance(content[0].items[2], TextContent)
assert content[0].items[2].text == "test"
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(messages_out),
stream=False,
extra_body=expected_data_settings,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_azure_on_your_data_fail(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content="test",
role="assistant",
context="not a dictionary",
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
messages_in = chat_history
messages_in.add_user_message(prompt)
messages_out = ChatHistory()
messages_out.add_user_message(prompt)
expected_data_settings = {
"data_sources": [
{
"type": "AzureCognitiveSearch",
"parameters": {
"indexName": "test_index",
"endpoint": "https://test-endpoint-search.com",
"key": "test_key",
},
}
]
}
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(extra_body=expected_data_settings)
azure_chat_completion = AzureChatCompletion()
content = await azure_chat_completion.get_chat_message_contents(
chat_history=messages_in, settings=complete_prompt_execution_settings, kernel=kernel
)
assert isinstance(content[0].items[0], TextContent)
assert content[0].items[0].text == "test"
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(messages_out),
stream=False,
extra_body=expected_data_settings,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_azure_on_your_data_split_messages(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content="test",
role="assistant",
context={
"citations": {
"content": "test content",
"title": "test title",
"url": "test url",
"filepath": "test filepath",
"chunk_id": "test chunk_id",
},
"intent": "query used",
},
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
messages_in = chat_history
messages_in.add_user_message(prompt)
messages_out = ChatHistory()
messages_out.add_user_message(prompt)
complete_prompt_execution_settings = AzureChatPromptExecutionSettings()
azure_chat_completion = AzureChatCompletion()
content = await azure_chat_completion.get_chat_message_contents(
chat_history=messages_in, settings=complete_prompt_execution_settings, kernel=kernel
)
messages = azure_chat_completion.split_message(content[0])
assert len(messages) == 3
assert isinstance(messages[0].items[0], FunctionCallContent)
assert isinstance(messages[1].items[0], FunctionResultContent)
assert isinstance(messages[2].items[0], TextContent)
assert messages[2].items[0].text == "test"
message = azure_chat_completion.split_message(messages[0])
assert message == [messages[0]]
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_function_calling(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content=None,
role="assistant",
function_call={"name": "test-function", "arguments": '{"key": "value"}'},
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
chat_history.add_user_message(prompt)
azure_chat_completion = AzureChatCompletion()
functions = [{"name": "test-function", "description": "test-description"}]
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(
function_call="test-function",
functions=functions,
)
content = await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history,
settings=complete_prompt_execution_settings,
kernel=kernel,
)
assert isinstance(content[0].items[0], FunctionCallContent)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
stream=False,
functions=functions,
function_call=complete_prompt_execution_settings.function_call,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_tool_calling(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content=None,
role="assistant",
tool_calls=[
{
"id": "test id",
"function": {"name": "test-tool", "arguments": '{"key": "value"}'},
"type": "function",
}
],
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
chat_history.add_user_message(prompt)
azure_chat_completion = AzureChatCompletion()
complete_prompt_execution_settings = AzureChatPromptExecutionSettings()
content = await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history,
settings=complete_prompt_execution_settings,
kernel=kernel,
)
assert isinstance(content[0].items[0], FunctionCallContent)
assert content[0].items[0].id == "test id"
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
stream=False,
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_tool_calling_parallel_tool_calls(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content=None,
role="assistant",
tool_calls=[
{
"id": "test id",
"function": {"name": "test-tool", "arguments": '{"key": "value"}'},
"type": "function",
}
],
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
chat_history.add_user_message(prompt)
class MockPlugin:
@kernel_function(name="test_tool")
def test_tool(self, key: str):
return "test"
kernel.add_plugin(MockPlugin(), plugin_name="test_tool")
orig_chat_history = deepcopy(chat_history)
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(
service_id="test_service_id", function_choice_behavior=FunctionChoiceBehavior.Auto()
)
with patch(
"semantic_kernel.kernel.Kernel.invoke_function_call",
new_callable=AsyncMock,
) as mock_process_function_call:
azure_chat_completion = AzureChatCompletion()
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history,
settings=complete_prompt_execution_settings,
kernel=kernel,
arguments=KernelArguments(),
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=False,
messages=azure_chat_completion._prepare_chat_history_for_request(orig_chat_history),
tools=[
{
"type": "function",
"function": {
"name": "test_tool-test_tool",
"description": "",
"parameters": {
"type": "object",
"properties": {"key": {"type": "string"}},
"required": ["key"],
},
},
}
],
tool_choice="auto",
)
mock_process_function_call.assert_awaited()
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_tool_calling_parallel_tool_calls_disabled(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_chat_completion_response: ChatCompletion,
) -> None:
mock_chat_completion_response.choices = [
Choice(
index=0,
message=ChatCompletionMessage(
content=None,
role="assistant",
tool_calls=[
{
"id": "test id",
"function": {"name": "test-tool", "arguments": '{"key": "value"}'},
"type": "function",
}
],
),
finish_reason="stop",
)
]
mock_create.return_value = mock_chat_completion_response
prompt = "hello world"
chat_history.add_user_message(prompt)
class MockPlugin:
@kernel_function(name="test_tool")
def test_tool(self, key: str):
return "test"
kernel.add_plugin(MockPlugin(), plugin_name="test_tool")
orig_chat_history = deepcopy(chat_history)
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(
service_id="test_service_id",
function_choice_behavior=FunctionChoiceBehavior.Auto(),
parallel_tool_calls=False,
)
with patch(
"semantic_kernel.kernel.Kernel.invoke_function_call",
new_callable=AsyncMock,
) as mock_process_function_call:
azure_chat_completion = AzureChatCompletion()
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history,
settings=complete_prompt_execution_settings,
kernel=kernel,
arguments=KernelArguments(),
)
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=False,
messages=azure_chat_completion._prepare_chat_history_for_request(orig_chat_history),
parallel_tool_calls=False,
tools=[
{
"type": "function",
"function": {
"name": "test_tool-test_tool",
"description": "",
"parameters": {
"type": "object",
"properties": {"key": {"type": "string"}},
"required": ["key"],
},
},
}
],
tool_choice="auto",
)
mock_process_function_call.assert_awaited()
CONTENT_FILTERED_ERROR_MESSAGE = (
"The response was filtered due to the prompt triggering Azure OpenAI's content management policy. Please "
"modify your prompt and retry. To learn more about our content filtering policies please read our "
"documentation: https://go.microsoft.com/fwlink/?linkid=2198766"
)
CONTENT_FILTERED_ERROR_FULL_MESSAGE = (
"Error code: 400 - {'error': {'message': \"%s\", 'type': null, 'param': 'prompt', 'code': 'content_filter', "
"'status': 400, 'innererror': {'code': 'ResponsibleAIPolicyViolation', 'content_filter_result': {'hate': "
"{'filtered': True, 'severity': 'high'}, 'self_harm': {'filtered': False, 'severity': 'safe'}, 'sexual': "
"{'filtered': False, 'severity': 'safe'}, 'violence': {'filtered': False, 'severity': 'safe'}}}}}"
) % CONTENT_FILTERED_ERROR_MESSAGE
@patch.object(AsyncChatCompletions, "create")
async def test_content_filtering_raises_correct_exception(
mock_create, kernel: Kernel, 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()
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
mock_create.side_effect = openai.BadRequestError(
CONTENT_FILTERED_ERROR_FULL_MESSAGE,
response=Response(400, request=Request("POST", test_endpoint)),
body={
"message": CONTENT_FILTERED_ERROR_MESSAGE,
"type": None,
"param": "prompt",
"code": "content_filter",
"status": 400,
"innererror": {
"code": "ResponsibleAIPolicyViolation",
"content_filter_result": {
"hate": {"filtered": True, "severity": "high"},
"self_harm": {"filtered": False, "severity": "safe"},
"sexual": {"filtered": False, "severity": "safe"},
"violence": {"filtered": False, "severity": "safe"},
},
},
},
)
azure_chat_completion = AzureChatCompletion()
with pytest.raises(ContentFilterAIException, match="service encountered a content error") as exc_info:
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
content_filter_exc = exc_info.value
assert content_filter_exc.param == "prompt"
assert content_filter_exc.content_filter_result["hate"].filtered
assert content_filter_exc.content_filter_result["hate"].severity == ContentFilterResultSeverity.HIGH
@patch.object(AsyncChatCompletions, "create")
async def test_content_filtering_without_response_code_raises_with_default_code(
mock_create, kernel: Kernel, 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()
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
mock_create.side_effect = openai.BadRequestError(
CONTENT_FILTERED_ERROR_FULL_MESSAGE,
response=Response(400, request=Request("POST", test_endpoint)),
body={
"message": CONTENT_FILTERED_ERROR_MESSAGE,
"type": None,
"param": "prompt",
"code": "content_filter",
"status": 400,
"innererror": {
"content_filter_result": {
"hate": {"filtered": True, "severity": "high"},
"self_harm": {"filtered": False, "severity": "safe"},
"sexual": {"filtered": False, "severity": "safe"},
"violence": {"filtered": False, "severity": "safe"},
},
},
},
)
azure_chat_completion = AzureChatCompletion()
with pytest.raises(ContentFilterAIException, match="service encountered a content error"):
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
@patch.object(AsyncChatCompletions, "create")
async def test_bad_request_non_content_filter(
mock_create, kernel: Kernel, 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()
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
mock_create.side_effect = openai.BadRequestError(
"The request was bad.", response=Response(400, request=Request("POST", test_endpoint)), body={}
)
azure_chat_completion = AzureChatCompletion()
with pytest.raises(ServiceResponseException, match="service failed to complete the prompt"):
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
)
@patch.object(AsyncChatCompletions, "create")
async def test_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_choice_behavior=FunctionChoiceBehavior.Auto()
)
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
mock_create.side_effect = openai.BadRequestError(
"The request was bad.", response=Response(400, request=Request("POST", test_endpoint)), body={}
)
azure_chat_completion = AzureChatCompletion()
with pytest.raises(
ServiceInvalidExecutionSettingsError,
match="The kernel is required for function calls.",
):
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings
)
@patch.object(AsyncChatCompletions, "create")
async def test_auto_invoke_false_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_choice_behavior=FunctionChoiceBehavior.Auto(auto_invoke=False)
)
test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
mock_create.side_effect = openai.BadRequestError(
"The request was bad.", response=Response(400, request=Request("POST", test_endpoint)), body={}
)
azure_chat_completion = AzureChatCompletion()
with pytest.raises(
ServiceInvalidExecutionSettingsError,
match="The kernel is required for function calls.",
):
await azure_chat_completion.get_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings
)
@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock)
async def test_cmc_streaming(
mock_create,
kernel: Kernel,
azure_openai_unit_test_env,
chat_history: ChatHistory,
mock_streaming_chat_completion_response: AsyncStream[ChatCompletionChunk],
) -> None:
mock_create.return_value = mock_streaming_chat_completion_response
chat_history.add_user_message("hello world")
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(service_id="test_service_id")
azure_chat_completion = AzureChatCompletion()
async for msg in azure_chat_completion.get_streaming_chat_message_contents(
chat_history=chat_history, settings=complete_prompt_execution_settings, kernel=kernel
):
assert msg is not None
mock_create.assert_awaited_once_with(
model=azure_openai_unit_test_env["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"],
stream=True,
messages=azure_chat_completion._prepare_chat_history_for_request(chat_history),
# NOTE: The `stream_options={"include_usage": True}` is explicitly enforced in
# `OpenAIChatCompletionBase._inner_get_streaming_chat_message_contents`.
# To ensure consistency, we align the arguments here accordingly.
stream_options={"include_usage": True},
)
@@ -0,0 +1,111 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import Mock, patch
import pytest
from openai import AsyncAzureOpenAI
from openai.types import Completion
from semantic_kernel.connectors.ai.open_ai.services.azure_text_completion import AzureTextCompletion
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.exceptions import ServiceInitializationError
@pytest.fixture
def mock_text_completion_response() -> Mock:
mock_response = Mock(spec=Completion)
mock_response.id = "test_id"
mock_response.created = "time"
mock_response.usage = None
mock_response.choices = []
return mock_response
def test_init(azure_openai_unit_test_env) -> None:
# Test successful initialization
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 == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"]
assert isinstance(azure_text_completion, TextCompletionClientBase)
def test_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(
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 == 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_embedding_generates_no_token_with_api_key_in_env(azure_openai_unit_test_env) -> None:
with (
patch(
"semantic_kernel.utils.authentication.entra_id_authentication.get_entra_auth_token",
) as mock_get_token,
):
azure_text_completion = AzureTextCompletion()
assert azure_text_completion.client is not None
# API key is provided in env var, so the ad_token should be None
assert mock_get_token.call_count == 0
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"]], indirect=True)
def test_init_with_empty_deployment_name(monkeypatch, azure_openai_unit_test_env) -> None:
monkeypatch.delenv("AZURE_OPENAI_TEXT_DEPLOYMENT_NAME", raising=False)
with pytest.raises(ServiceInitializationError):
AzureTextCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
def test_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextCompletion()
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_serialize(azure_openai_unit_test_env) -> None:
default_headers = {"X-Test": "test"}
settings = {
"deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_TEXT_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,
}
azure_text_completion = AzureTextCompletion.from_dict(settings)
dumped_settings = azure_text_completion.to_dict()
assert dumped_settings["ai_model_id"] == settings["deployment_name"]
assert settings["endpoint"] in str(dumped_settings["base_url"])
assert settings["deployment_name"] in str(dumped_settings["base_url"])
assert settings["api_key"] == dumped_settings["api_key"]
assert settings["api_version"] == dumped_settings["api_version"]
# 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
@@ -0,0 +1,126 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import AsyncMock, call, patch
import pytest
from openai import AsyncAzureOpenAI
from openai.resources.embeddings import AsyncEmbeddings
from semantic_kernel.connectors.ai.embedding_generator_base import EmbeddingGeneratorBase
from semantic_kernel.connectors.ai.open_ai.services.azure_text_embedding import AzureTextEmbedding
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_azure_text_embedding_init(azure_openai_unit_test_env) -> None:
# Test successful initialization
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 == azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"]
assert isinstance(azure_text_embedding, EmbeddingGeneratorBase)
@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(ServiceInitializationError):
AzureTextEmbedding(
env_file_path="test.env",
)
@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(
env_file_path="test.env",
)
@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()
@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": 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,
}
azure_text_embedding = AzureTextEmbedding.from_dict(settings=settings)
assert azure_text_embedding.client is not None
assert isinstance(azure_text_embedding.client, AsyncAzureOpenAI)
assert azure_text_embedding.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"]
assert isinstance(azure_text_embedding, EmbeddingGeneratorBase)
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():
assert key in azure_text_embedding.client.default_headers
assert azure_text_embedding.client.default_headers[key] == value
def test_azure_text_embedding_generates_no_token_with_api_key_in_env(azure_openai_unit_test_env) -> None:
with (
patch(
"semantic_kernel.utils.authentication.entra_id_authentication.get_entra_auth_token",
) as mock_get_token,
):
azure_text_embedding = AzureTextEmbedding()
assert azure_text_embedding.client is not None
# API key is provided in env var, so the ad_token should be None
assert mock_get_token.call_count == 0
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock)
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()
await azure_text_embedding.generate_embeddings(texts, dimensions=embedding_dimensions)
mock_create.assert_awaited_once_with(
input=texts,
model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
dimensions=embedding_dimensions,
)
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock)
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()
await azure_text_embedding.generate_embeddings(texts, batch_size=3)
mock_create.assert_has_awaits(
[
call(
model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
input=texts[0:3],
),
call(
model=azure_openai_unit_test_env["AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME"],
input=texts[3:5],
),
],
any_order=False,
)
@@ -0,0 +1,82 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import patch
import httpx
import pytest
from openai import AsyncAzureOpenAI, _legacy_response
from openai.resources.audio.speech import AsyncSpeech
from semantic_kernel.connectors.ai.open_ai import AzureTextToAudio
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_azure_text_to_audio_init(azure_openai_unit_test_env) -> None:
azure_text_to_audio = AzureTextToAudio()
assert azure_text_to_audio.client is not None
assert isinstance(azure_text_to_audio.client, AsyncAzureOpenAI)
assert azure_text_to_audio.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME"]
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME"]], indirect=True)
def test_azure_text_to_audio_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="The Azure OpenAI text to audio deployment name is required."):
AzureTextToAudio(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_text_to_audio_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextToAudio(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_azure_text_to_audio_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="Please provide an endpoint or a base_url"):
AzureTextToAudio(env_file_path="test.env")
@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
def test_azure_text_to_audio_init_with_invalid_http_endpoint(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="Invalid settings: "):
AzureTextToAudio()
@pytest.mark.parametrize(
"override_env_param_dict",
[{"AZURE_OPENAI_BASE_URL": "https://test_text_to_audio_deployment.test-base-url.com"}],
indirect=True,
)
def test_azure_text_to_audio_init_with_from_dict(azure_openai_unit_test_env) -> None:
default_headers = {"test_header": "test_value"}
settings = {
"deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_AUDIO_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,
}
azure_text_to_audio = AzureTextToAudio.from_dict(settings=settings)
assert azure_text_to_audio.client is not None
assert isinstance(azure_text_to_audio.client, AsyncAzureOpenAI)
assert azure_text_to_audio.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME"]
assert settings["deployment_name"] in str(azure_text_to_audio.client.base_url)
assert azure_text_to_audio.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():
assert key in azure_text_to_audio.client.default_headers
assert azure_text_to_audio.client.default_headers[key] == value
@patch.object(AsyncSpeech, "create", return_value=_legacy_response.HttpxBinaryResponseContent(httpx.Response(200)))
async def test_azure_text_to_audio_get_audio_contents(mock_speech_create, azure_openai_unit_test_env) -> None:
openai_audio_to_text = AzureTextToAudio()
audio_contents = await openai_audio_to_text.get_audio_contents("Hello World!")
assert len(audio_contents) == 1
assert audio_contents[0].ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME"]
@@ -0,0 +1,99 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import AsyncMock, patch
import pytest
from openai import AsyncAzureOpenAI
from openai.resources.images import AsyncImages
from openai.types.image import Image
from openai.types.images_response import ImagesResponse
from semantic_kernel.connectors.ai.open_ai.services.azure_text_to_image import AzureTextToImage
from semantic_kernel.connectors.ai.text_to_image_client_base import TextToImageClientBase
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_azure_text_to_image_init(azure_openai_unit_test_env) -> None:
# Test successful initialization
azure_text_to_image = AzureTextToImage()
assert azure_text_to_image.client is not None
assert isinstance(azure_text_to_image.client, AsyncAzureOpenAI)
assert azure_text_to_image.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME"]
assert isinstance(azure_text_to_image, TextToImageClientBase)
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME"]], indirect=True)
def test_azure_text_to_image_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextToImage(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_text_to_image_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextToImage(env_file_path="test.env")
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_azure_text_to_image_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextToImage(env_file_path="test.env")
@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
def test_azure_text_to_image_init_with_invalid_endpoint(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextToImage()
@pytest.mark.parametrize(
"override_env_param_dict",
[{"AZURE_OPENAI_BASE_URL": "https://test_text_to_image_deployment.test-base-url.com"}],
indirect=True,
)
def test_azure_text_to_image_init_with_from_dict(azure_openai_unit_test_env) -> None:
default_headers = {"test_header": "test_value"}
settings = {
"deployment_name": azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_IMAGE_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,
}
azure_text_to_image = AzureTextToImage.from_dict(settings=settings)
assert azure_text_to_image.client is not None
assert isinstance(azure_text_to_image.client, AsyncAzureOpenAI)
assert azure_text_to_image.ai_model_id == azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME"]
assert isinstance(azure_text_to_image, TextToImageClientBase)
assert settings["deployment_name"] in str(azure_text_to_image.client.base_url)
assert azure_text_to_image.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():
assert key in azure_text_to_image.client.default_headers
assert azure_text_to_image.client.default_headers[key] == value
@patch.object(AsyncImages, "generate", new_callable=AsyncMock)
async def test_azure_text_to_image_calls_with_parameters(mock_generate, azure_openai_unit_test_env) -> None:
mock_response = ImagesResponse(created=1, data=[Image(url="abc")], usage=None)
mock_generate.return_value = mock_response
prompt = "A painting of a vase with flowers"
width = 512
azure_text_to_image = AzureTextToImage(
deployment_name=azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME"]
)
await azure_text_to_image.generate_image(prompt, width=width, height=width)
mock_generate.assert_awaited_once_with(
prompt=prompt,
model=azure_openai_unit_test_env["AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME"],
size=f"{width}x{width}",
n=1,
)
@@ -0,0 +1,87 @@
# Copyright (c) Microsoft. All rights reserved.
import os
from unittest.mock import AsyncMock, patch
import pytest
from openai import AsyncClient
from openai.resources.audio.transcriptions import AsyncTranscriptions
from openai.types.audio import Transcription
from semantic_kernel.connectors.ai.open_ai import OpenAIAudioToTextExecutionSettings
from semantic_kernel.connectors.ai.open_ai.services.open_ai_audio_to_text import OpenAIAudioToText
from semantic_kernel.contents import AudioContent
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError, ServiceInvalidRequestError
def test_init(openai_unit_test_env):
openai_audio_to_text = OpenAIAudioToText()
assert openai_audio_to_text.client is not None
assert isinstance(openai_audio_to_text.client, AsyncClient)
assert openai_audio_to_text.ai_model_id == openai_unit_test_env["OPENAI_AUDIO_TO_TEXT_MODEL_ID"]
def test_init_validation_fail() -> None:
with pytest.raises(ServiceInitializationError, match="Failed to create OpenAI settings."):
OpenAIAudioToText(api_key="34523", ai_model_id={"test": "dict"})
@pytest.mark.parametrize("exclude_list", [["OPENAI_AUDIO_TO_TEXT_MODEL_ID"]], indirect=True)
def test_init_audio_to_text_model_not_provided(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="The OpenAI audio to text model ID is required."):
OpenAIAudioToText(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAIAudioToText(
env_file_path="test.env",
)
def test_init_to_from_dict(openai_unit_test_env):
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_AUDIO_TO_TEXT_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
audio_to_text = OpenAIAudioToText.from_dict(settings)
dumped_settings = audio_to_text.to_dict()
assert dumped_settings["ai_model_id"] == settings["ai_model_id"]
assert dumped_settings["api_key"] == settings["api_key"]
def test_prompt_execution_settings_class(openai_unit_test_env) -> None:
openai_audio_to_text = OpenAIAudioToText()
assert openai_audio_to_text.get_prompt_execution_settings_class() == OpenAIAudioToTextExecutionSettings
async def test_get_text_contents(openai_unit_test_env):
audio_content = AudioContent.from_audio_file(
os.path.join(os.path.dirname(__file__), "../../../../../", "assets/sample_audio.mp3")
)
with patch.object(AsyncTranscriptions, "create", new_callable=AsyncMock) as mock_transcription_create:
mock_transcription_create.return_value = Transcription(text="This is a test audio file.")
openai_audio_to_text = OpenAIAudioToText()
text_contents = await openai_audio_to_text.get_text_contents(audio_content)
assert len(text_contents) == 1
assert text_contents[0].text == "This is a test audio file."
assert text_contents[0].ai_model_id == openai_unit_test_env["OPENAI_AUDIO_TO_TEXT_MODEL_ID"]
async def test_get_text_contents_invalid_audio_content(openai_unit_test_env):
audio_content = AudioContent()
openai_audio_to_text = OpenAIAudioToText()
with pytest.raises(ServiceInvalidRequestError, match="Audio content uri must be a string to a local file."):
await openai_audio_to_text.get_text_contents(audio_content)
@@ -0,0 +1,105 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.const import USER_AGENT
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_init(openai_unit_test_env) -> None:
# Test successful initialization
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_init_validation_fail() -> None:
# Test successful initialization
with pytest.raises(ServiceInitializationError):
OpenAIChatCompletion(api_key="34523", ai_model_id={"test": "dict"})
def test_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_init_with_default_header(openai_unit_test_env) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
# Test successful initialization
open_ai_chat_completion = OpenAIChatCompletion(
default_headers=default_headers,
)
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
for key, value in default_headers.items():
assert key in open_ai_chat_completion.client.default_headers
assert open_ai_chat_completion.client.default_headers[key] == value
@pytest.mark.parametrize("exclude_list", [["OPENAI_CHAT_MODEL_ID"]], indirect=True)
def test_init_with_empty_model_id(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAIChatCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
ai_model_id = "test_model_id"
with pytest.raises(ServiceInitializationError):
OpenAIChatCompletion(
ai_model_id=ai_model_id,
env_file_path="test.env",
)
def test_serialize(openai_unit_test_env) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"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"] == 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"]
assert dumped_settings["default_headers"][key] == value
# Assert that the 'User-agent' header is not present in the dumped_settings default headers
assert USER_AGENT not in dumped_settings["default_headers"]
def test_serialize_with_org_id(openai_unit_test_env) -> None:
settings = {
"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"] == 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"]
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,324 @@
# Copyright (c) Microsoft. All rights reserved.
import json
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
from openai import AsyncStream
from openai.resources import AsyncCompletions
from openai.types import Completion as TextCompletion
from openai.types import CompletionChoice as TextCompletionChoice
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAITextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_completion import OpenAITextCompletion
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.ai.text_completion_client_base import TextCompletionClientBase
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_init(openai_unit_test_env) -> None:
# Test successful initialization
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_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_init_with_default_header(openai_unit_test_env) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
# Test successful initialization
open_ai_text_completion = OpenAITextCompletion(
default_headers=default_headers,
)
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_init_validation_fail() -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextCompletion(api_key="34523", ai_model_id={"test": "dict"})
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextCompletion(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_TEXT_MODEL_ID"]], indirect=True)
def test_init_with_empty_model(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextCompletion(
env_file_path="test.env",
)
def test_serialize(openai_unit_test_env) -> None:
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"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"] == 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_serialize_def_headers_string(openai_unit_test_env) -> None:
default_headers = '{"X-Unit-Test": "test-guid"}'
settings = {
"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"] == 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 json.loads(default_headers).items():
assert key in dumped_settings["default_headers"]
assert dumped_settings["default_headers"][key] == value
def test_serialize_with_org_id(openai_unit_test_env) -> None:
settings = {
"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"] == 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"]
# region Get Text Contents
@pytest.fixture()
def completion_response() -> TextCompletion:
return TextCompletion(
id="test",
choices=[TextCompletionChoice(text="test", index=0, finish_reason="stop")],
created=0,
model="test",
object="text_completion",
)
@pytest.fixture()
def streaming_completion_response() -> AsyncStream[TextCompletion]:
content = TextCompletion(
id="test",
choices=[TextCompletionChoice(text="test", index=0, finish_reason="stop")],
created=0,
model="test",
object="text_completion",
)
stream = MagicMock(spec=AsyncStream)
stream.__aiter__.return_value = [content]
return stream
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_tc(
mock_create,
openai_unit_test_env,
completion_response,
) -> None:
mock_create.return_value = completion_response
complete_prompt_execution_settings = OpenAITextPromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
await openai_text_completion.get_text_contents(prompt="test", settings=complete_prompt_execution_settings)
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=False,
prompt="test",
echo=False,
)
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_tc_singular(
mock_create,
openai_unit_test_env,
completion_response,
) -> None:
mock_create.return_value = completion_response
complete_prompt_execution_settings = OpenAITextPromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
await openai_text_completion.get_text_content(prompt="test", settings=complete_prompt_execution_settings)
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=False,
prompt="test",
echo=False,
)
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_tc_prompt_execution_settings(
mock_create,
openai_unit_test_env,
completion_response,
) -> None:
mock_create.return_value = completion_response
complete_prompt_execution_settings = PromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
await openai_text_completion.get_text_contents(prompt="test", settings=complete_prompt_execution_settings)
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=False,
prompt="test",
echo=False,
)
# region Streaming
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_stc(
mock_create,
openai_unit_test_env,
streaming_completion_response,
) -> None:
mock_create.return_value = streaming_completion_response
complete_prompt_execution_settings = OpenAITextPromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
[
text
async for text in openai_text_completion.get_streaming_text_contents(
prompt="test", settings=complete_prompt_execution_settings
)
]
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=True,
prompt="test",
echo=False,
)
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_stc_singular(
mock_create,
openai_unit_test_env,
streaming_completion_response,
) -> None:
mock_create.return_value = streaming_completion_response
complete_prompt_execution_settings = OpenAITextPromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
[
text
async for text in openai_text_completion.get_streaming_text_content(
prompt="test", settings=complete_prompt_execution_settings
)
]
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=True,
prompt="test",
echo=False,
)
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_stc_prompt_execution_settings(
mock_create,
openai_unit_test_env,
streaming_completion_response,
) -> None:
mock_create.return_value = streaming_completion_response
complete_prompt_execution_settings = PromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
[
text
async for text in openai_text_completion.get_streaming_text_contents(
prompt="test", settings=complete_prompt_execution_settings
)
]
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=True,
prompt="test",
echo=False,
)
@patch.object(AsyncCompletions, "create", new_callable=AsyncMock)
async def test_stc_empty_choices(
mock_create,
openai_unit_test_env,
) -> None:
content1 = TextCompletion(
id="test",
choices=[],
created=0,
model="test",
object="text_completion",
)
content2 = TextCompletion(
id="test",
choices=[TextCompletionChoice(text="test", index=0, finish_reason="stop")],
created=0,
model="test",
object="text_completion",
)
stream = MagicMock(spec=AsyncStream)
stream.__aiter__.return_value = [content1, content2]
mock_create.return_value = stream
complete_prompt_execution_settings = OpenAITextPromptExecutionSettings(service_id="test_service_id")
openai_text_completion = OpenAITextCompletion()
results = [
text
async for text in openai_text_completion.get_streaming_text_contents(
prompt="test", settings=complete_prompt_execution_settings
)
]
assert len(results) == 1
mock_create.assert_awaited_once_with(
model=openai_unit_test_env["OPENAI_TEXT_MODEL_ID"],
stream=True,
prompt="test",
echo=False,
)
@@ -0,0 +1,126 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import AsyncMock, patch
import pytest
from openai import AsyncClient
from openai.resources.embeddings import AsyncEmbeddings
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIEmbeddingPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_embedding import OpenAITextEmbedding
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError, ServiceResponseException
def test_init(openai_unit_test_env):
openai_text_embedding = OpenAITextEmbedding()
assert openai_text_embedding.client is not None
assert isinstance(openai_text_embedding.client, AsyncClient)
assert openai_text_embedding.ai_model_id == openai_unit_test_env["OPENAI_EMBEDDING_MODEL_ID"]
assert openai_text_embedding.get_prompt_execution_settings_class() == OpenAIEmbeddingPromptExecutionSettings
def test_init_validation_fail() -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextEmbedding(api_key="34523", ai_model_id={"test": "dict"})
def test_init_to_from_dict(openai_unit_test_env):
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_EMBEDDING_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
text_embedding = OpenAITextEmbedding.from_dict(settings)
dumped_settings = text_embedding.to_dict()
assert dumped_settings["ai_model_id"] == settings["ai_model_id"]
assert dumped_settings["api_key"] == settings["api_key"]
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextEmbedding(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_EMBEDDING_MODEL_ID"]], indirect=True)
def test_init_with_no_model_id(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextEmbedding(
env_file_path="test.env",
)
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock)
async def test_embedding_calls_with_parameters(mock_create, openai_unit_test_env) -> None:
ai_model_id = "test_model_id"
texts = ["hello world", "goodbye world"]
embedding_dimensions = 1536
openai_text_embedding = OpenAITextEmbedding(
ai_model_id=ai_model_id,
)
await openai_text_embedding.generate_embeddings(texts, dimensions=embedding_dimensions)
mock_create.assert_awaited_once_with(
input=texts,
model=ai_model_id,
dimensions=embedding_dimensions,
)
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock)
async def test_embedding_calls_with_settings(mock_create, openai_unit_test_env) -> None:
ai_model_id = "test_model_id"
texts = ["hello world", "goodbye world"]
settings = OpenAIEmbeddingPromptExecutionSettings(service_id="default", dimensions=1536)
openai_text_embedding = OpenAITextEmbedding(service_id="default", ai_model_id=ai_model_id)
await openai_text_embedding.generate_embeddings(texts, settings=settings, timeout=10)
mock_create.assert_awaited_once_with(
input=texts,
model=ai_model_id,
dimensions=1536,
timeout=10,
)
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock, side_effect=Exception)
async def test_embedding_fail(mock_create, openai_unit_test_env) -> None:
ai_model_id = "test_model_id"
texts = ["hello world", "goodbye world"]
embedding_dimensions = 1536
openai_text_embedding = OpenAITextEmbedding(
ai_model_id=ai_model_id,
)
with pytest.raises(ServiceResponseException):
await openai_text_embedding.generate_embeddings(texts, dimensions=embedding_dimensions)
@patch.object(AsyncEmbeddings, "create", new_callable=AsyncMock)
async def test_embedding_pes(mock_create, openai_unit_test_env) -> None:
ai_model_id = "test_model_id"
texts = ["hello world", "goodbye world"]
embedding_dimensions = 1536
pes = PromptExecutionSettings(ai_model_id=ai_model_id, dimensions=embedding_dimensions)
openai_text_embedding = OpenAITextEmbedding(ai_model_id=ai_model_id)
await openai_text_embedding.generate_raw_embeddings(texts, pes)
mock_create.assert_awaited_once_with(
input=texts,
model=ai_model_id,
dimensions=embedding_dimensions,
)
@@ -0,0 +1,69 @@
# Copyright (c) Microsoft. All rights reserved.
from unittest.mock import patch
import httpx
import pytest
from openai import AsyncClient, _legacy_response
from openai.resources.audio.speech import AsyncSpeech
from semantic_kernel.connectors.ai.open_ai import OpenAITextToAudio, OpenAITextToAudioExecutionSettings
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError
def test_init(openai_unit_test_env):
openai_text_to_audio = OpenAITextToAudio()
assert openai_text_to_audio.client is not None
assert isinstance(openai_text_to_audio.client, AsyncClient)
assert openai_text_to_audio.ai_model_id == openai_unit_test_env["OPENAI_TEXT_TO_AUDIO_MODEL_ID"]
def test_init_validation_fail() -> None:
with pytest.raises(ServiceInitializationError, match="Failed to create OpenAI settings."):
OpenAITextToAudio(api_key="34523", ai_model_id={"test": "dict"})
@pytest.mark.parametrize("exclude_list", [["OPENAI_TEXT_TO_AUDIO_MODEL_ID"]], indirect=True)
def test_init_text_to_audio_model_not_provided(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError, match="The OpenAI text to audio model ID is required."):
OpenAITextToAudio(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextToAudio(
env_file_path="test.env",
)
def test_init_to_from_dict(openai_unit_test_env):
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_TEXT_TO_AUDIO_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
audio_to_text = OpenAITextToAudio.from_dict(settings)
dumped_settings = audio_to_text.to_dict()
assert dumped_settings["ai_model_id"] == settings["ai_model_id"]
assert dumped_settings["api_key"] == settings["api_key"]
def test_prompt_execution_settings_class(openai_unit_test_env) -> None:
openai_text_to_audio = OpenAITextToAudio()
assert openai_text_to_audio.get_prompt_execution_settings_class() == OpenAITextToAudioExecutionSettings
@patch.object(AsyncSpeech, "create", return_value=_legacy_response.HttpxBinaryResponseContent(httpx.Response(200)))
async def test_get_text_contents(mock_speech_create, openai_unit_test_env):
openai_text_to_audio = OpenAITextToAudio()
audio_contents = await openai_text_to_audio.get_audio_contents("Hello World!")
assert len(audio_contents) == 1
assert audio_contents[0].ai_model_id == openai_unit_test_env["OPENAI_TEXT_TO_AUDIO_MODEL_ID"]
@@ -0,0 +1,375 @@
# Copyright (c) Microsoft. All rights reserved.
import os
import warnings
from unittest.mock import AsyncMock, patch
import pydantic
import pytest
from openai import AsyncClient
from openai.resources.images import AsyncImages
from openai.types.image import Image
from openai.types.images_response import ImagesResponse
from semantic_kernel.connectors.ai.open_ai import OpenAITextToImage, OpenAITextToImageExecutionSettings
from semantic_kernel.connectors.ai.open_ai.services.open_ai_text_to_image_base import OpenAITextToImageBase
from semantic_kernel.exceptions.service_exceptions import (
ServiceInitializationError,
ServiceInvalidExecutionSettingsError,
ServiceInvalidRequestError,
ServiceResponseException,
)
sample_img = os.path.join(os.path.dirname(__file__), "../../../../../assets/sample_image.jpg")
def test_init(openai_unit_test_env):
"""Test that OpenAITextToImage initializes with the correct model id and client."""
openai_text_to_image = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
assert openai_text_to_image.client is not None
assert isinstance(openai_text_to_image.client, AsyncClient)
assert openai_text_to_image.ai_model_id == openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"]
@pytest.mark.parametrize("exclude_list", [["OPENAI_TEXT_TO_IMAGE_MODEL_ID"]], indirect=True)
def test_init_validation_fail(openai_unit_test_env) -> None:
"""Test that initialization fails when required parameters are missing."""
with pytest.raises(ServiceInitializationError):
OpenAITextToImage(api_key="34523", ai_model_id=None, env_file_path="test.env")
def test_init_to_from_dict(openai_unit_test_env):
"""Test to_dict and from_dict methods for correct serialization and deserialization."""
default_headers = {"X-Unit-Test": "test-guid"}
settings = {
"ai_model_id": openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"],
"api_key": openai_unit_test_env["OPENAI_API_KEY"],
"default_headers": default_headers,
}
text_to_image = OpenAITextToImage.from_dict(settings)
dumped_settings = text_to_image.to_dict()
assert dumped_settings["ai_model_id"] == settings["ai_model_id"]
assert dumped_settings["api_key"] == settings["api_key"]
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_init_with_empty_api_key(openai_unit_test_env) -> None:
"""Test that initialization fails when API key is missing."""
with pytest.raises(ServiceInitializationError):
OpenAITextToImage(
env_file_path="test.env",
)
@pytest.mark.parametrize("exclude_list", [["OPENAI_TEXT_TO_IMAGE_MODEL_ID"]], indirect=True)
def test_init_with_no_model_id(openai_unit_test_env) -> None:
"""Test that initialization fails when model id is missing."""
with pytest.raises(ServiceInitializationError):
OpenAITextToImage(
env_file_path="test.env",
)
def test_prompt_execution_settings_class(openai_unit_test_env) -> None:
"""Test that the correct prompt execution settings class is returned."""
openai_text_to_image = OpenAITextToImage()
assert openai_text_to_image.get_prompt_execution_settings_class() == OpenAITextToImageExecutionSettings
@patch.object(AsyncImages, "generate", new_callable=AsyncMock)
async def test_generate_calls_with_parameters(mock_generate, openai_unit_test_env) -> None:
"""Test that generate_image calls the OpenAI API with correct parameters."""
mock_response = ImagesResponse(created=1, data=[Image(url="abc")], usage=None)
mock_generate.return_value = mock_response
ai_model_id = "test_model_id"
prompt = "painting of flowers in vase"
width = 512
openai_text_to_image = OpenAITextToImage(ai_model_id=ai_model_id)
with warnings.catch_warnings(record=True) as w:
await openai_text_to_image.generate_image(description=prompt, width=width, height=width)
mock_generate.assert_awaited_once_with(
prompt=prompt,
model=ai_model_id,
size=f"{width}x{width}",
n=1,
)
assert len(w) == 3
@patch.object(AsyncImages, "generate", new_callable=AsyncMock, side_effect=Exception)
async def test_generate_fail(mock_generate, openai_unit_test_env) -> None:
"""Test that generate_image raises ServiceResponseException on API failure."""
ai_model_id = "test_model_id"
width = 512
openai_text_to_image = OpenAITextToImage(ai_model_id=ai_model_id)
with pytest.raises(ServiceResponseException):
await openai_text_to_image.generate_image(description="painting of flowers in vase", width=width, height=width)
async def test_generate_invalid_image_size(openai_unit_test_env) -> None:
"""Test that invalid image size raises ServiceInvalidExecutionSettingsError."""
ai_model_id = "test_model_id"
width = 100
openai_text_to_image = OpenAITextToImage(ai_model_id=ai_model_id)
with pytest.raises(ServiceInvalidExecutionSettingsError):
await openai_text_to_image.generate_image(description="painting of flowers in vase", width=width, height=width)
async def test_generate_empty_description(openai_unit_test_env) -> None:
"""Test that empty description raises ServiceInvalidExecutionSettingsError."""
ai_model_id = "test_model_id"
width = 100
openai_text_to_image = OpenAITextToImage(ai_model_id=ai_model_id)
with pytest.raises(ServiceInvalidExecutionSettingsError):
await openai_text_to_image.generate_image(description="", width=width, height=width)
@patch.object(AsyncImages, "generate", new_callable=AsyncMock)
async def test_generate_no_result(mock_generate, openai_unit_test_env) -> None:
"""Test that no result from API raises ServiceResponseException."""
mock_generate.return_value = ImagesResponse(created=0, data=[], usage=None)
ai_model_id = "test_model_id"
width = 512
openai_text_to_image = OpenAITextToImage(ai_model_id=ai_model_id)
with pytest.raises(ServiceResponseException):
await openai_text_to_image.generate_image(description="painting of flowers in vase", width=width, height=width)
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_with_path_success(mock_edit, openai_unit_test_env):
"""Test editing an image using a file path returns the expected URL."""
mock_edit.return_value = ImagesResponse(created=1, data=[Image(url="edited_url")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.edit_image(
prompt="edit this image",
image_paths=[sample_img],
)
assert result == ["edited_url"]
mock_edit.assert_awaited()
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_with_file_success(mock_edit, openai_unit_test_env):
"""Test editing an image using a file object returns the expected URL."""
mock_edit.return_value = ImagesResponse(created=1, data=[Image(url="edited_url")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with open(sample_img, "rb") as f:
result = await service.edit_image(
prompt="edit this image",
image_files=[f],
)
assert result == ["edited_url"]
mock_edit.assert_awaited()
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_with_mask_path_and_file(mock_edit, openai_unit_test_env):
"""Test editing an image with both mask path and mask file returns the expected URL."""
mock_edit.return_value = ImagesResponse(created=1, data=[Image(url="edited_url")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
# mask_path
result = await service.edit_image(
prompt="edit with mask",
image_paths=[sample_img],
mask_path=sample_img,
)
assert result == ["edited_url"]
# mask_file
with open(sample_img, "rb") as mf:
result2 = await service.edit_image(
prompt="edit with mask",
image_paths=[sample_img],
mask_file=mf,
)
assert result2 == ["edited_url"]
@pytest.mark.asyncio
async def test_edit_image_prompt_required(openai_unit_test_env):
"""Test that an empty prompt raises ServiceInvalidRequestError."""
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceInvalidRequestError):
await service.edit_image(prompt="", image_paths=[sample_img])
@pytest.mark.asyncio
async def test_edit_image_both_path_and_file_error(openai_unit_test_env):
"""Test that providing both image_paths and image_files raises ServiceInvalidRequestError."""
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with (
open(sample_img, "rb") as f,
pytest.raises(ServiceInvalidRequestError),
):
await service.edit_image(
prompt="edit",
image_paths=[sample_img],
image_files=[f],
)
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_no_valid_data_in_response(mock_edit, openai_unit_test_env):
"""Test that no valid data in edit response raises ServiceResponseException."""
mock_edit.return_value = ImagesResponse(created=1, data=[], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceResponseException):
await service.edit_image(
prompt="edit",
image_paths=[sample_img],
)
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_with_n_parameter(mock_generate, openai_unit_test_env):
"""Test that generate_images returns correct URLs when n parameter is set."""
mock_generate.return_value = ImagesResponse(created=3, data=[Image(url=f"url_{i}") for i in range(3)], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
settings = OpenAITextToImageExecutionSettings(n=3)
result = await service.generate_images("prompt", settings=settings)
assert result == [f"url_{i}" for i in range(3)]
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_with_output_compression_and_background(mock_generate, openai_unit_test_env):
"""Test that output_compression and background parameters are handled correctly."""
mock_generate.return_value = ImagesResponse(created=1, data=[Image(url="url")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
settings = OpenAITextToImageExecutionSettings(output_compression=5, background="transparent")
await service.generate_images("prompt", settings=settings)
called_settings = mock_generate.call_args[0][0]
assert called_settings.output_compression == 5
assert called_settings.background == "transparent"
@patch.object(OpenAITextToImageBase, "store_usage")
def test_store_usage_for_images_response(mock_store_usage, openai_unit_test_env):
"""Test that store_usage is called for ImagesResponse."""
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
response = ImagesResponse(created=1, data=[Image(url="url")], usage=None)
service.store_usage(response)
mock_store_usage.assert_called()
@pytest.mark.asyncio
async def test_edit_image_invalid_n_parameter():
"""Test that invalid n parameter raises pydantic.ValidationError."""
with pytest.raises(pydantic.ValidationError):
OpenAITextToImageExecutionSettings(n=0)
with pytest.raises(pydantic.ValidationError):
OpenAITextToImageExecutionSettings(n=11)
@pytest.mark.asyncio
async def test_generate_images_empty_prompt(openai_unit_test_env):
"""Test that empty prompt raises ServiceInvalidRequestError."""
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceInvalidRequestError):
await service.generate_images("")
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_no_result(mock_generate, openai_unit_test_env):
"""Test that empty response data raises ServiceResponseException."""
mock_generate.return_value = ImagesResponse(created=0, data=[], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceResponseException):
await service.generate_images("prompt")
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_b64_json_response(mock_generate, openai_unit_test_env):
"""Test that generate_images returns b64_json when url is not present."""
mock_generate.return_value = ImagesResponse(created=1, data=[Image(b64_json="base64encodeddata")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.generate_images("prompt")
assert result == ["base64encodeddata"]
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_mixed_url_and_b64_response(mock_generate, openai_unit_test_env):
"""Test that generate_images handles mixed url and b64_json responses."""
mock_generate.return_value = ImagesResponse(
created=2,
data=[Image(url="http://example.com/img1.png"), Image(b64_json="base64data")],
usage=None,
)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.generate_images("prompt")
assert result == ["http://example.com/img1.png", "base64data"]
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_with_default_settings(mock_generate, openai_unit_test_env):
"""Test that generate_images works when no settings are provided."""
mock_generate.return_value = ImagesResponse(created=1, data=[Image(url="url")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.generate_images("a beautiful sunset")
assert result == ["url"]
mock_generate.assert_awaited_once()
@patch.object(OpenAITextToImageBase, "_send_request", new_callable=AsyncMock)
async def test_generate_images_no_valid_image_data(mock_generate, openai_unit_test_env):
"""Test that generate_images raises error when images have neither url nor b64_json."""
mock_generate.return_value = ImagesResponse(created=1, data=[Image()], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceResponseException, match="No valid image data found"):
await service.generate_images("prompt")
@pytest.mark.asyncio
async def test_edit_image_neither_path_nor_file(openai_unit_test_env):
"""Test that providing neither image_paths nor image_files raises ServiceInvalidRequestError."""
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceInvalidRequestError):
await service.edit_image(prompt="edit this")
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_b64_json_response(mock_edit, openai_unit_test_env):
"""Test editing an image returns b64_json when url is not present."""
mock_edit.return_value = ImagesResponse(created=1, data=[Image(b64_json="edited_b64")], usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.edit_image(
prompt="edit this image",
image_paths=[sample_img],
)
assert result == ["edited_b64"]
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_mixed_response(mock_edit, openai_unit_test_env):
"""Test editing images handles mixed b64_json and url responses."""
mock_edit.return_value = ImagesResponse(
created=2,
data=[Image(b64_json="b64data"), Image(url="http://example.com/edited.png")],
usage=None,
)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
result = await service.edit_image(
prompt="edit these images",
image_paths=[sample_img],
)
assert result == ["b64data", "http://example.com/edited.png"]
@patch.object(OpenAITextToImageBase, "_send_image_edit_request", new_callable=AsyncMock)
async def test_edit_image_response_no_data_attribute(mock_edit, openai_unit_test_env):
"""Test that edit_image raises error when response has no valid data."""
mock_edit.return_value = ImagesResponse(created=1, data=None, usage=None)
service = OpenAITextToImage(ai_model_id=openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"])
with pytest.raises(ServiceResponseException):
await service.edit_image(
prompt="edit",
image_paths=[sample_img],
)
@@ -0,0 +1,384 @@
# Copyright (c) Microsoft. All rights reserved.
import pytest
from pydantic import BaseModel
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.azure_chat_prompt_execution_settings import (
AzureAISearchDataSource,
AzureAISearchDataSourceParameters,
AzureChatPromptExecutionSettings,
ExtraBody,
)
from semantic_kernel.connectors.ai.open_ai.prompt_execution_settings.open_ai_prompt_execution_settings import (
OpenAIChatPromptExecutionSettings,
OpenAITextPromptExecutionSettings,
)
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
from semantic_kernel.connectors.azure_ai_search import AzureAISearchSettings
from semantic_kernel.exceptions import ServiceInvalidExecutionSettingsError
from semantic_kernel.kernel_pydantic import KernelBaseModel
############################################
# Test classes for structured output
class ClassTest:
attribute: str
class ClassTestPydantic(KernelBaseModel):
attribute: str
############################################
def test_default_openai_chat_prompt_execution_settings():
settings = OpenAIChatPromptExecutionSettings()
assert settings.temperature is None
assert settings.top_p is None
assert settings.presence_penalty is None
assert settings.frequency_penalty is None
assert settings.max_tokens is None
assert settings.stop is None
assert settings.number_of_responses is None
assert settings.logit_bias is None
assert settings.messages is None
def test_custom_openai_chat_prompt_execution_settings():
settings = OpenAIChatPromptExecutionSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop="\n",
number_of_responses=2,
logit_bias={"1": 1},
messages=[{"role": "system", "content": "Hello"}],
)
assert settings.temperature == 0.5
assert settings.top_p == 0.5
assert settings.presence_penalty == 0.5
assert settings.frequency_penalty == 0.5
assert settings.max_tokens == 128
assert settings.stop == "\n"
assert settings.number_of_responses == 2
assert settings.logit_bias == {"1": 1}
assert settings.messages == [{"role": "system", "content": "Hello"}]
def test_openai_chat_prompt_execution_settings_from_default_completion_config():
settings = PromptExecutionSettings(service_id="test_service")
chat_settings = OpenAIChatPromptExecutionSettings.from_prompt_execution_settings(settings)
assert chat_settings.service_id == "test_service"
assert chat_settings.temperature is None
assert chat_settings.top_p is None
assert chat_settings.presence_penalty is None
assert chat_settings.frequency_penalty is None
assert chat_settings.max_tokens is None
assert chat_settings.stop is None
assert chat_settings.number_of_responses is None
assert chat_settings.logit_bias is None
def test_openai_chat_prompt_execution_settings_from_openai_prompt_execution_settings():
chat_settings = OpenAIChatPromptExecutionSettings(service_id="test_service", temperature=1.0)
new_settings = OpenAIChatPromptExecutionSettings(service_id="test_2", temperature=0.0)
chat_settings.update_from_prompt_execution_settings(new_settings)
assert chat_settings.service_id == "test_2"
assert chat_settings.temperature == 0.0
def test_openai_text_prompt_execution_settings_validation():
with pytest.raises(ServiceInvalidExecutionSettingsError, match="best_of must be greater than number_of_responses"):
OpenAITextPromptExecutionSettings(best_of=1, number_of_responses=2)
def test_openai_text_prompt_execution_settings_validation_manual():
text_oai = OpenAITextPromptExecutionSettings(best_of=1, number_of_responses=1)
with pytest.raises(ServiceInvalidExecutionSettingsError, match="best_of must be greater than number_of_responses"):
text_oai.number_of_responses = 2
def test_openai_chat_prompt_execution_settings_from_custom_completion_config():
settings = PromptExecutionSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"logprobs": 1,
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatPromptExecutionSettings.from_prompt_execution_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
def test_openai_chat_prompt_execution_settings_from_custom_completion_config_with_none():
settings = PromptExecutionSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"functions": None,
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatPromptExecutionSettings.from_prompt_execution_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
assert chat_settings.functions is None
def test_openai_chat_prompt_execution_settings_from_custom_completion_config_with_functions():
settings = PromptExecutionSettings(
service_id="test_service",
extension_data={
"temperature": 0.5,
"top_p": 0.5,
"presence_penalty": 0.5,
"frequency_penalty": 0.5,
"max_tokens": 128,
"stop": ["\n"],
"number_of_responses": 2,
"functions": [{}],
"function_call": "auto",
"logit_bias": {"1": 1},
"messages": [{"role": "system", "content": "Hello"}],
},
)
chat_settings = OpenAIChatPromptExecutionSettings.from_prompt_execution_settings(settings)
assert chat_settings.temperature == 0.5
assert chat_settings.top_p == 0.5
assert chat_settings.presence_penalty == 0.5
assert chat_settings.frequency_penalty == 0.5
assert chat_settings.max_tokens == 128
assert chat_settings.stop == ["\n"]
assert chat_settings.number_of_responses == 2
assert chat_settings.logit_bias == {"1": 1}
assert chat_settings.functions == [{}]
def test_create_options():
settings = OpenAIChatPromptExecutionSettings(
temperature=0.5,
top_p=0.5,
presence_penalty=0.5,
frequency_penalty=0.5,
max_tokens=128,
stop=["\n"],
number_of_responses=2,
logit_bias={"1": 1},
messages=[{"role": "system", "content": "Hello"}],
function_call="auto",
)
options = settings.prepare_settings_dict()
assert options["temperature"] == 0.5
assert options["top_p"] == 0.5
assert options["presence_penalty"] == 0.5
assert options["frequency_penalty"] == 0.5
assert options["max_tokens"] == 128
assert options["stop"] == ["\n"]
assert options["n"] == 2
assert options["logit_bias"] == {"1": 1}
assert not options["stream"]
def test_create_options_azure_data():
az_source = AzureAISearchDataSource(
parameters={
"indexName": "test-index",
"endpoint": "test-endpoint",
"authentication": {"type": "api_key", "key": "test-key"},
}
)
extra = ExtraBody(data_sources=[az_source])
assert extra["data_sources"] is not None
assert extra.data_sources is not None
settings = AzureChatPromptExecutionSettings(extra_body=extra)
options = settings.prepare_settings_dict()
assert options["extra_body"] == extra.model_dump(exclude_none=True, by_alias=True)
assert options["extra_body"]["data_sources"][0]["type"] == "azure_search"
def test_create_options_azure_data_from_azure_ai_settings(azure_ai_search_unit_test_env):
az_source = AzureAISearchDataSource.from_azure_ai_search_settings(AzureAISearchSettings())
extra = ExtraBody(data_sources=[az_source])
assert extra["data_sources"] is not None
settings = AzureChatPromptExecutionSettings(extra_body=extra)
options = settings.prepare_settings_dict()
assert options["extra_body"] == extra.model_dump(exclude_none=True, by_alias=True)
assert options["extra_body"]["data_sources"][0]["type"] == "azure_search"
def test_azure_open_ai_chat_prompt_execution_settings_with_cosmosdb_data_sources():
input_dict = {
"messages": [{"role": "system", "content": "Hello"}],
"extra_body": {
"dataSources": [
{
"type": "AzureCosmosDB",
"parameters": {
"authentication": {
"type": "ConnectionString",
"connectionString": "mongodb+srv://onyourdatatest:{password}$@{cluster-name}.mongocluster.cosmos.azure.com/?tls=true&authMechanism=SCRAM-SHA-256&retrywrites=false&maxIdleTimeMS=120000",
},
"databaseName": "vectordb",
"containerName": "azuredocs",
"indexName": "azuredocindex",
"embeddingDependency": {
"type": "DeploymentName",
"deploymentName": "{embedding deployment name}",
},
"fieldsMapping": {"vectorFields": ["contentvector"]},
},
}
]
},
}
settings = AzureChatPromptExecutionSettings.model_validate(input_dict, strict=True, from_attributes=True)
assert settings.extra_body["dataSources"][0]["type"] == "AzureCosmosDB"
def test_azure_open_ai_chat_prompt_execution_settings_with_aisearch_data_sources():
input_dict = {
"messages": [{"role": "system", "content": "Hello"}],
"extra_body": {
"dataSources": [
{
"type": "AzureCognitiveSearch",
"parameters": {
"authentication": {
"type": "APIKey",
"key": "****",
},
"endpoint": "https://****.search.windows.net/",
"indexName": "azuredocindex",
"queryType": "vector",
"embeddingDependency": {
"type": "DeploymentName",
"deploymentName": "{embedding deployment name}",
},
"fieldsMapping": {"vectorFields": ["contentvector"]},
},
}
]
},
}
settings = AzureChatPromptExecutionSettings.model_validate(input_dict, strict=True, from_attributes=True)
assert settings.extra_body["dataSources"][0]["type"] == "AzureCognitiveSearch"
@pytest.mark.parametrize(
"authentication",
[
{"type": "APIKey", "key": "test_key"},
{"type": "api_key", "key": "test_key"},
pytest.param({"type": "api_key"}, marks=pytest.mark.xfail),
{"type": "SystemAssignedManagedIdentity"},
{"type": "system_assigned_managed_identity"},
{"type": "UserAssignedManagedIdentity", "managed_identity_resource_id": "test_id"},
{"type": "user_assigned_managed_identity", "managed_identity_resource_id": "test_id"},
pytest.param({"type": "user_assigned_managed_identity"}, marks=pytest.mark.xfail),
{"type": "AccessToken", "access_token": "test_token"},
{"type": "access_token", "access_token": "test_token"},
pytest.param({"type": "access_token"}, marks=pytest.mark.xfail),
pytest.param({"type": "invalid"}, marks=pytest.mark.xfail),
],
ids=[
"APIKey",
"api_key",
"api_key_no_key",
"SystemAssignedManagedIdentity",
"system_assigned_managed_identity",
"UserAssignedManagedIdentity",
"user_assigned_managed_identity",
"user_assigned_managed_identity_no_id",
"AccessToken",
"access_token",
"access_token_no_token",
"invalid",
],
)
def test_aisearch_data_source_parameters(authentication) -> None:
AzureAISearchDataSourceParameters(index_name="test_index", authentication=authentication)
def test_azure_open_ai_chat_prompt_execution_settings_with_response_format_json():
response_format = {"type": "json_object"}
settings = AzureChatPromptExecutionSettings(response_format=response_format)
options = settings.prepare_settings_dict()
assert options["response_format"] == response_format
def test_openai_chat_prompt_execution_settings_with_json_structured_output():
settings = OpenAIChatPromptExecutionSettings()
settings.response_format = {
"type": "json_schema",
"json_schema": {
"name": "math_response",
"schema": {
"type": "object",
"properties": {
"steps": {
"type": "array",
"items": {
"type": "object",
"properties": {"explanation": {"type": "string"}, "output": {"type": "string"}},
"required": ["explanation", "output"],
"additionalProperties": False,
},
},
"final_answer": {"type": "string"},
},
"required": ["steps", "final_answer"],
"additionalProperties": False,
},
"strict": True,
},
}
assert isinstance(settings.response_format, dict)
def test_openai_chat_prompt_execution_settings_with_nonpydantic_type_structured_output():
settings = OpenAIChatPromptExecutionSettings()
settings.response_format = ClassTest
assert isinstance(settings.response_format, type)
def test_openai_chat_prompt_execution_settings_with_pydantic_type_structured_output():
settings = OpenAIChatPromptExecutionSettings()
settings.response_format = ClassTestPydantic
assert issubclass(settings.response_format, BaseModel)
def test_openai_chat_prompt_execution_settings_with_invalid_structured_output():
settings = OpenAIChatPromptExecutionSettings()
with pytest.raises(ServiceInvalidExecutionSettingsError):
settings.response_format = "invalid"