Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: Updates to user agent. Add unit tests. #6824

Merged
merged 6 commits into from
Jun 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion python/semantic_kernel/connectors/ai/open_ai/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@
from typing import Final

DEFAULT_AZURE_API_VERSION: Final[str] = "2024-02-01"
USER_AGENT: Final[str] = "User-Agent"
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from openai import AsyncAzureOpenAI
from pydantic import ConfigDict, validate_call

from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION, USER_AGENT
from semantic_kernel.connectors.ai.open_ai.const import DEFAULT_AZURE_API_VERSION
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIHandler, OpenAIModelTypes
from semantic_kernel.connectors.telemetry import APP_INFO, prepend_semantic_kernel_to_user_agent
from semantic_kernel.const import USER_AGENT
from semantic_kernel.exceptions import ServiceInitializationError
from semantic_kernel.kernel_pydantic import HttpsUrl

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
from openai import AsyncOpenAI
from pydantic import ConfigDict, Field, validate_call

from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.connectors.ai.open_ai.services.open_ai_handler import OpenAIHandler
from semantic_kernel.connectors.ai.open_ai.services.open_ai_model_types import OpenAIModelTypes
from semantic_kernel.connectors.telemetry import APP_INFO, prepend_semantic_kernel_to_user_agent
from semantic_kernel.const import USER_AGENT
from semantic_kernel.exceptions import ServiceInitializationError

logger: logging.Logger = logging.getLogger(__name__)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from azure.search.documents.indexes.models import SearchableField, SearchField, SearchFieldDataType, SimpleField
from dotenv import load_dotenv

from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.const import USER_AGENT
from semantic_kernel.exceptions import ServiceInitializationError
from semantic_kernel.memory.memory_record import MemoryRecord

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@
import httpx
from openapi_core import Spec

from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.connectors.openapi_plugin.models.rest_api_operation import RestApiOperation
from semantic_kernel.connectors.openapi_plugin.models.rest_api_operation_expected_response import (
RestApiOperationExpectedResponse,
)
from semantic_kernel.connectors.openapi_plugin.models.rest_api_operation_payload import RestApiOperationPayload
from semantic_kernel.connectors.openapi_plugin.models.rest_api_operation_run_options import RestApiOperationRunOptions
from semantic_kernel.connectors.telemetry import APP_INFO, prepend_semantic_kernel_to_user_agent
from semantic_kernel.exceptions.function_exceptions import FunctionExecutionException
from semantic_kernel.functions.kernel_arguments import KernelArguments
from semantic_kernel.utils.experimental_decorator import experimental_class
Expand Down Expand Up @@ -124,8 +124,6 @@ async def run_operation(
options: RestApiOperationRunOptions | None = None,
) -> str:
"""Run the operation."""
from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT

