chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
from pytest import fixture
|
||||
|
||||
|
||||
@fixture()
|
||||
def onnx_unit_test_env(monkeypatch, exclude_list, override_env_param_dict):
|
||||
"""Fixture to set environment variables for OnnxGenAISettings."""
|
||||
if exclude_list is None:
|
||||
exclude_list = []
|
||||
|
||||
if override_env_param_dict is None:
|
||||
override_env_param_dict = {}
|
||||
|
||||
env_vars = {
|
||||
"ONNX_GEN_AI_CHAT_MODEL_FOLDER": "test",
|
||||
"ONNX_GEN_AI_TEXT_MODEL_FOLDER": "test",
|
||||
}
|
||||
|
||||
env_vars.update(override_env_param_dict)
|
||||
|
||||
for key, value in env_vars.items():
|
||||
if key not in exclude_list:
|
||||
monkeypatch.setenv(key, value)
|
||||
else:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
return env_vars
|
||||
|
||||
|
||||
gen_ai_config = {"model": {"test": "test"}}
|
||||
|
||||
gen_ai_config_vision = {"model": {"vision": "test"}}
|
||||
@@ -0,0 +1,187 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import json
|
||||
import os
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.connectors.ai.onnx import OnnxGenAIChatCompletion, OnnxGenAIPromptExecutionSettings, ONNXTemplate
|
||||
from semantic_kernel.contents import AuthorRole, ChatHistory, ChatMessageContent, ImageContent
|
||||
from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvalidExecutionSettingsError
|
||||
from semantic_kernel.kernel import Kernel
|
||||
from tests.unit.connectors.ai.onnx.conftest import gen_ai_config, gen_ai_config_vision
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_valid_env_variable(gen_ai_config, model, tokenizer, onnx_unit_test_env):
|
||||
service = OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3, env_file_path="test.env")
|
||||
assert not service.enable_multi_modality
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config_vision))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_vision_valid_env_variable(
|
||||
gen_ai_vision_config, model, tokenizer, onnx_unit_test_env
|
||||
):
|
||||
service = OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3, env_file_path="test.env")
|
||||
assert service.enable_multi_modality
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_valid_parameter(gen_ai_config, model, tokenizer):
|
||||
assert OnnxGenAIChatCompletion(ai_model_path="/valid_path", template=ONNXTemplate.PHI3)
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_str_template(gen_ai_config, model, tokenizer):
|
||||
assert OnnxGenAIChatCompletion(ai_model_path="/valid_path", template="phi3")
|
||||
|
||||
|
||||
def test_onnx_chat_completion_with_invalid_model():
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAIChatCompletion(
|
||||
ai_model_path="/invalid_path",
|
||||
template=ONNXTemplate.PHI3,
|
||||
)
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config_vision))
|
||||
def test_onnx_chat_completion_with_multimodality_without_prompt_template(gen_ai_config_vision):
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAIChatCompletion()
|
||||
|
||||
|
||||
def test_onnx_chat_completion_with_invalid_env_variable(onnx_unit_test_env):
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAIChatCompletion(
|
||||
template=ONNXTemplate.PHI3,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_list", [["ONNX_GEN_AI_CHAT_MODEL_FOLDER"]], indirect=True)
|
||||
def test_onnx_chat_completion_with_missing_ai_path(onnx_unit_test_env):
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3, env_file_path="test.env")
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
async def test_onnx_chat_completion(gen_ai_config, model, tokenizer):
|
||||
generator_mock = MagicMock()
|
||||
generator_mock.__aiter__.return_value = [["H"], ["e"], ["l"], ["l"], ["o"]]
|
||||
|
||||
chat_completion = OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3, ai_model_path="test")
|
||||
|
||||
history = ChatHistory()
|
||||
history.add_system_message("test")
|
||||
history.add_user_message("test")
|
||||
|
||||
with patch.object(chat_completion, "_generate_next_token_async", return_value=generator_mock):
|
||||
completed_text: ChatMessageContent = await chat_completion.get_chat_message_content(
|
||||
prompt="test", chat_history=history, settings=OnnxGenAIPromptExecutionSettings(), kernel=Kernel()
|
||||
)
|
||||
|
||||
assert str(completed_text) == "Hello"
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
async def test_onnx_chat_completion_streaming(gen_ai_config, model, tokenizer):
|
||||
generator_mock = MagicMock()
|
||||
generator_mock.__aiter__.return_value = [["H"], ["e"], ["l"], ["l"], ["o"]]
|
||||
|
||||
chat_completion = OnnxGenAIChatCompletion(template=ONNXTemplate.PHI3, ai_model_path="test")
|
||||
|
||||
history = ChatHistory()
|
||||
history.add_system_message("test")
|
||||
history.add_user_message("test")
|
||||
|
||||
completed_text: str = ""
|
||||
|
||||
with patch.object(chat_completion, "_generate_next_token_async", return_value=generator_mock):
|
||||
async for chunk in chat_completion.get_streaming_chat_message_content(
|
||||
prompt="test", chat_history=history, settings=OnnxGenAIPromptExecutionSettings(), kernel=Kernel()
|
||||
):
|
||||
completed_text += str(chunk)
|
||||
|
||||
assert completed_text == "Hello"
|
||||
|
||||
|
||||
@patch("onnxruntime_genai.Model")
|
||||
def test_onnx_chat_get_image_history(model):
|
||||
builtin_open = open # save the unpatched version
|
||||
|
||||
def patch_open(*args, **kwargs):
|
||||
if "genai_config.json" in str(args[0]):
|
||||
# mocked open for path "genai_config.json"
|
||||
return mock_open(read_data=json.dumps(gen_ai_config_vision))(*args, **kwargs)
|
||||
# unpatched version for every other path
|
||||
return builtin_open(*args, **kwargs)
|
||||
|
||||
with patch("builtins.open", patch_open):
|
||||
chat_completion = OnnxGenAIChatCompletion(
|
||||
template=ONNXTemplate.PHI3,
|
||||
ai_model_path="test",
|
||||
)
|
||||
|
||||
image_content = ImageContent.from_image_path(
|
||||
image_path=os.path.join(os.path.dirname(__file__), "../../../../../", "assets/sample_image.jpg")
|
||||
)
|
||||
|
||||
history = ChatHistory()
|
||||
history.add_system_message("test")
|
||||
history.add_user_message("test")
|
||||
history.add_message(
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[image_content],
|
||||
),
|
||||
)
|
||||
|
||||
last_image = chat_completion._get_images_from_history(history)
|
||||
assert last_image == [image_content]
|
||||
|
||||
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
async def test_onnx_chat_get_image_history_with_not_multimodal(model, tokenizer):
|
||||
builtin_open = open # save the unpatched version
|
||||
|
||||
def patch_open(*args, **kwargs):
|
||||
if "genai_config.json" in str(args[0]):
|
||||
# mocked open for path "genai_config.json"
|
||||
return mock_open(read_data=json.dumps(gen_ai_config))(*args, **kwargs)
|
||||
# unpatched version for every other path
|
||||
return builtin_open(*args, **kwargs)
|
||||
|
||||
with patch("builtins.open", patch_open):
|
||||
chat_completion = OnnxGenAIChatCompletion(
|
||||
template=ONNXTemplate.PHI3,
|
||||
ai_model_path="test",
|
||||
)
|
||||
|
||||
image_content = ImageContent.from_image_path(
|
||||
image_path=os.path.join(os.path.dirname(__file__), "../../../../../", "assets/sample_image.jpg")
|
||||
)
|
||||
|
||||
history = ChatHistory()
|
||||
history.add_system_message("test")
|
||||
history.add_user_message("test")
|
||||
history.add_message(
|
||||
ChatMessageContent(
|
||||
role=AuthorRole.USER,
|
||||
items=[image_content],
|
||||
),
|
||||
)
|
||||
|
||||
with pytest.raises(ServiceInvalidExecutionSettingsError):
|
||||
_ = await chat_completion._get_images_from_history(history)
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import json
|
||||
from unittest.mock import MagicMock, mock_open, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from semantic_kernel.connectors.ai.onnx import OnnxGenAIPromptExecutionSettings, OnnxGenAITextCompletion # noqa: E402
|
||||
from semantic_kernel.contents import TextContent
|
||||
from semantic_kernel.exceptions import ServiceInitializationError
|
||||
from tests.unit.connectors.ai.onnx.conftest import gen_ai_config
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_valid_env_variable(gen_ai_config, model, tokenizer, onnx_unit_test_env):
|
||||
assert OnnxGenAITextCompletion(env_file_path="test.env")
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
def test_onnx_chat_completion_with_valid_parameter(gen_ai_config, model, tokenizer):
|
||||
assert OnnxGenAITextCompletion(ai_model_path="/valid_path")
|
||||
|
||||
|
||||
def test_onnx_chat_completion_with_invalid_model():
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAITextCompletion(ai_model_path="/invalid_path")
|
||||
|
||||
|
||||
def test_onnx_chat_completion_with_invalid_env_variable(onnx_unit_test_env):
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAITextCompletion()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("exclude_list", [["ONNX_GEN_AI_TEXT_MODEL_FOLDER"]], indirect=True)
|
||||
def test_onnx_chat_completion_with_missing_ai_path(onnx_unit_test_env):
|
||||
with pytest.raises(ServiceInitializationError):
|
||||
OnnxGenAITextCompletion(env_file_path="test.env")
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
async def test_onnx_text_completion(gen_ai_config, model, tokenizer):
|
||||
generator_mock = MagicMock()
|
||||
generator_mock.__aiter__.return_value = [["H"], ["e"], ["l"], ["l"], ["o"]]
|
||||
|
||||
text_completion = OnnxGenAITextCompletion(ai_model_path="test")
|
||||
with patch.object(text_completion, "_generate_next_token_async", return_value=generator_mock):
|
||||
completed_text: TextContent = await text_completion.get_text_content(
|
||||
prompt="test", settings=OnnxGenAIPromptExecutionSettings()
|
||||
)
|
||||
|
||||
assert completed_text.text == "Hello"
|
||||
|
||||
|
||||
@patch("builtins.open", new_callable=mock_open, read_data=json.dumps(gen_ai_config))
|
||||
@patch("onnxruntime_genai.Model")
|
||||
@patch("onnxruntime_genai.Tokenizer")
|
||||
async def test_onnx_text_completion_streaming(gen_ai_config, model, tokenizer):
|
||||
generator_mock = MagicMock()
|
||||
generator_mock.__aiter__.return_value = [["H"], ["e"], ["l"], ["l"], ["o"]]
|
||||
|
||||
text_completion = OnnxGenAITextCompletion(ai_model_path="test")
|
||||
completed_text: str = ""
|
||||
with patch.object(text_completion, "_generate_next_token_async", return_value=generator_mock):
|
||||
async for chunk in text_completion.get_streaming_text_content(
|
||||
prompt="test", settings=OnnxGenAIPromptExecutionSettings()
|
||||
):
|
||||
completed_text += chunk.text
|
||||
|
||||
assert completed_text == "Hello"
|
||||
@@ -0,0 +1,132 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
from semantic_kernel.connectors.ai.onnx.utils import (
|
||||
gemma_template,
|
||||
llama_template,
|
||||
phi3_template,
|
||||
phi3v_template,
|
||||
phi4_template,
|
||||
phi4mm_template,
|
||||
)
|
||||
from semantic_kernel.contents import AudioContent, AuthorRole, ChatHistory, ImageContent, TextContent
|
||||
|
||||
|
||||
def test_phi3v_template_with_text_and_image():
|
||||
history = ChatHistory(
|
||||
messages=[
|
||||
{"role": AuthorRole.SYSTEM, "content": "System message"},
|
||||
{
|
||||
"role": AuthorRole.USER,
|
||||
"items": [TextContent(text="User text message"), ImageContent(url="http://example.com/image.png")],
|
||||
},
|
||||
{"role": AuthorRole.ASSISTANT, "content": "Assistant message"},
|
||||
]
|
||||
)
|
||||
|
||||
expected_output = (
|
||||
"<|system|>\nSystem message<|end|>\n"
|
||||
"<|user|>\nUser text message<|end|>\n"
|
||||
"<|image_1|>\n"
|
||||
"<|assistant|>\nAssistant message<|end|>\n"
|
||||
"<|assistant|>\n"
|
||||
)
|
||||
|
||||
assert phi3v_template(history) == expected_output
|
||||
|
||||
|
||||
def test_phi4mm_template_with_text_and_image():
|
||||
history = ChatHistory(
|
||||
messages=[
|
||||
{"role": AuthorRole.SYSTEM, "content": "System message"},
|
||||
{
|
||||
"role": AuthorRole.USER,
|
||||
"items": [
|
||||
TextContent(text="User text message"),
|
||||
ImageContent(url="http://example.com/image.png"),
|
||||
AudioContent(url="http://example.com/audio.mp3"),
|
||||
],
|
||||
},
|
||||
{"role": AuthorRole.ASSISTANT, "content": "Assistant message"},
|
||||
]
|
||||
)
|
||||
|
||||
expected_output = (
|
||||
"<|system|>\nSystem message<|end|>\n"
|
||||
"<|user|>\nUser text message<|end|>\n"
|
||||
"<|image_1|>\n"
|
||||
"<|audio_1|>\n"
|
||||
"<|assistant|>\nAssistant message<|end|>\n"
|
||||
"<|assistant|>\n"
|
||||
)
|
||||
|
||||
assert phi4mm_template(history) == expected_output
|
||||
|
||||
|
||||
def test_phi3_template_with_only_text():
|
||||
history = ChatHistory(messages=[{"role": AuthorRole.USER, "items": [TextContent(text="User text message")]}])
|
||||
|
||||
expected_output = "<|user|>\nUser text message<|end|>\n<|assistant|>\n"
|
||||
|
||||
assert phi3_template(history) == expected_output
|
||||
|
||||
|
||||
def test_phi4_template_with_only_text():
|
||||
history = ChatHistory(messages=[{"role": AuthorRole.USER, "items": [TextContent(text="User text message")]}])
|
||||
|
||||
expected_output = "<|user|>\nUser text message<|end|>\n<|assistant|>\n"
|
||||
|
||||
assert phi4_template(history) == expected_output
|
||||
|
||||
|
||||
def test_gemma_template_with_user_and_assistant_messages():
|
||||
history = ChatHistory(
|
||||
messages=[
|
||||
{"role": AuthorRole.USER, "content": "User text message"},
|
||||
{"role": AuthorRole.ASSISTANT, "content": "Assistant message"},
|
||||
]
|
||||
)
|
||||
|
||||
expected_output = (
|
||||
"<bos>"
|
||||
"<start_of_turn>user\nUser text message<end_of_turn>\n"
|
||||
"<start_of_turn>model\nAssistant message<end_of_turn>\n"
|
||||
"<start_of_turn>model\n"
|
||||
)
|
||||
|
||||
assert gemma_template(history) == expected_output
|
||||
|
||||
|
||||
def test_gemma_template_with_only_user_message():
|
||||
history = ChatHistory(messages=[{"role": AuthorRole.USER, "content": "User text message"}])
|
||||
|
||||
expected_output = "<bos><start_of_turn>user\nUser text message<end_of_turn>\n<start_of_turn>model\n"
|
||||
|
||||
assert gemma_template(history) == expected_output
|
||||
|
||||
|
||||
def test_llama_template_with_user_and_assistant_messages():
|
||||
history = ChatHistory(
|
||||
messages=[
|
||||
{"role": AuthorRole.USER, "content": "User text message"},
|
||||
{"role": AuthorRole.ASSISTANT, "content": "Assistant message"},
|
||||
]
|
||||
)
|
||||
|
||||
expected_output = (
|
||||
"<|start_header_id|>user<|end_header_id|>\n\nUser text message<|eot_id|>"
|
||||
"<|start_header_id|>assistant<|end_header_id|>\n\nAssistant message<|eot_id|>"
|
||||
"<|start_header_id|>assistant<|end_header_id|>"
|
||||
)
|
||||
|
||||
assert llama_template(history) == expected_output
|
||||
|
||||
|
||||
def test_llama_template_with_only_user_message():
|
||||
history = ChatHistory(messages=[{"role": AuthorRole.USER, "content": "User text message"}])
|
||||
|
||||
expected_output = (
|
||||
"<|start_header_id|>user<|end_header_id|>\n\nUser text message<|eot_id|>"
|
||||
"<|start_header_id|>assistant<|end_header_id|>"
|
||||
)
|
||||
|
||||
assert llama_template(history) == expected_output
|
||||
@@ -0,0 +1,84 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
import pytest
|
||||
from pydantic import ValidationError
|
||||
|
||||
from semantic_kernel.connectors.ai.onnx.onnx_gen_ai_prompt_execution_settings import (
|
||||
OnnxGenAIPromptExecutionSettings,
|
||||
)
|
||||
from semantic_kernel.connectors.ai.prompt_execution_settings import PromptExecutionSettings
|
||||
|
||||
|
||||
def test_default_onnx_chat_prompt_execution_settings():
|
||||
settings = OnnxGenAIPromptExecutionSettings()
|
||||
assert settings.temperature is None
|
||||
assert settings.top_p is None
|
||||
|
||||
|
||||
def test_custom_onnx_chat_prompt_execution_settings():
|
||||
settings = OnnxGenAIPromptExecutionSettings(
|
||||
temperature=0.5,
|
||||
top_p=0.5,
|
||||
max_length=128,
|
||||
)
|
||||
assert settings.temperature == 0.5
|
||||
assert settings.top_p == 0.5
|
||||
assert settings.max_length == 128
|
||||
|
||||
|
||||
def test_onnx_chat_prompt_execution_settings_from_default_completion_config():
|
||||
settings = PromptExecutionSettings(service_id="test_service")
|
||||
chat_settings = OnnxGenAIPromptExecutionSettings.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
|
||||
|
||||
|
||||
def test_onnx_chat_prompt_execution_settings_from_onnx_prompt_execution_settings():
|
||||
chat_settings = OnnxGenAIPromptExecutionSettings(service_id="test_service", temperature=1.0)
|
||||
new_settings = OnnxGenAIPromptExecutionSettings(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_onnx_chat_prompt_execution_settings_from_custom_completion_config():
|
||||
settings = PromptExecutionSettings(
|
||||
service_id="test_service",
|
||||
extension_data={
|
||||
"temperature": 0.5,
|
||||
"top_p": 0.5,
|
||||
"max_length": 128,
|
||||
},
|
||||
)
|
||||
chat_settings = OnnxGenAIPromptExecutionSettings.from_prompt_execution_settings(settings)
|
||||
assert chat_settings.temperature == 0.5
|
||||
assert chat_settings.top_p == 0.5
|
||||
assert chat_settings.max_length == 128
|
||||
|
||||
|
||||
def test_create_options():
|
||||
settings = OnnxGenAIPromptExecutionSettings(
|
||||
service_id="test_service",
|
||||
extension_data={
|
||||
"temperature": 0.5,
|
||||
"top_p": 0.5,
|
||||
"max_length": 128,
|
||||
},
|
||||
)
|
||||
options = settings.prepare_settings_dict()
|
||||
assert options["temperature"] == 0.5
|
||||
assert options["top_p"] == 0.5
|
||||
assert options["max_length"] == 128
|
||||
|
||||
|
||||
def test_create_options_with_wrong_parameter():
|
||||
with pytest.raises(ValidationError):
|
||||
OnnxGenAIPromptExecutionSettings(
|
||||
service_id="test_service",
|
||||
function_choice_behavior="auto",
|
||||
extension_data={
|
||||
"temperature": 10.0,
|
||||
"top_p": 0.5,
|
||||
"max_length": 128,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user