chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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"]
|
||||
+1111
File diff suppressed because it is too large
Load Diff
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"
|
||||
Reference in New Issue
Block a user