chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import logging
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import TYPE_CHECKING, Annotated, Any
|
||||
from unittest.mock import MagicMock
|
||||
from uuid import uuid4
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pytest import fixture
|
||||
|
||||
from semantic_kernel.agents import Agent, DeclarativeSpecMixin, register_agent_type
|
||||
from semantic_kernel.data.vector import VectorStoreCollectionDefinition, VectorStoreField, vectorstoremodel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_kernel import Kernel
|
||||
from semantic_kernel.contents import ChatHistory
|
||||
from semantic_kernel.filters import FunctionInvocationContext
|
||||
from semantic_kernel.functions import KernelFunction
|
||||
from semantic_kernel.services.ai_service_client_base import AIServiceClientBase
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
logging.getLogger("tests.utils").setLevel(logging.INFO)
|
||||
logging.getLogger("openai").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpx").setLevel(logging.WARNING)
|
||||
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
||||
logging.getLogger("semantic_kernel").setLevel(logging.INFO)
|
||||
|
||||
|
||||
# region: Kernel fixtures
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def kernel() -> "Kernel":
|
||||
from semantic_kernel import Kernel
|
||||
|
||||
return Kernel()
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def service() -> "AIServiceClientBase":
|
||||
from semantic_kernel.services.ai_service_client_base import AIServiceClientBase
|
||||
|
||||
return AIServiceClientBase(service_id="service", ai_model_id="ai_model_id")
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def default_service() -> "AIServiceClientBase":
|
||||
from semantic_kernel.services.ai_service_client_base import AIServiceClientBase
|
||||
|
||||
return AIServiceClientBase(service_id="default", ai_model_id="ai_model_id")
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def kernel_with_service(kernel: "Kernel", service: "AIServiceClientBase") -> "Kernel":
|
||||
kernel.add_service(service)
|
||||
return kernel
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def kernel_with_default_service(kernel: "Kernel", default_service: "AIServiceClientBase") -> "Kernel":
|
||||
kernel.add_service(default_service)
|
||||
return kernel
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def not_decorated_native_function() -> Callable:
|
||||
def not_decorated_native_function(arg1: str) -> str:
|
||||
return "test"
|
||||
|
||||
return not_decorated_native_function
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def decorated_native_function() -> Callable:
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
|
||||
@kernel_function(name="getLightStatus")
|
||||
def decorated_native_function(arg1: str) -> str:
|
||||
return "test"
|
||||
|
||||
return decorated_native_function
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def custom_plugin_class():
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
|
||||
class CustomPlugin:
|
||||
@kernel_function(name="getLightStatus")
|
||||
def decorated_native_function(self) -> str:
|
||||
return "test"
|
||||
|
||||
return CustomPlugin
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def experimental_plugin_class():
|
||||
from semantic_kernel.functions.kernel_function_decorator import kernel_function
|
||||
from semantic_kernel.utils.feature_stage_decorator import experimental
|
||||
|
||||
@experimental
|
||||
class ExperimentalPlugin:
|
||||
@kernel_function(name="getLightStatus")
|
||||
def decorated_native_function(self) -> str:
|
||||
return "test"
|
||||
|
||||
return ExperimentalPlugin
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def auto_function_invocation_filter() -> Callable:
|
||||
"""A filter that will be called for each function call in the response."""
|
||||
from semantic_kernel.filters import AutoFunctionInvocationContext
|
||||
|
||||
async def auto_function_invocation_filter(context: AutoFunctionInvocationContext, next):
|
||||
await next(context)
|
||||
context.terminate = True
|
||||
|
||||
return auto_function_invocation_filter
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def create_mock_function() -> Callable:
|
||||
from semantic_kernel.contents.streaming_text_content import StreamingTextContent
|
||||
from semantic_kernel.functions.function_result import FunctionResult
|
||||
from semantic_kernel.functions.kernel_function import KernelFunction
|
||||
from semantic_kernel.functions.kernel_function_metadata import KernelFunctionMetadata
|
||||
|
||||
async def stream_func(*args, **kwargs):
|
||||
yield [StreamingTextContent(choice_index=0, text="test", metadata={})]
|
||||
|
||||
def create_mock_function(name: str, value: str = "test") -> "KernelFunction":
|
||||
kernel_function_metadata = KernelFunctionMetadata(
|
||||
name=name,
|
||||
plugin_name="TestPlugin",
|
||||
description="test description",
|
||||
parameters=[],
|
||||
is_prompt=True,
|
||||
is_asynchronous=True,
|
||||
)
|
||||
|
||||
class CustomKernelFunction(KernelFunction):
|
||||
call_count: int = 0
|
||||
|
||||
async def _invoke_internal_stream(
|
||||
self,
|
||||
context: "FunctionInvocationContext",
|
||||
) -> None:
|
||||
self.call_count += 1
|
||||
context.result = FunctionResult(
|
||||
function=kernel_function_metadata,
|
||||
value=stream_func(),
|
||||
)
|
||||
|
||||
async def _invoke_internal(self, context: "FunctionInvocationContext"):
|
||||
self.call_count += 1
|
||||
context.result = FunctionResult(function=kernel_function_metadata, value=value, metadata={})
|
||||
|
||||
return CustomKernelFunction(metadata=kernel_function_metadata)
|
||||
|
||||
return create_mock_function
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def get_tool_call_mock():
|
||||
from semantic_kernel.contents.function_call_content import FunctionCallContent
|
||||
|
||||
tool_call_mock = MagicMock(spec=FunctionCallContent)
|
||||
tool_call_mock.split_name_dict.return_value = {"arg_name": "arg_value"}
|
||||
tool_call_mock.to_kernel_arguments.return_value = {"arg_name": "arg_value"}
|
||||
tool_call_mock.name = "test-function"
|
||||
tool_call_mock.function_name = "function"
|
||||
tool_call_mock.plugin_name = "test"
|
||||
tool_call_mock.arguments = {"arg_name": "arg_value"}
|
||||
tool_call_mock.ai_model_id = None
|
||||
tool_call_mock.metadata = {}
|
||||
tool_call_mock.index = 0
|
||||
tool_call_mock.parse_arguments.return_value = {"arg_name": "arg_value"}
|
||||
tool_call_mock.id = "test_id"
|
||||
|
||||
return tool_call_mock
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def chat_history() -> "ChatHistory":
|
||||
from semantic_kernel.contents.chat_history import ChatHistory
|
||||
|
||||
return ChatHistory()
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def prompt() -> str:
|
||||
return "test prompt"
|
||||
|
||||
|
||||
# region: Connector Settings fixtures
|
||||
@fixture
|
||||
def exclude_list(request):
|
||||
"""Fixture that returns a list of environment variables to exclude."""
|
||||
return request.param if hasattr(request, "param") else []
|
||||
|
||||
|
||||
@fixture
|
||||
def override_env_param_dict(request):
|
||||
"""Fixture that returns a dict of environment variables to override."""
|
||||
return request.param if hasattr(request, "param") else {}
|
||||
|
||||
|
||||
# These two fixtures are used for multiple things, also non-connector tests
|
||||
@fixture()
|
||||
def azure_openai_unit_test_env(monkeypatch, exclude_list, override_env_param_dict):
|
||||
"""Fixture to set environment variables for AzureOpenAISettings."""
|
||||
if exclude_list is None:
|
||||
exclude_list = []
|
||||
|
||||
if override_env_param_dict is None:
|
||||
override_env_param_dict = {}
|
||||
|
||||
env_vars = {
|
||||
"AZURE_OPENAI_CHAT_DEPLOYMENT_NAME": "test_chat_deployment",
|
||||
"AZURE_OPENAI_TEXT_DEPLOYMENT_NAME": "test_text_deployment",
|
||||
"AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME": "test_embedding_deployment",
|
||||
"AZURE_OPENAI_TEXT_TO_IMAGE_DEPLOYMENT_NAME": "test_text_to_image_deployment",
|
||||
"AZURE_OPENAI_AUDIO_TO_TEXT_DEPLOYMENT_NAME": "test_audio_to_text_deployment",
|
||||
"AZURE_OPENAI_TEXT_TO_AUDIO_DEPLOYMENT_NAME": "test_text_to_audio_deployment",
|
||||
"AZURE_OPENAI_REALTIME_DEPLOYMENT_NAME": "test_realtime_deployment",
|
||||
"AZURE_OPENAI_API_KEY": "test_api_key",
|
||||
"AZURE_OPENAI_ENDPOINT": "https://test-endpoint.com",
|
||||
"AZURE_OPENAI_API_VERSION": "2023-03-15-preview",
|
||||
"AZURE_OPENAI_BASE_URL": "https://test_text_deployment.test-base-url.com",
|
||||
"AZURE_OPENAI_TOKEN_ENDPOINT": "https://test-token-endpoint.com",
|
||||
}
|
||||
|
||||
env_vars.update(override_env_param_dict)
|
||||
|
||||
for key, value in env_vars.items():
|
||||
if key not in exclude_list:
|
||||
monkeypatch.setenv(key, value)
|
||||
else:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
return env_vars
|
||||
|
||||
|
||||
@fixture()
|
||||
def openai_unit_test_env(monkeypatch, exclude_list, override_env_param_dict):
|
||||
"""Fixture to set environment variables for OpenAISettings."""
|
||||
if exclude_list is None:
|
||||
exclude_list = []
|
||||
|
||||
if override_env_param_dict is None:
|
||||
override_env_param_dict = {}
|
||||
|
||||
env_vars = {
|
||||
"OPENAI_API_KEY": "test_api_key",
|
||||
"OPENAI_ORG_ID": "test_org_id",
|
||||
"OPENAI_RESPONSES_MODEL_ID": "test_responses_model_id",
|
||||
"OPENAI_CHAT_MODEL_ID": "test_chat_model_id",
|
||||
"OPENAI_TEXT_MODEL_ID": "test_text_model_id",
|
||||
"OPENAI_EMBEDDING_MODEL_ID": "test_embedding_model_id",
|
||||
"OPENAI_TEXT_TO_IMAGE_MODEL_ID": "test_text_to_image_model_id",
|
||||
"OPENAI_AUDIO_TO_TEXT_MODEL_ID": "test_audio_to_text_model_id",
|
||||
"OPENAI_TEXT_TO_AUDIO_MODEL_ID": "test_text_to_audio_model_id",
|
||||
"OPENAI_REALTIME_MODEL_ID": "test_realtime_model_id",
|
||||
}
|
||||
|
||||
env_vars.update(override_env_param_dict)
|
||||
|
||||
for key, value in env_vars.items():
|
||||
if key not in exclude_list:
|
||||
monkeypatch.setenv(key, value)
|
||||
else:
|
||||
monkeypatch.delenv(key, raising=False)
|
||||
|
||||
return env_vars
|
||||
|
||||
|
||||
# region: Data Model Fixtures
|
||||
# some of these fixtures are used in both unit and integration tests
|
||||
@fixture
|
||||
def index_kind(request) -> str:
|
||||
if hasattr(request, "param"):
|
||||
return request.param
|
||||
return "hnsw"
|
||||
|
||||
|
||||
@fixture
|
||||
def distance_function(request) -> str:
|
||||
if hasattr(request, "param"):
|
||||
return request.param
|
||||
return "cosine_similarity"
|
||||
|
||||
|
||||
@fixture
|
||||
def vector_property_type(request) -> str:
|
||||
if hasattr(request, "param"):
|
||||
return request.param
|
||||
return "float"
|
||||
|
||||
|
||||
@fixture
|
||||
def dimensions(request) -> int:
|
||||
if hasattr(request, "param"):
|
||||
return request.param
|
||||
return 5
|
||||
|
||||
|
||||
@fixture
|
||||
def dataclass_vector_data_model(
|
||||
index_kind: str, distance_function: str, vector_property_type: str, dimensions: int
|
||||
) -> object:
|
||||
@vectorstoremodel
|
||||
@dataclass
|
||||
class MyDataModel:
|
||||
vector: Annotated[
|
||||
list[float] | str | None,
|
||||
VectorStoreField(
|
||||
"vector",
|
||||
index_kind=index_kind,
|
||||
dimensions=dimensions,
|
||||
distance_function=distance_function,
|
||||
type=vector_property_type,
|
||||
),
|
||||
] = None
|
||||
id: Annotated[str, VectorStoreField("key", type="str")] = field(default_factory=lambda: str(uuid4()))
|
||||
content: Annotated[str, VectorStoreField("data", type="str")] = "content1"
|
||||
|
||||
return MyDataModel
|
||||
|
||||
|
||||
@fixture
|
||||
def definition(
|
||||
index_kind: str, distance_function: str, vector_property_type: str, dimensions: int
|
||||
) -> VectorStoreCollectionDefinition:
|
||||
return VectorStoreCollectionDefinition(
|
||||
fields=[
|
||||
VectorStoreField("key", name="id", type="str"),
|
||||
VectorStoreField("data", name="content", type="str", is_full_text_indexed=True),
|
||||
VectorStoreField(
|
||||
"vector",
|
||||
name="vector",
|
||||
dimensions=dimensions,
|
||||
index_kind=index_kind,
|
||||
distance_function=distance_function,
|
||||
type=vector_property_type,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@fixture
|
||||
def definition_pandas(index_kind: str, distance_function: str, vector_property_type: str, dimensions: int) -> object:
|
||||
import pandas as pd
|
||||
|
||||
return VectorStoreCollectionDefinition(
|
||||
fields=[
|
||||
VectorStoreField(
|
||||
"vector",
|
||||
name="vector",
|
||||
index_kind=index_kind,
|
||||
dimensions=dimensions,
|
||||
distance_function=distance_function,
|
||||
type=vector_property_type,
|
||||
),
|
||||
VectorStoreField("key", name="id"),
|
||||
VectorStoreField("data", name="content", type="str"),
|
||||
],
|
||||
container_mode=True,
|
||||
to_dict=lambda x: x.to_dict(orient="records"),
|
||||
from_dict=lambda x, **_: pd.DataFrame(x),
|
||||
)
|
||||
|
||||
|
||||
@fixture
|
||||
def record_type(index_kind: str, distance_function: str, vector_property_type: str, dimensions: int) -> object:
|
||||
@vectorstoremodel
|
||||
class DataModelClass(BaseModel):
|
||||
content: Annotated[str, VectorStoreField("data")]
|
||||
vector: Annotated[
|
||||
list[float] | str | None,
|
||||
VectorStoreField(
|
||||
"vector",
|
||||
type=vector_property_type,
|
||||
dimensions=dimensions,
|
||||
index_kind=index_kind,
|
||||
distance_function=distance_function,
|
||||
),
|
||||
] = None
|
||||
id: Annotated[str, VectorStoreField("key")]
|
||||
|
||||
def model_post_init(self, context: Any) -> None:
|
||||
if self.vector is None:
|
||||
self.vector = self.content
|
||||
|
||||
return DataModelClass
|
||||
|
||||
|
||||
@fixture
|
||||
def record_type_with_key_as_key_field(
|
||||
index_kind: str, distance_function: str, vector_property_type: str, dimensions: int
|
||||
) -> object:
|
||||
"""Data model type with key as key field."""
|
||||
|
||||
@vectorstoremodel
|
||||
class DataModelClass(BaseModel):
|
||||
content: Annotated[str, VectorStoreField("data")]
|
||||
vector: Annotated[
|
||||
str | list[float] | None,
|
||||
VectorStoreField(
|
||||
"vector",
|
||||
index_kind=index_kind,
|
||||
distance_function=distance_function,
|
||||
type=vector_property_type,
|
||||
dimensions=dimensions,
|
||||
),
|
||||
]
|
||||
key: Annotated[str, VectorStoreField("key")]
|
||||
|
||||
return DataModelClass
|
||||
|
||||
|
||||
# region Declarative Spec
|
||||
|
||||
|
||||
@register_agent_type("test_agent")
|
||||
class TestAgent(DeclarativeSpecMixin, Agent):
|
||||
@classmethod
|
||||
def resolve_placeholders(cls, yaml_str, settings=None, extras=None):
|
||||
return yaml_str
|
||||
|
||||
@classmethod
|
||||
async def _from_dict(cls, data, **kwargs):
|
||||
return cls(
|
||||
name=data.get("name"),
|
||||
description=data.get("description"),
|
||||
instructions=data.get("instructions"),
|
||||
kernel=data.get("kernel"),
|
||||
)
|
||||
|
||||
async def get_response(self, messages, instructions_override=None):
|
||||
return "test response"
|
||||
|
||||
async def invoke(self, messages, **kwargs):
|
||||
return "invoke result"
|
||||
|
||||
async def invoke_stream(self, messages, **kwargs):
|
||||
yield "stream result"
|
||||
|
||||
|
||||
@fixture(scope="session")
|
||||
def test_agent_cls():
|
||||
return TestAgent
|
||||
|
||||
|
||||
# endregion
|
||||
Reference in New Issue
Block a user