diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 023b15d3bb74..38acc3663bca 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -56,6 +56,9 @@ A2A_USER_INPUT= AZURE_TEST_RUN_LIVE=false AZURE_SKIP_LIVE_RECORDING=true +CONTAINER_APP_RESOURCE_ID= +CONTAINER_INGRESS_SUBDOMAIN_SUFFIX= + # Used in Fine-tuning tests COMPLETED_OAI_MODEL_SFT_FINE_TUNING_JOB_ID= COMPLETED_OAI_MODEL_RFT_FINE_TUNING_JOB_ID= diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index a99e1018791d..b6cf2082b929 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_b0e6f379ee" + "Tag": "python/ai/azure-ai-projects_8677538e54" } diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py index 720a1e593d68..c0130b4ffde1 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/_patch.py @@ -16,6 +16,7 @@ from azure.identity import get_bearer_token_provider from ._client import AIProjectClient as AIProjectClientGenerated from .operations import TelemetryOperations +from ._version import VERSION logger = logging.getLogger(__name__) @@ -25,7 +26,7 @@ def _patch_user_agent(user_agent: Optional[str]) -> str: # All authenticated external clients exposed by this client will have this application id # set on their user-agent. For more info on user-agent HTTP header, see: # https://azure.github.io/azure-sdk/general_azurecore.html#telemetry-policy - USER_AGENT_APP_ID = "AIProjectClient" + USER_AGENT_APP_ID = f"AIProjectClient/Python-{VERSION}" if user_agent: # If the calling application has set "user_agent" when constructing the AIProjectClient, @@ -120,6 +121,7 @@ def get_openai_client(self, **kwargs: Any) -> "OpenAI": # type: ignore[name-def * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai" appended. * ``api-version`` set to "2025-05-15-preview" by default, unless overridden by the ``api_version`` keyword argument. * ``api_key`` set to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient constructor, with scope "https://ai.azure.com/.default". + * ``default_headers`` will automatically include a User-Agent header with the default value "AIProjectClient/Python-{version}". .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. @@ -136,6 +138,13 @@ def get_openai_client(self, **kwargs: Any) -> "OpenAI": # type: ignore[name-def if "default_query" not in kwargs: kwargs["default_query"] = {"api-version": "2025-11-15-preview"} + # Set User-Agent header + user_headers = kwargs.pop("default_headers", {}) + if "User-Agent" in user_headers: + user_headers["User-Agent"] = _patch_user_agent(user_headers["User-Agent"]) + else: + user_headers["User-Agent"] = self._patched_user_agent + logger.debug( # pylint: disable=specify-parameter-names-in-call "[get_openai_client] Creating OpenAI client using Entra ID authentication, base_url = `%s`", # pylint: disable=line-too-long base_url, @@ -249,6 +258,7 @@ def _log_request_body(self, request: httpx.Request) -> None: ), base_url=base_url, http_client=http_client, + default_headers=user_headers, **kwargs, ) diff --git a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py index d55e69cf7103..ac27baa72c30 100644 --- a/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py +++ b/sdk/ai/azure-ai-projects/azure/ai/projects/aio/_patch.py @@ -105,6 +105,7 @@ def get_openai_client(self, **kwargs: Any) -> "AsyncOpenAI": # type: ignore[nam * ``base_url`` set to the endpoint provided to the AIProjectClient constructor, with "/openai" appended. * ``api-version`` set to "2025-05-15-preview" by default, unless overridden by the ``api_version`` keyword argument. * ``api_key`` set to a get_bearer_token_provider() callable that uses the TokenCredential provided to the AIProjectClient constructor, with scope "https://ai.azure.com/.default". + * ``default_headers`` will automatically include a User-Agent header with the default value "AIProjectClient/Python-{version}". .. note:: The packages ``openai`` and ``azure.identity`` must be installed prior to calling this method. @@ -121,6 +122,13 @@ def get_openai_client(self, **kwargs: Any) -> "AsyncOpenAI": # type: ignore[nam if "default_query" not in kwargs: kwargs["default_query"] = {"api-version": "2025-11-15-preview"} + # Set User-Agent header + user_headers = kwargs.pop("default_headers", {}) + if "User-Agent" in user_headers: + user_headers["User-Agent"] = _patch_user_agent(user_headers["User-Agent"]) + else: + user_headers["User-Agent"] = self._patched_user_agent + logger.debug( # pylint: disable=specify-parameter-names-in-call "[get_openai_client] Creating OpenAI client using Entra ID authentication, base_url = `%s`", # pylint: disable=line-too-long base_url, @@ -234,6 +242,7 @@ def _log_request_body(self, request: httpx.Request) -> None: ), base_url=base_url, http_client=http_client, + default_headers=user_headers, **kwargs, ) diff --git a/sdk/ai/azure-ai-projects/tests/conftest.py b/sdk/ai/azure-ai-projects/tests/conftest.py index c28a47fb1170..459858283725 100644 --- a/sdk/ai/azure-ai-projects/tests/conftest.py +++ b/sdk/ai/azure-ai-projects/tests/conftest.py @@ -20,6 +20,7 @@ add_body_key_sanitizer, add_remove_header_sanitizer, add_body_regex_sanitizer, + add_header_regex_sanitizer, ) if not load_dotenv(find_dotenv(), override=True): @@ -164,6 +165,9 @@ def sanitize_url_paths(): remove_batch_sanitizers(["AZSDK3493"]) remove_batch_sanitizers(["AZSDK3430"]) + # Sanitize User-Agent header + add_header_regex_sanitizer(key="User-Agent", value="sanitized-user-agent") + # Sanitize ARM operation headers that contain certificates and identifiers add_general_regex_sanitizer(regex=r"[?&]t=[0-9]+", value="&t=sanitized-timestamp") add_general_regex_sanitizer(regex=r"[?&]c=[^&\"]+", value="&c=sanitized-certificate")