url = self.build_operation_url(
operation=operation,
arguments=arguments,
Expand All @@ -143,7 +141,9 @@ async def run_operation(
headers_update = await self.auth_callback(headers=headers)
headers.update(headers_update)

headers[USER_AGENT] = " ".join((HTTP_USER_AGENT, headers.get(USER_AGENT, ""))).rstrip()
if APP_INFO:
headers.update(APP_INFO)
headers = prepend_semantic_kernel_to_user_agent(headers)

if "Content-Type" not in headers:
headers["Content-Type"] = self._get_first_response_media_type(operation.responses)
Expand Down
16 changes: 10 additions & 6 deletions python/semantic_kernel/connectors/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
from importlib.metadata import PackageNotFoundError, version
from typing import Any

from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.const import USER_AGENT

TELEMETRY_DISABLED_ENV_VAR = "AZURE_TELEMETRY_DISABLED"

IS_TELEMETRY_ENABLED = os.environ.get(TELEMETRY_DISABLED_ENV_VAR, "false").lower() not in ["true", "1"]

HTTP_USER_AGENT = "Semantic-Kernel"
HTTP_USER_AGENT = "semantic-kernel-python"

try:
version_info = version("semantic-kernel")
Expand All @@ -19,22 +19,26 @@

APP_INFO = (
{
"Semantic-Kernel-Version": f"python-{version_info}",
"semantic-kernel-version": f"python/{version_info}",
}
if IS_TELEMETRY_ENABLED
else None
)


def prepend_semantic_kernel_to_user_agent(headers: dict[str, Any]):
"""Prepend "Semantic-Kernel" to the User-Agent in the headers.
"""Prepend "semantic-kernel" to the User-Agent in the headers.

Args:
headers: The existing headers dictionary.

Returns:
The modified headers dictionary with "Semantic-Kernel" prepended to the User-Agent.
The modified headers dictionary with "semantic-kernel" prepended to the User-Agent.
"""
headers[USER_AGENT] = f"{HTTP_USER_AGENT} {headers[USER_AGENT]}" if USER_AGENT in headers else f"{HTTP_USER_AGENT}"
headers[USER_AGENT] = (
f"{HTTP_USER_AGENT}/{version_info} {headers[USER_AGENT]}"
if USER_AGENT in headers
else f"{HTTP_USER_AGENT}/{version_info}"
)

return headers
1 change: 1 addition & 0 deletions python/semantic_kernel/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@

METADATA_EXCEPTION_KEY: Final[str] = "exception"
DEFAULT_SERVICE_NAME: Final[str] = "default"
USER_AGENT: Final[str] = "User-Agent"
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
import httpx
from pydantic import ValidationError

from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.connectors.telemetry import HTTP_USER_AGENT, version_info
from semantic_kernel.const import USER_AGENT
from semantic_kernel.core_plugins.sessions_python_tool.sessions_python_settings import (
ACASessionsSettings,
SessionsPythonSettings,
Expand Down Expand Up @@ -93,7 +93,7 @@ def _sanitize_input(self, code: str) -> str:
code = re.sub(r"^(\s|`)*(?i:python)?\s*", "", code)
# Removes whitespace & ` from end
return re.sub(r"(\s|`)*$", "", code)

def _construct_remote_file_path(self, remote_file_path: str) -> str:
"""Construct the remote file path.

Expand All @@ -109,8 +109,8 @@ def _construct_remote_file_path(self, remote_file_path: str) -> str:

def _build_url_with_version(self, base_url, endpoint, params):
"""Builds a URL with the provided base URL, endpoint, and query parameters."""
params['api-version'] = SESSIONS_API_VERSION
query_string = '&'.join([f"{key}={value}" for key, value in params.items()])
params["api-version"] = SESSIONS_API_VERSION
query_string = "&".join([f"{key}={value}" for key, value in params.items()])
return f"{base_url}{endpoint}?{query_string}"

@kernel_function(
Expand Down Expand Up @@ -178,7 +178,9 @@ async def upload_file(
self,
*,
local_file_path: Annotated[str, "The path to the local file on the machine"],
remote_file_path: Annotated[str | None, "The remote path to the file in the session. Defaults to /mnt/data"] = None, # noqa: E501
remote_file_path: Annotated[
str | None, "The remote path to the file in the session. Defaults to /mnt/data"
] = None,
) -> Annotated[SessionsRemoteFileMetadata, "The metadata of the uploaded file"]:
"""Upload a file to the session pool.

Expand Down Expand Up @@ -222,7 +224,7 @@ async def upload_file(
response.raise_for_status()

response_json = response.json()
return SessionsRemoteFileMetadata.from_dict(response_json['$values'][0])
return SessionsRemoteFileMetadata.from_dict(response_json["$values"][0])

@kernel_function(name="list_files", description="Lists all files in the provided Session ID")
async def list_files(self) -> list[SessionsRemoteFileMetadata]:
Expand All @@ -242,7 +244,7 @@ async def list_files(self) -> list[SessionsRemoteFileMetadata]:
url = self._build_url_with_version(
base_url=self.pool_management_endpoint,
endpoint="python/files",
params={"identifier": self.settings.session_id}
params={"identifier": self.settings.session_id},
)

response = await self.http_client.get(
Expand Down Expand Up @@ -275,10 +277,7 @@ async def download_file(self, *, remote_file_path: str, local_file_path: str | N
url = self._build_url_with_version(
base_url=self.pool_management_endpoint,
endpoint="python/downloadFile",
params={
"identifier": self.settings.session_id,
"filename": remote_file_path
}
params={"identifier": self.settings.session_id, "filename": remote_file_path},
)

response = await self.http_client.get(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ def test_google_palm_chat_completion_init_with_empty_api_key(google_palm_unit_te
with pytest.raises(ServiceInitializationError):
GooglePalmChatCompletion(
ai_model_id=ai_model_id,
env_file_path="test.env",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def test_google_palm_text_completion_init_with_empty_api_key(google_palm_unit_te
with pytest.raises(ServiceInitializationError):
GooglePalmTextCompletion(
ai_model_id=ai_model_id,
env_file_path="test.env",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def test_google_palm_text_embedding_init_with_empty_api_key(google_palm_unit_tes
with pytest.raises(ServiceInitializationError):
GooglePalmTextEmbedding(
ai_model_id=ai_model_id,
env_file_path="test.env",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.function_call_behavior import FunctionCallBehavior
from semantic_kernel.connectors.ai.open_ai import AzureChatCompletion
from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.connectors.ai.open_ai.exceptions.content_filter_ai_exception import (
ContentFilterAIException,
ContentFilterResultSeverity,
Expand All @@ -22,6 +21,7 @@
AzureChatPromptExecutionSettings,
ExtraBody,
)
from semantic_kernel.const import USER_AGENT
from semantic_kernel.contents.chat_history import ChatHistory
from semantic_kernel.exceptions import ServiceInitializationError, ServiceInvalidExecutionSettingsError
from semantic_kernel.exceptions.service_exceptions import ServiceResponseException
Expand Down Expand Up @@ -58,19 +58,25 @@ def test_azure_chat_completion_init_base_url(azure_openai_unit_test_env) -> None
@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_CHAT_DEPLOYMENT_NAME"]], indirect=True)
def test_azure_chat_completion_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion()
AzureChatCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_chat_completion_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion()
AzureChatCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_azure_chat_completion_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureChatCompletion()
AzureChatCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
Expand Down Expand Up @@ -450,9 +456,7 @@ async def test_azure_chat_completion_auto_invoke_false_no_kernel_provided_throws
prompt = "some prompt that would trigger the content filtering"
chat_history.add_user_message(prompt)
complete_prompt_execution_settings = AzureChatPromptExecutionSettings(
function_call_behavior=FunctionCallBehavior.EnableFunctions(
auto_invoke=False, filters={}
)
function_call_behavior=FunctionCallBehavior.EnableFunctions(auto_invoke=False, filters={})
)

test_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,28 @@ def test_azure_text_completion_init_with_custom_header(azure_openai_unit_test_en


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_TEXT_DEPLOYMENT_NAME"]], indirect=True)
def test_azure_text_completion_init_with_empty_deployment_name(azure_openai_unit_test_env) -> None:
def test_azure_text_completion_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()
AzureTextCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_text_completion_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextCompletion()
AzureTextCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_ENDPOINT", "AZURE_OPENAI_BASE_URL"]], indirect=True)
def test_azure_text_completion_init_with_empty_endpoint_and_base_url(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextCompletion()
AzureTextCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,25 @@ def test_azure_text_embedding_init(azure_openai_unit_test_env) -> None:
@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()
AzureTextEmbedding(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["AZURE_OPENAI_API_KEY"]], indirect=True)
def test_azure_text_embedding_init_with_empty_api_key(azure_openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
AzureTextEmbedding()
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()
AzureTextEmbedding(
env_file_path="test.env",
)


@pytest.mark.parametrize("override_env_param_dict", [{"AZURE_OPENAI_ENDPOINT": "http://test.com"}], indirect=True)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import pytest

from semantic_kernel.connectors.ai.chat_completion_client_base import ChatCompletionClientBase
from semantic_kernel.connectors.ai.open_ai.const import USER_AGENT
from semantic_kernel.connectors.ai.open_ai.services.open_ai_chat_completion import OpenAIChatCompletion
from semantic_kernel.const import USER_AGENT
from semantic_kernel.exceptions.service_exceptions import ServiceInitializationError


Expand Down Expand Up @@ -46,7 +46,9 @@ def test_open_ai_chat_completion_init_with_default_header(openai_unit_test_env)
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_open_ai_chat_completion_init_with_empty_model_id(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAIChatCompletion()
OpenAIChatCompletion(
env_file_path="test.env",
)


@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
Expand All @@ -56,6 +58,7 @@ def test_open_ai_chat_completion_init_with_empty_api_key(openai_unit_test_env) -
with pytest.raises(ServiceInitializationError):
OpenAIChatCompletion(
ai_model_id=ai_model_id,
env_file_path="test.env",
)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ def test_open_ai_text_completion_init_with_default_header(openai_unit_test_env)
@pytest.mark.parametrize("exclude_list", [["OPENAI_API_KEY"]], indirect=True)
def test_open_ai_text_completion_init_with_empty_api_key(openai_unit_test_env) -> None:
with pytest.raises(ServiceInitializationError):
OpenAITextCompletion()
OpenAITextCompletion(
env_file_path="test.env",
)


def test_open_ai_text_completion_serialize(openai_unit_test_env) -> None:
Expand Down
Loading
Loading